summaryrefslogtreecommitdiffstats
path: root/src/display
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
commit35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch)
tree657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/display
parentInitial commit. (diff)
downloadinkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz
inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/display')
-rw-r--r--src/display/CMakeLists.txt130
-rw-r--r--src/display/README17
-rw-r--r--src/display/cairo-templates.h700
-rw-r--r--src/display/cairo-utils.cpp1746
-rw-r--r--src/display/cairo-utils.h267
-rw-r--r--src/display/canvas-arena.cpp385
-rw-r--r--src/display/canvas-arena.h72
-rw-r--r--src/display/canvas-axonomgrid.cpp758
-rw-r--r--src/display/canvas-axonomgrid.h99
-rw-r--r--src/display/canvas-bpath.cpp253
-rw-r--r--src/display/canvas-bpath.h114
-rw-r--r--src/display/canvas-debug.cpp98
-rw-r--r--src/display/canvas-debug.h42
-rw-r--r--src/display/canvas-grid.cpp1143
-rw-r--r--src/display/canvas-grid.h216
-rw-r--r--src/display/canvas-rotate.cpp291
-rw-r--r--src/display/canvas-rotate.h51
-rw-r--r--src/display/canvas-temporary-item-list.cpp82
-rw-r--r--src/display/canvas-temporary-item-list.h61
-rw-r--r--src/display/canvas-temporary-item.cpp80
-rw-r--r--src/display/canvas-temporary-item.h59
-rw-r--r--src/display/canvas-text.cpp323
-rw-r--r--src/display/canvas-text.h85
-rw-r--r--src/display/curve.cpp713
-rw-r--r--src/display/curve.h106
-rw-r--r--src/display/drawing-context.cpp158
-rw-r--r--src/display/drawing-context.h153
-rw-r--r--src/display/drawing-group.cpp164
-rw-r--r--src/display/drawing-group.h59
-rw-r--r--src/display/drawing-image.cpp254
-rw-r--r--src/display/drawing-image.h66
-rw-r--r--src/display/drawing-item.cpp1221
-rw-r--r--src/display/drawing-item.h256
-rw-r--r--src/display/drawing-pattern.cpp198
-rw-r--r--src/display/drawing-pattern.h87
-rw-r--r--src/display/drawing-shape.cpp441
-rw-r--r--src/display/drawing-shape.h69
-rw-r--r--src/display/drawing-surface.cpp399
-rw-r--r--src/display/drawing-surface.h100
-rw-r--r--src/display/drawing-text.cpp758
-rw-r--r--src/display/drawing-text.h94
-rw-r--r--src/display/drawing.cpp258
-rw-r--r--src/display/drawing.h127
-rw-r--r--src/display/gnome-canvas-acetate.cpp71
-rw-r--r--src/display/gnome-canvas-acetate.h49
-rw-r--r--src/display/grayscale.cpp105
-rw-r--r--src/display/grayscale.h38
-rw-r--r--src/display/guideline.cpp301
-rw-r--r--src/display/guideline.h74
-rw-r--r--src/display/nr-3dutils.cpp66
-rw-r--r--src/display/nr-3dutils.h105
-rw-r--r--src/display/nr-filter-blend.cpp177
-rw-r--r--src/display/nr-filter-blend.h67
-rw-r--r--src/display/nr-filter-colormatrix.cpp229
-rw-r--r--src/display/nr-filter-colormatrix.h79
-rw-r--r--src/display/nr-filter-component-transfer.cpp246
-rw-r--r--src/display/nr-filter-component-transfer.h67
-rw-r--r--src/display/nr-filter-composite.cpp196
-rw-r--r--src/display/nr-filter-composite.h61
-rw-r--r--src/display/nr-filter-convolve-matrix.cpp243
-rw-r--r--src/display/nr-filter-convolve-matrix.h76
-rw-r--r--src/display/nr-filter-diffuselighting.cpp239
-rw-r--r--src/display/nr-filter-diffuselighting.h70
-rw-r--r--src/display/nr-filter-displacement-map.cpp172
-rw-r--r--src/display/nr-filter-displacement-map.h60
-rw-r--r--src/display/nr-filter-flood.cpp143
-rw-r--r--src/display/nr-filter-flood.h60
-rw-r--r--src/display/nr-filter-gaussian.cpp758
-rw-r--r--src/display/nr-filter-gaussian.h84
-rw-r--r--src/display/nr-filter-image.cpp344
-rw-r--r--src/display/nr-filter-image.h68
-rw-r--r--src/display/nr-filter-merge.cpp126
-rw-r--r--src/display/nr-filter-merge.h55
-rw-r--r--src/display/nr-filter-morphology.cpp264
-rw-r--r--src/display/nr-filter-morphology.h64
-rw-r--r--src/display/nr-filter-offset.cpp114
-rw-r--r--src/display/nr-filter-offset.h56
-rw-r--r--src/display/nr-filter-primitive.cpp197
-rw-r--r--src/display/nr-filter-primitive.h157
-rw-r--r--src/display/nr-filter-skeleton.cpp69
-rw-r--r--src/display/nr-filter-skeleton.h60
-rw-r--r--src/display/nr-filter-slot.cpp301
-rw-r--r--src/display/nr-filter-slot.h131
-rw-r--r--src/display/nr-filter-specularlighting.cpp248
-rw-r--r--src/display/nr-filter-specularlighting.h72
-rw-r--r--src/display/nr-filter-tile.cpp141
-rw-r--r--src/display/nr-filter-tile.h49
-rw-r--r--src/display/nr-filter-turbulence.cpp446
-rw-r--r--src/display/nr-filter-turbulence.h96
-rw-r--r--src/display/nr-filter-types.h71
-rw-r--r--src/display/nr-filter-units.cpp176
-rw-r--r--src/display/nr-filter-units.h169
-rw-r--r--src/display/nr-filter-utils.h83
-rw-r--r--src/display/nr-filter.cpp499
-rw-r--r--src/display/nr-filter.h224
-rw-r--r--src/display/nr-light-types.h36
-rw-r--r--src/display/nr-light.cpp120
-rw-r--r--src/display/nr-light.h174
-rw-r--r--src/display/nr-style.cpp431
-rw-r--r--src/display/nr-style.h149
-rw-r--r--src/display/nr-svgfonts.cpp434
-rw-r--r--src/display/nr-svgfonts.h71
-rw-r--r--src/display/rendermode.h46
-rw-r--r--src/display/snap-indicator.cpp438
-rw-r--r--src/display/snap-indicator.h71
-rw-r--r--src/display/sodipodi-ctrl.cpp695
-rw-r--r--src/display/sodipodi-ctrl.h90
-rw-r--r--src/display/sodipodi-ctrlrect.cpp323
-rw-r--r--src/display/sodipodi-ctrlrect.h86
-rw-r--r--src/display/sp-canvas-group.h44
-rw-r--r--src/display/sp-canvas-item.h158
-rw-r--r--src/display/sp-canvas-util.cpp111
-rw-r--r--src/display/sp-canvas-util.h59
-rw-r--r--src/display/sp-canvas.cpp2959
-rw-r--r--src/display/sp-canvas.h302
-rw-r--r--src/display/sp-ctrlcurve.cpp192
-rw-r--r--src/display/sp-ctrlcurve.h55
-rw-r--r--src/display/sp-ctrlline.cpp173
-rw-r--r--src/display/sp-ctrlline.h62
-rw-r--r--src/display/sp-ctrlquadr.cpp174
-rw-r--r--src/display/sp-ctrlquadr.h42
121 files changed, 28364 insertions, 0 deletions
diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt
new file mode 100644
index 0000000..edbd2b4
--- /dev/null
+++ b/src/display/CMakeLists.txt
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(display_SRC
+ cairo-utils.cpp
+ canvas-arena.cpp
+ canvas-axonomgrid.cpp
+ canvas-bpath.cpp
+ canvas-debug.cpp
+ canvas-grid.cpp
+ canvas-rotate.cpp
+ canvas-temporary-item-list.cpp
+ canvas-temporary-item.cpp
+ canvas-text.cpp
+ curve.cpp
+ drawing-context.cpp
+ drawing-group.cpp
+ drawing-image.cpp
+ drawing-item.cpp
+ drawing-pattern.cpp
+ drawing-shape.cpp
+ drawing-surface.cpp
+ drawing-text.cpp
+ drawing.cpp
+ gnome-canvas-acetate.cpp
+ grayscale.cpp
+ guideline.cpp
+ nr-3dutils.cpp
+ nr-filter-blend.cpp
+ nr-filter-colormatrix.cpp
+ nr-filter-component-transfer.cpp
+ nr-filter-composite.cpp
+ nr-filter-convolve-matrix.cpp
+ nr-filter-diffuselighting.cpp
+ nr-filter-displacement-map.cpp
+ nr-filter-flood.cpp
+ nr-filter-gaussian.cpp
+ nr-filter-image.cpp
+ nr-filter-merge.cpp
+ nr-filter-morphology.cpp
+ nr-filter-offset.cpp
+ nr-filter-primitive.cpp
+ # nr-filter-skeleton.cpp
+ nr-filter-slot.cpp
+ nr-filter-specularlighting.cpp
+ nr-filter-tile.cpp
+ nr-filter-turbulence.cpp
+ nr-filter-units.cpp
+ nr-filter.cpp
+ nr-light.cpp
+ nr-style.cpp
+ nr-svgfonts.cpp
+ snap-indicator.cpp
+ sodipodi-ctrl.cpp
+ sodipodi-ctrlrect.cpp
+ sp-canvas-util.cpp
+ sp-canvas.cpp
+ sp-ctrlcurve.cpp
+ sp-ctrlline.cpp
+ sp-ctrlquadr.cpp
+
+
+ # -------
+ # Headers
+ cairo-templates.h
+ cairo-utils.h
+ canvas-arena.h
+ canvas-axonomgrid.h
+ canvas-bpath.h
+ canvas-debug.h
+ canvas-grid.h
+ canvas-rotate.h
+ canvas-temporary-item-list.h
+ canvas-temporary-item.h
+ canvas-text.h
+ curve.h
+ drawing-context.h
+ drawing-group.h
+ drawing-image.h
+ drawing-item.h
+ drawing-pattern.h
+ drawing-shape.h
+ drawing-surface.h
+ drawing-text.h
+ drawing.h
+ gnome-canvas-acetate.h
+ grayscale.h
+ guideline.h
+ nr-3dutils.h
+ nr-filter-blend.h
+ nr-filter-colormatrix.h
+ nr-filter-component-transfer.h
+ nr-filter-composite.h
+ nr-filter-convolve-matrix.h
+ nr-filter-diffuselighting.h
+ nr-filter-displacement-map.h
+ nr-filter-flood.h
+ nr-filter-gaussian.h
+ nr-filter-image.h
+ nr-filter-merge.h
+ nr-filter-morphology.h
+ nr-filter-offset.h
+ nr-filter-primitive.h
+ nr-filter-skeleton.h
+ nr-filter-slot.h
+ nr-filter-specularlighting.h
+ nr-filter-tile.h
+ nr-filter-turbulence.h
+ nr-filter-types.h
+ nr-filter-units.h
+ nr-filter-utils.h
+ nr-filter.h
+ nr-light-types.h
+ nr-light.h
+ nr-style.h
+ nr-svgfonts.h
+ rendermode.h
+ snap-indicator.h
+ sodipodi-ctrl.h
+ sodipodi-ctrlrect.h
+ sp-canvas-group.h
+ sp-canvas-item.h
+ sp-canvas-util.h
+ sp-canvas.h
+ sp-ctrlcurve.h
+ sp-ctrlline.h
+ sp-ctrlquadr.h
+)
+
+# add_inkscape_lib(display_LIB "${display_SRC}")
+add_inkscape_source("${display_SRC}")
diff --git a/src/display/README b/src/display/README
new file mode 100644
index 0000000..0a74759
--- /dev/null
+++ b/src/display/README
@@ -0,0 +1,17 @@
+
+
+This directory contains code for rendering graphics to the
+canvas. This includes rendering of SVG elements (DrawingItem and
+derived classes) as well as rendering of control items -
+nodes/handles/lines (CanvasItem and derived classes).
+
+Currently we rely on the "Cairo" graphics library for rendering.
+
+To do:
+
+* Split into three directories: drawing, canvas, common.
+* Remove SPCurve.
+
+
+
+
diff --git a/src/display/cairo-templates.h b/src/display/cairo-templates.h
new file mode 100644
index 0000000..9a3dd66
--- /dev/null
+++ b/src/display/cairo-templates.h
@@ -0,0 +1,700 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo software blending templates.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
+#define SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <glib.h>
+
+#ifdef HAVE_OPENMP
+#include <omp.h>
+#include "preferences.h"
+// single-threaded operation if the number of pixels is below this threshold
+static const int OPENMP_THRESHOLD = 2048;
+#endif
+
+#include <algorithm>
+#include <cairo.h>
+#include <cmath>
+#include "display/nr-3dutils.h"
+#include "display/cairo-utils.h"
+
+/**
+ * Blend two surfaces using the supplied functor.
+ * This template blends two Cairo image surfaces using a blending functor that takes
+ * two 32-bit ARGB pixel values and returns a modified 32-bit pixel value.
+ * Differences in input surface formats are handled transparently. In future, this template
+ * will also handle software fallback for GL surfaces.
+ */
+template <typename Blend>
+void ink_cairo_surface_blend(cairo_surface_t *in1, cairo_surface_t *in2, cairo_surface_t *out, Blend blend)
+{
+ cairo_surface_flush(in1);
+ cairo_surface_flush(in2);
+
+ // ASSUMPTIONS
+ // 1. Cairo ARGB32 surface strides are always divisible by 4
+ // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
+ // 3. Both surfaces are of the same size
+ // 4. Output surface is ARGB32 if at least one input is ARGB32
+
+ int w = cairo_image_surface_get_width(in2);
+ int h = cairo_image_surface_get_height(in2);
+ int stride1 = cairo_image_surface_get_stride(in1);
+ int stride2 = cairo_image_surface_get_stride(in2);
+ int strideout = cairo_image_surface_get_stride(out);
+ int bpp1 = cairo_image_surface_get_format(in1) == CAIRO_FORMAT_A8 ? 1 : 4;
+ int bpp2 = cairo_image_surface_get_format(in2) == CAIRO_FORMAT_A8 ? 1 : 4;
+ int bppout = std::max(bpp1, bpp2);
+
+ // Check whether we can loop over pixels without taking stride into account.
+ bool fast_path = true;
+ fast_path &= (stride1 == w * bpp1);
+ fast_path &= (stride2 == w * bpp2);
+ fast_path &= (strideout == w * bppout);
+
+ int limit = w * h;
+
+ guint32 *const in1_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(in1));
+ guint32 *const in2_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(in2));
+ guint32 *const out_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(out));
+
+ // NOTE
+ // OpenMP probably doesn't help much here.
+ // It would be better to render more than 1 tile at a time.
+ #if HAVE_OPENMP
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int numOfThreads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
+ if (numOfThreads){} // inform compiler we are using it.
+ #endif
+
+ // The number of code paths here is evil.
+ if (bpp1 == 4) {
+ if (bpp2 == 4) {
+ if (fast_path) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ *(out_data + i) = blend(*(in1_data + i), *(in2_data + i));
+ }
+ } else {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint32 *in1_p = in1_data + i * stride1/4;
+ guint32 *in2_p = in2_data + i * stride2/4;
+ guint32 *out_p = out_data + i * strideout/4;
+ for (int j = 0; j < w; ++j) {
+ *out_p = blend(*in1_p, *in2_p);
+ ++in1_p; ++in2_p; ++out_p;
+ }
+ }
+ }
+ } else {
+ // bpp2 == 1
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint32 *in1_p = in1_data + i * stride1/4;
+ guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i * stride2;
+ guint32 *out_p = out_data + i * strideout/4;
+ for (int j = 0; j < w; ++j) {
+ guint32 in2_px = *in2_p;
+ in2_px <<= 24;
+ *out_p = blend(*in1_p, in2_px);
+ ++in1_p; ++in2_p; ++out_p;
+ }
+ }
+ }
+ } else {
+ if (bpp2 == 4) {
+ // bpp1 == 1
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i * stride1;
+ guint32 *in2_p = in2_data + i * stride2/4;
+ guint32 *out_p = out_data + i * strideout/4;
+ for (int j = 0; j < w; ++j) {
+ guint32 in1_px = *in1_p;
+ in1_px <<= 24;
+ *out_p = blend(in1_px, *in2_p);
+ ++in1_p; ++in2_p; ++out_p;
+ }
+ }
+ } else {
+ // bpp1 == 1 && bpp2 == 1
+ if (fast_path) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i;
+ guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i;
+ guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i;
+ guint32 in1_px = *in1_p; in1_px <<= 24;
+ guint32 in2_px = *in2_p; in2_px <<= 24;
+ guint32 out_px = blend(in1_px, in2_px);
+ *out_p = out_px >> 24;
+ }
+ } else {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i * stride1;
+ guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i * stride2;
+ guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
+ for (int j = 0; j < w; ++j) {
+ guint32 in1_px = *in1_p; in1_px <<= 24;
+ guint32 in2_px = *in2_p; in2_px <<= 24;
+ guint32 out_px = blend(in1_px, in2_px);
+ *out_p = out_px >> 24;
+ ++in1_p; ++in2_p; ++out_p;
+ }
+ }
+ }
+ }
+ }
+
+ cairo_surface_mark_dirty(out);
+}
+
+template <typename Filter>
+void ink_cairo_surface_filter(cairo_surface_t *in, cairo_surface_t *out, Filter filter)
+{
+ cairo_surface_flush(in);
+
+ // ASSUMPTIONS
+ // 1. Cairo ARGB32 surface strides are always divisible by 4
+ // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
+ // 3. Surfaces have the same dimensions
+ // 4. Output surface is A8 if input is A8
+
+ int w = cairo_image_surface_get_width(in);
+ int h = cairo_image_surface_get_height(in);
+ int stridein = cairo_image_surface_get_stride(in);
+ int strideout = cairo_image_surface_get_stride(out);
+ int bppin = cairo_image_surface_get_format(in) == CAIRO_FORMAT_A8 ? 1 : 4;
+ int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
+ int limit = w * h;
+
+ // Check whether we can loop over pixels without taking stride into account.
+ bool fast_path = true;
+ fast_path &= (stridein == w * bppin);
+ fast_path &= (strideout == w * bppout);
+
+ guint32 *const in_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(in));
+ guint32 *const out_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(out));
+
+ #if HAVE_OPENMP
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int numOfThreads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
+ if (numOfThreads){} // inform compiler we are using it.
+ #endif
+
+ // this is provided just in case, to avoid problems with strict aliasing rules
+ if (in == out) {
+ if (bppin == 4) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ *(in_data + i) = filter(*(in_data + i));
+ }
+ } else {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ guint8 *in_p = reinterpret_cast<guint8*>(in_data) + i;
+ guint32 in_px = *in_p; in_px <<= 24;
+ guint32 out_px = filter(in_px);
+ *in_p = out_px >> 24;
+ }
+ }
+ cairo_surface_mark_dirty(out);
+ return;
+ }
+
+ if (bppin == 4) {
+ if (bppout == 4) {
+ // bppin == 4, bppout == 4
+ if (fast_path) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ *(out_data + i) = filter(*(in_data + i));
+ }
+ } else {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint32 *in_p = in_data + i * stridein/4;
+ guint32 *out_p = out_data + i * strideout/4;
+ for (int j = 0; j < w; ++j) {
+ *out_p = filter(*in_p);
+ ++in_p; ++out_p;
+ }
+ }
+ }
+ } else {
+ // bppin == 4, bppout == 1
+ // we use this path with COLORMATRIX_LUMINANCETOALPHA
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint32 *in_p = in_data + i * stridein/4;
+ guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
+ for (int j = 0; j < w; ++j) {
+ guint32 out_px = filter(*in_p);
+ *out_p = out_px >> 24;
+ ++in_p; ++out_p;
+ }
+ }
+ }
+ } else {
+ // bppin == 1, bppout == 1
+ // Note: there is no path for bppin == 1, bppout == 4 because it is useless
+ if (fast_path) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < limit; ++i) {
+ guint8 *in_p = reinterpret_cast<guint8*>(in_data) + i;
+ guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i;
+ guint32 in_px = *in_p; in_px <<= 24;
+ guint32 out_px = filter(in_px);
+ *out_p = out_px >> 24;
+ }
+ } else {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = 0; i < h; ++i) {
+ guint8 *in_p = reinterpret_cast<guint8*>(in_data) + i * stridein;
+ guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
+ for (int j = 0; j < w; ++j) {
+ guint32 in_px = *in_p; in_px <<= 24;
+ guint32 out_px = filter(in_px);
+ *out_p = out_px >> 24;
+ ++in_p; ++out_p;
+ }
+ }
+ }
+ }
+ cairo_surface_mark_dirty(out);
+}
+
+
+/**
+ * Synthesize surface pixels based on their position.
+ * This template accepts a functor that gets called with the x and y coordinates of the pixels,
+ * given as integers.
+ * @param out Output surface
+ * @param out_area The region of the output surface that should be synthesized
+ * @param synth Synthesis functor
+ */
+template <typename Synth>
+void ink_cairo_surface_synthesize(cairo_surface_t *out, cairo_rectangle_t const &out_area, Synth synth)
+{
+ // ASSUMPTIONS
+ // 1. Cairo ARGB32 surface strides are always divisible by 4
+ // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
+
+ int w = out_area.width;
+ int h = out_area.height;
+ int strideout = cairo_image_surface_get_stride(out);
+ int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
+ // NOTE: fast path is not used, because we would need 2 divisions to get pixel indices
+
+ unsigned char *out_data = cairo_image_surface_get_data(out);
+
+ #if HAVE_OPENMP
+ int limit = w * h;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int numOfThreads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
+ if (numOfThreads){} // inform compiler we are using it.
+ #endif
+
+ if (bppout == 4) {
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = out_area.y; i < h; ++i) {
+ guint32 *out_p = reinterpret_cast<guint32*>(out_data + i * strideout);
+ for (int j = out_area.x; j < w; ++j) {
+ *out_p = synth(j, i);
+ ++out_p;
+ }
+ }
+ } else {
+ // bppout == 1
+ #if HAVE_OPENMP
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif
+ for (int i = out_area.y; i < h; ++i) {
+ guint8 *out_p = out_data + i * strideout;
+ for (int j = out_area.x; j < w; ++j) {
+ guint32 out_px = synth(j, i);
+ *out_p = out_px >> 24;
+ ++out_p;
+ }
+ }
+ }
+ cairo_surface_mark_dirty(out);
+}
+
+template <typename Synth>
+void ink_cairo_surface_synthesize(cairo_surface_t *out, Synth synth)
+{
+ int w = cairo_image_surface_get_width(out);
+ int h = cairo_image_surface_get_height(out);
+
+ cairo_rectangle_t area;
+ area.x = 0;
+ area.y = 0;
+ area.width = w;
+ area.height = h;
+
+ ink_cairo_surface_synthesize(out, area, synth);
+}
+
+struct SurfaceSynth {
+ SurfaceSynth(cairo_surface_t *surface)
+ : _px(cairo_image_surface_get_data(surface))
+ , _w(cairo_image_surface_get_width(surface))
+ , _h(cairo_image_surface_get_height(surface))
+ , _stride(cairo_image_surface_get_stride(surface))
+ , _alpha(cairo_surface_get_content(surface) == CAIRO_CONTENT_ALPHA)
+ {
+ cairo_surface_flush(surface);
+ }
+
+ guint32 pixelAt(int x, int y) const {
+ if (_alpha) {
+ unsigned char *px = _px + y*_stride + x;
+ return *px << 24;
+ } else {
+ unsigned char *px = _px + y*_stride + x*4;
+ return *reinterpret_cast<guint32*>(px);
+ }
+ }
+ guint32 alphaAt(int x, int y) const {
+ if (_alpha) {
+ unsigned char *px = _px + y*_stride + x;
+ return *px;
+ } else {
+ unsigned char *px = _px + y*_stride + x*4;
+ guint32 p = *reinterpret_cast<guint32*>(px);
+ return (p & 0xff000000) >> 24;
+ }
+ }
+
+ // retrieve a pixel value with bilinear interpolation
+ guint32 pixelAt(double x, double y) const {
+ if (_alpha) {
+ return alphaAt(x, y) << 24;
+ }
+
+ double xf = floor(x), yf = floor(y);
+ int xi = xf, yi = yf;
+ guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
+ guint32 p00, p01, p10, p11;
+
+ unsigned char *pxi = _px + yi*_stride + xi*4;
+ guint32 *pxu = reinterpret_cast<guint32*>(pxi);
+ guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
+ p00 = *pxu; p10 = *(pxu + 1);
+ p01 = *pxl; p11 = *(pxl + 1);
+
+ guint32 comp[4];
+
+ for (unsigned i = 0; i < 4; ++i) {
+ guint32 shift = i*8;
+ guint32 mask = 0xff << shift;
+ guint32 c00 = (p00 & mask) >> shift;
+ guint32 c10 = (p10 & mask) >> shift;
+ guint32 c01 = (p01 & mask) >> shift;
+ guint32 c11 = (p11 & mask) >> shift;
+
+ guint32 iu = (255-xif) * c00 + xif * c10;
+ guint32 il = (255-xif) * c01 + xif * c11;
+ comp[i] = (255-yif) * iu + yif * il;
+ comp[i] = (comp[i] + (255*255/2)) / (255*255);
+ }
+
+ guint32 result = comp[0] | (comp[1] << 8) | (comp[2] << 16) | (comp[3] << 24);
+ return result;
+ }
+
+ // retrieve an alpha value with bilinear interpolation
+ guint32 alphaAt(double x, double y) const {
+ double xf = floor(x), yf = floor(y);
+ int xi = xf, yi = yf;
+ guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
+ guint32 p00, p01, p10, p11;
+ if (_alpha) {
+ unsigned char *pxu = _px + yi*_stride + xi;
+ unsigned char *pxl = pxu + _stride;
+ p00 = *pxu; p10 = *(pxu + 1);
+ p01 = *pxl; p11 = *(pxl + 1);
+ } else {
+ unsigned char *pxi = _px + yi*_stride + xi*4;
+ guint32 *pxu = reinterpret_cast<guint32*>(pxi);
+ guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
+ p00 = (*pxu & 0xff000000) >> 24; p10 = (*(pxu + 1) & 0xff000000) >> 24;
+ p01 = (*pxl & 0xff000000) >> 24; p11 = (*(pxl + 1) & 0xff000000) >> 24;
+ }
+ guint32 iu = (255-xif) * p00 + xif * p10;
+ guint32 il = (255-xif) * p01 + xif * p11;
+ guint32 result = (255-yif) * iu + yif * il;
+ result = (result + (255*255/2)) / (255*255);
+ return result;
+ }
+
+ // compute surface normal at given coordinates using 3x3 Sobel gradient filter
+ NR::Fvector surfaceNormalAt(int x, int y, double scale) const {
+ // Below there are some multiplies by zero. They will be optimized out.
+ // Do not remove them, because they improve readability.
+ // NOTE: fetching using alphaAt is slightly lazy.
+ NR::Fvector normal;
+ double fx = -scale/255.0, fy = -scale/255.0;
+ normal[Z_3D] = 1.0;
+ if (G_UNLIKELY(x == 0)) {
+ // leftmost column
+ if (G_UNLIKELY(y == 0)) {
+ // upper left corner
+ fx *= (2.0/3.0);
+ fy *= (2.0/3.0);
+ double p00 = alphaAt(x,y), p10 = alphaAt(x+1, y),
+ p01 = alphaAt(x,y+1), p11 = alphaAt(x+1, y+1);
+ normal[X_3D] =
+ -2.0 * p00 +2.0 * p10
+ -1.0 * p01 +1.0 * p11;
+ normal[Y_3D] =
+ -2.0 * p00 -1.0 * p10
+ +2.0 * p01 +1.0 * p11;
+ } else if (G_UNLIKELY(y == (_h - 1))) {
+ // lower left corner
+ fx *= (2.0/3.0);
+ fy *= (2.0/3.0);
+ double p00 = alphaAt(x,y-1), p10 = alphaAt(x+1, y-1),
+ p01 = alphaAt(x,y ), p11 = alphaAt(x+1, y);
+ normal[X_3D] =
+ -1.0 * p00 +1.0 * p10
+ -2.0 * p01 +2.0 * p11;
+ normal[Y_3D] =
+ -2.0 * p00 -1.0 * p10
+ +2.0 * p01 +1.0 * p11;
+ } else {
+ // leftmost column
+ fx *= (1.0/2.0);
+ fy *= (1.0/3.0);
+ double p00 = alphaAt(x, y-1), p10 = alphaAt(x+1, y-1),
+ p01 = alphaAt(x, y ), p11 = alphaAt(x+1, y ),
+ p02 = alphaAt(x, y+1), p12 = alphaAt(x+1, y+1);
+ normal[X_3D] =
+ -1.0 * p00 +1.0 * p10
+ -2.0 * p01 +2.0 * p11
+ -1.0 * p02 +1.0 * p12;
+ normal[Y_3D] =
+ -2.0 * p00 -1.0 * p10
+ +0.0 * p01 +0.0 * p11 // this will be optimized out
+ +2.0 * p02 +1.0 * p12;
+ }
+ } else if (G_UNLIKELY(x == (_w - 1))) {
+ // rightmost column
+ if (G_UNLIKELY(y == 0)) {
+ // top right corner
+ fx *= (2.0/3.0);
+ fy *= (2.0/3.0);
+ double p00 = alphaAt(x-1,y), p10 = alphaAt(x, y),
+ p01 = alphaAt(x-1,y+1), p11 = alphaAt(x, y+1);
+ normal[X_3D] =
+ -2.0 * p00 +2.0 * p10
+ -1.0 * p01 +1.0 * p11;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10
+ +1.0 * p01 +2.0 * p11;
+ } else if (G_UNLIKELY(y == (_h - 1))) {
+ // bottom right corner
+ fx *= (2.0/3.0);
+ fy *= (2.0/3.0);
+ double p00 = alphaAt(x-1,y-1), p10 = alphaAt(x, y-1),
+ p01 = alphaAt(x-1,y ), p11 = alphaAt(x, y);
+ normal[X_3D] =
+ -1.0 * p00 +1.0 * p10
+ -2.0 * p01 +2.0 * p11;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10
+ +1.0 * p01 +2.0 * p11;
+ } else {
+ // rightmost column
+ fx *= (1.0/2.0);
+ fy *= (1.0/3.0);
+ double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1),
+ p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ),
+ p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1);
+ normal[X_3D] =
+ -1.0 * p00 +1.0 * p10
+ -2.0 * p01 +2.0 * p11
+ -1.0 * p02 +1.0 * p12;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10
+ +0.0 * p01 +0.0 * p11
+ +1.0 * p02 +2.0 * p12;
+ }
+ } else {
+ // interior
+ if (G_UNLIKELY(y == 0)) {
+ // top row
+ fx *= (1.0/3.0);
+ fy *= (1.0/2.0);
+ double p00 = alphaAt(x-1, y ), p10 = alphaAt(x, y ), p20 = alphaAt(x+1, y ),
+ p01 = alphaAt(x-1, y+1), p11 = alphaAt(x, y+1), p21 = alphaAt(x+1, y+1);
+ normal[X_3D] =
+ -2.0 * p00 +0.0 * p10 +2.0 * p20
+ -1.0 * p01 +0.0 * p11 +1.0 * p21;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10 -1.0 * p20
+ +1.0 * p01 +2.0 * p11 +1.0 * p21;
+ } else if (G_UNLIKELY(y == (_h - 1))) {
+ // bottom row
+ fx *= (1.0/3.0);
+ fy *= (1.0/2.0);
+ double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
+ p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ), p21 = alphaAt(x+1, y );
+ normal[X_3D] =
+ -1.0 * p00 +0.0 * p10 +1.0 * p20
+ -2.0 * p01 +0.0 * p11 +2.0 * p21;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10 -1.0 * p20
+ +1.0 * p01 +2.0 * p11 +1.0 * p21;
+ } else {
+ // interior pixels
+ // note: p11 is actually unused, so we don't fetch its value
+ fx *= (1.0/4.0);
+ fy *= (1.0/4.0);
+ double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
+ p01 = alphaAt(x-1, y ), p11 = 0.0, p21 = alphaAt(x+1, y ),
+ p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1), p22 = alphaAt(x+1, y+1);
+ normal[X_3D] =
+ -1.0 * p00 +0.0 * p10 +1.0 * p20
+ -2.0 * p01 +0.0 * p11 +2.0 * p21
+ -1.0 * p02 +0.0 * p12 +1.0 * p22;
+ normal[Y_3D] =
+ -1.0 * p00 -2.0 * p10 -1.0 * p20
+ +0.0 * p01 +0.0 * p11 +0.0 * p21
+ +1.0 * p02 +2.0 * p12 +1.0 * p22;
+ }
+ }
+ normal[X_3D] *= fx;
+ normal[Y_3D] *= fy;
+ NR::normalize_vector(normal);
+ return normal;
+ }
+
+ unsigned char *_px;
+ int _w, _h, _stride;
+ bool _alpha;
+};
+
+/*
+// simple pixel accessor for image surface that handles different edge wrapping modes
+class PixelAccessor {
+public:
+ typedef PixelAccessor self;
+ enum EdgeMode {
+ EDGE_PAD,
+ EDGE_WRAP,
+ EDGE_ZERO
+ };
+
+ PixelAccessor(cairo_surface_t *s, EdgeMode e)
+ : _surface(s)
+ , _px(cairo_image_surface_get_data(s))
+ , _x(0), _y(0)
+ , _w(cairo_image_surface_get_width(s))
+ , _h(cairo_image_surface_get_height(s))
+ , _stride(cairo_image_surface_get_stride(s))
+ , _edge_mode(e)
+ , _alpha(cairo_image_surface_get_format(s) == CAIRO_FORMAT_A8)
+ {}
+
+ guint32 pixelAt(int x, int y) {
+ // This is a lot of ifs for a single pixel access. However, branch prediction
+ // should help us a lot, as the result of ifs is always the same for a single image.
+ int real_x = x, real_y = y;
+ switch (_edge_mode) {
+ case EDGE_PAD:
+ real_x = CLAMP(x, 0, _w-1);
+ real_y = CLAMP(y, 0, _h-1);
+ break;
+ case EDGE_WRAP:
+ real_x %= _w;
+ real_y %= _h;
+ break;
+ case EDGE_ZERO:
+ default:
+ if (x < 0 || x >= _w || y < 0 || y >= _h)
+ return 0;
+ break;
+ }
+ if (_alpha) {
+ return *(_px + real_y*_stride + real_x) << 24;
+ } else {
+ guint32 *px = reinterpret_cast<guint32*>(_px +real_y*_stride + real_x*4);
+ return *px;
+ }
+ }
+private:
+ cairo_surface_t *_surface;
+ guint8 *_px;
+ int _x, _y, _w, _h, _stride;
+ EdgeMode _edge_mode;
+ bool _alpha;
+};*/
+
+// Some helpers for pixel manipulation
+G_GNUC_CONST inline gint32
+pxclamp(gint32 v, gint32 low, gint32 high) {
+ // NOTE: it is possible to write a "branchless" clamping operation.
+ // However, it will be slower than this function, because the code below
+ // is compiled to conditional moves.
+ if (v < low) return low;
+ if (v > high) return high;
+ return v;
+}
+
+#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/display/cairo-utils.cpp b/src/display/cairo-utils.cpp
new file mode 100644
index 0000000..f616af7
--- /dev/null
+++ b/src/display/cairo-utils.cpp
@@ -0,0 +1,1746 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Helper functions to use cairo with inkscape
+ *
+ * Copyright (C) 2007 bulia byak
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include "display/cairo-utils.h"
+
+#include <stdexcept>
+
+#include <glib/gstdio.h>
+#include <glibmm/fileutils.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <2geom/pathvector.h>
+#include <2geom/curves.h>
+#include <2geom/affine.h>
+#include <2geom/point.h>
+#include <2geom/path.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-to-bezier.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/operators.hpp>
+#include <boost/optional/optional.hpp>
+
+#include "color.h"
+#include "cairo-templates.h"
+#include "document.h"
+#include "preferences.h"
+#include "util/units.h"
+#include "helper/pixbuf-ops.h"
+
+
+/**
+ * Key for cairo_surface_t to keep track of current color interpolation value
+ * Only the address of the structure is used, it is never initialized. See:
+ * http://www.cairographics.org/manual/cairo-Types.html#cairo-user-data-key-t
+ */
+cairo_user_data_key_t ink_color_interpolation_key;
+cairo_user_data_key_t ink_pixbuf_key;
+
+namespace Inkscape {
+
+CairoGroup::CairoGroup(cairo_t *_ct) : ct(_ct), pushed(false) {}
+CairoGroup::~CairoGroup() {
+ if (pushed) {
+ cairo_pattern_t *p = cairo_pop_group(ct);
+ cairo_pattern_destroy(p);
+ }
+}
+void CairoGroup::push() {
+ cairo_push_group(ct);
+ pushed = true;
+}
+void CairoGroup::push_with_content(cairo_content_t content) {
+ cairo_push_group_with_content(ct, content);
+ pushed = true;
+}
+cairo_pattern_t *CairoGroup::pop() {
+ if (pushed) {
+ cairo_pattern_t *ret = cairo_pop_group(ct);
+ pushed = false;
+ return ret;
+ } else {
+ throw std::logic_error("Cairo group popped without pushing it first");
+ }
+}
+Cairo::RefPtr<Cairo::Pattern> CairoGroup::popmm() {
+ if (pushed) {
+ cairo_pattern_t *ret = cairo_pop_group(ct);
+ Cairo::RefPtr<Cairo::Pattern> retmm(new Cairo::Pattern(ret, true));
+ pushed = false;
+ return retmm;
+ } else {
+ throw std::logic_error("Cairo group popped without pushing it first");
+ }
+}
+void CairoGroup::pop_to_source() {
+ if (pushed) {
+ cairo_pop_group_to_source(ct);
+ pushed = false;
+ }
+}
+
+CairoContext::CairoContext(cairo_t *obj, bool ref)
+ : Cairo::Context(obj, ref)
+{}
+
+void CairoContext::transform(Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ cm.xx = m[0];
+ cm.xy = m[2];
+ cm.x0 = m[4];
+ cm.yx = m[1];
+ cm.yy = m[3];
+ cm.y0 = m[5];
+ cairo_transform(cobj(), &cm);
+}
+
+void CairoContext::set_source_rgba32(guint32 color)
+{
+ double red = SP_RGBA32_R_F(color);
+ double gre = SP_RGBA32_G_F(color);
+ double blu = SP_RGBA32_B_F(color);
+ double alp = SP_RGBA32_A_F(color);
+ cairo_set_source_rgba(cobj(), red, gre, blu, alp);
+}
+
+void CairoContext::append_path(Geom::PathVector const &pv)
+{
+ feed_pathvector_to_cairo(cobj(), pv);
+}
+
+Cairo::RefPtr<CairoContext> CairoContext::create(Cairo::RefPtr<Cairo::Surface> const &target)
+{
+ cairo_t *ct = cairo_create(target->cobj());
+ Cairo::RefPtr<CairoContext> ret(new CairoContext(ct, true));
+ return ret;
+}
+
+
+/* The class below implement the following hack:
+ *
+ * The pixels formats of Cairo and GdkPixbuf are different.
+ * GdkPixbuf accesses pixels as bytes, alpha is not premultiplied,
+ * and successive bytes of a single pixel contain R, G, B and A components.
+ * Cairo accesses pixels as 32-bit ints, alpha is premultiplied,
+ * and each int contains as 0xAARRGGBB, accessed with bitwise operations.
+ *
+ * In other words, on a little endian system, a GdkPixbuf will contain:
+ * char *data = "rgbargbargba...."
+ * int *data = { 0xAABBGGRR, 0xAABBGGRR, 0xAABBGGRR, ... }
+ * while a Cairo image surface will contain:
+ * char *data = "bgrabgrabgra...."
+ * int *data = { 0xAARRGGBB, 0xAARRGGBB, 0xAARRGGBB, ... }
+ *
+ * It is possible to convert between these two formats (almost) losslessly.
+ * Some color information from partially transparent regions of the image
+ * is lost, but the result when displaying this image will remain the same.
+ *
+ * The class allows interoperation between GdkPixbuf
+ * and Cairo surfaces without creating a copy of the image.
+ * This is implemented by creating a GdkPixbuf and a Cairo image surface
+ * which share their data. Depending on what is needed at a given time,
+ * the pixels are converted in place to the Cairo or the GdkPixbuf format.
+ */
+
+/** Create a pixbuf from a Cairo surface.
+ * The constructor takes ownership of the passed surface,
+ * so it should not be destroyed. */
+Pixbuf::Pixbuf(cairo_surface_t *s)
+ : _pixbuf(gdk_pixbuf_new_from_data(
+ cairo_image_surface_get_data(s), GDK_COLORSPACE_RGB, TRUE, 8,
+ cairo_image_surface_get_width(s), cairo_image_surface_get_height(s),
+ cairo_image_surface_get_stride(s), nullptr, nullptr))
+ , _surface(s)
+ , _mod_time(0)
+ , _pixel_format(PF_CAIRO)
+ , _cairo_store(true)
+{}
+
+/** Create a pixbuf from a GdkPixbuf.
+ * The constructor takes ownership of the passed GdkPixbuf reference,
+ * so it should not be unrefed. */
+Pixbuf::Pixbuf(GdkPixbuf *pb)
+ : _pixbuf(pb)
+ , _surface(nullptr)
+ , _mod_time(0)
+ , _pixel_format(PF_GDK)
+ , _cairo_store(false)
+{
+ _forceAlpha();
+ _surface = cairo_image_surface_create_for_data(
+ gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf));
+}
+
+Pixbuf::Pixbuf(Inkscape::Pixbuf const &other)
+ : _pixbuf(gdk_pixbuf_copy(other._pixbuf))
+ , _surface(cairo_image_surface_create_for_data(
+ gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf)))
+ , _mod_time(other._mod_time)
+ , _path(other._path)
+ , _pixel_format(other._pixel_format)
+ , _cairo_store(false)
+{}
+
+Pixbuf::~Pixbuf()
+{
+ if (_cairo_store) {
+ g_object_unref(_pixbuf);
+ cairo_surface_destroy(_surface);
+ } else {
+ cairo_surface_destroy(_surface);
+ g_object_unref(_pixbuf);
+ }
+}
+
+#if !GDK_PIXBUF_CHECK_VERSION(2, 41, 0)
+/**
+ * Incremental file read introduced to workaround
+ * https://gitlab.gnome.org/GNOME/gdk-pixbuf/issues/70
+ */
+static bool _workaround_issue_70__gdk_pixbuf_loader_write( //
+ GdkPixbufLoader *loader, guchar *decoded, gsize decoded_len, GError **error)
+{
+ bool success = true;
+ gsize bytes_left = decoded_len;
+ gsize secret_limit = 0xffff;
+ guchar *decoded_head = decoded;
+ while (bytes_left && success) {
+ gsize bytes = (bytes_left > secret_limit) ? secret_limit : bytes_left;
+ success = gdk_pixbuf_loader_write(loader, decoded_head, bytes, error);
+ decoded_head += bytes;
+ bytes_left -= bytes;
+ }
+
+ return success;
+}
+#define gdk_pixbuf_loader_write _workaround_issue_70__gdk_pixbuf_loader_write
+#endif
+
+Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
+{
+ Pixbuf *pixbuf = nullptr;
+
+ bool data_is_image = false;
+ bool data_is_svg = false;
+ bool data_is_base64 = false;
+
+ gchar const *data = uri_data;
+
+ while (*data) {
+ if (strncmp(data,"base64",6) == 0) {
+ /* base64-encoding */
+ data_is_base64 = true;
+ data_is_image = true; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
+ data += 6;
+ }
+ else if (strncmp(data,"image/png",9) == 0) {
+ /* PNG image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/jpg",9) == 0) {
+ /* JPEG image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/jpeg",10) == 0) {
+ /* JPEG image */
+ data_is_image = true;
+ data += 10;
+ }
+ else if (strncmp(data,"image/jp2",9) == 0) {
+ /* JPEG2000 image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/svg+xml",13) == 0) {
+ /* JPEG2000 image */
+ data_is_svg = true;
+ data_is_image = true;
+ data += 13;
+ }
+ else { /* unrecognized option; skip it */
+ while (*data) {
+ if (((*data) == ';') || ((*data) == ',')) {
+ break;
+ }
+ data++;
+ }
+ }
+ if ((*data) == ';') {
+ data++;
+ continue;
+ }
+ if ((*data) == ',') {
+ data++;
+ break;
+ }
+ }
+
+ if ((*data) && data_is_image && !data_is_svg && data_is_base64) {
+ GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+
+ if (!loader) return nullptr;
+
+ gsize decoded_len = 0;
+ guchar *decoded = g_base64_decode(data, &decoded_len);
+
+ if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, nullptr)) {
+ gdk_pixbuf_loader_close(loader, nullptr);
+ GdkPixbuf *buf = gdk_pixbuf_loader_get_pixbuf(loader);
+ if (buf) {
+ g_object_ref(buf);
+ pixbuf = new Pixbuf(buf);
+
+ GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
+ gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
+ pixbuf->_setMimeData(decoded, decoded_len, fmt_name);
+ g_free(fmt_name);
+ } else {
+ g_free(decoded);
+ }
+ } else {
+ g_free(decoded);
+ }
+ g_object_unref(loader);
+ }
+
+ if ((*data) && data_is_image && data_is_svg && data_is_base64) {
+ gsize decoded_len = 0;
+ guchar *decoded = g_base64_decode(data, &decoded_len);
+ SPDocument *svgDoc = SPDocument::createNewDocFromMem (reinterpret_cast<gchar const *>(decoded), decoded_len, false);
+ // Check the document loaded properly
+ if (svgDoc == nullptr) {
+ return nullptr;
+ }
+ if (svgDoc->getRoot() == nullptr)
+ {
+ svgDoc->doUnref();
+ return nullptr;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
+ if (svgdpi && svgdpi > 0) {
+ dpi = svgdpi;
+ }
+ std::cout << dpi << "dpi" << std::endl;
+ // Get the size of the document
+ Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
+ Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
+ const double svgWidth_px = svgWidth.value("px");
+ const double svgHeight_px = svgHeight.value("px");
+
+ // Now get the resized values
+ const int scaledSvgWidth = round(svgWidth_px/(96.0/dpi));
+ const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
+
+ GdkPixbuf *buf = sp_generate_internal_bitmap(svgDoc, nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth, scaledSvgHeight, dpi, dpi, (guint32) 0xffffff00, nullptr)->getPixbufRaw();
+ // Tidy up
+ svgDoc->doUnref();
+ if (buf == nullptr) {
+ std::cerr << "Pixbuf::create_from_data: failed to load contents: " << std::endl;
+ g_free(decoded);
+ return nullptr;
+ } else {
+ g_object_ref(buf);
+ pixbuf = new Pixbuf(buf);
+ pixbuf->_setMimeData(decoded, decoded_len, "svg+xml");
+ }
+ }
+
+ return pixbuf;
+}
+
+Pixbuf *Pixbuf::create_from_file(std::string const &fn, double svgdpi)
+{
+ Pixbuf *pb = nullptr;
+ // test correctness of filename
+ if (!g_file_test(fn.c_str(), G_FILE_TEST_EXISTS)) {
+ return nullptr;
+ }
+ GStatBuf stdir;
+ int val = g_stat(fn.c_str(), &stdir);
+ if (val == 0 && stdir.st_mode & S_IFDIR){
+ return nullptr;
+ }
+ // we need to load the entire file into memory,
+ // since we'll store it as MIME data
+ gchar *data = nullptr;
+ gsize len = 0;
+ GError *error = nullptr;
+
+ if (g_file_get_contents(fn.c_str(), &data, &len, &error)) {
+
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ return nullptr;
+ }
+
+ pb = Pixbuf::create_from_buffer(std::move(data), len, svgdpi, fn);
+
+ if (pb) {
+ pb->_mod_time = stdir.st_mtime;
+ }
+ } else {
+ std::cerr << "Pixbuf::create_from_file: failed to get contents: " << fn << std::endl;
+ return nullptr;
+ }
+
+ return pb;
+}
+
+Pixbuf *Pixbuf::create_from_buffer(std::string const &buffer, double svgdpi, std::string const &fn)
+{
+ auto datacopy = (gchar *)g_memdup(buffer.data(), buffer.size());
+ return Pixbuf::create_from_buffer(std::move(datacopy), buffer.size(), svgdpi, fn);
+}
+
+Pixbuf *Pixbuf::create_from_buffer(gchar *&&data, gsize len, double svgdpi, std::string const &fn)
+{
+ Pixbuf *pb = nullptr;
+ GError *error = nullptr;
+ {
+ GdkPixbuf *buf = nullptr;
+ GdkPixbufLoader *loader = nullptr;
+ std::string::size_type idx;
+ idx = fn.rfind('.');
+ bool is_svg = false;
+ if(idx != std::string::npos)
+ {
+ if (boost::iequals(fn.substr(idx+1).c_str(), "svg")) {
+
+ SPDocument *svgDoc = SPDocument::createNewDocFromMem (data, len, true);
+
+ // Check the document loaded properly
+ if (svgDoc == nullptr) {
+ return nullptr;
+ }
+ if (svgDoc->getRoot() == nullptr)
+ {
+ svgDoc->doUnref();
+ return nullptr;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
+ if (svgdpi && svgdpi > 0) {
+ dpi = svgdpi;
+ }
+
+ // Get the size of the document
+ Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
+ Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
+ const double svgWidth_px = svgWidth.value("px");
+ const double svgHeight_px = svgHeight.value("px");
+
+ // Now get the resized values
+ const int scaledSvgWidth = round(svgWidth_px/(96.0/dpi));
+ const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
+
+ buf = sp_generate_internal_bitmap(svgDoc, nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth, scaledSvgHeight, dpi, dpi, (guint32) 0xffffff00, nullptr)->getPixbufRaw();
+
+ // Tidy up
+ svgDoc->doUnref();
+ if (buf == nullptr) {
+ return nullptr;
+ }
+ is_svg = true;
+ }
+ }
+ if (!is_svg) {
+ loader = gdk_pixbuf_loader_new();
+ gdk_pixbuf_loader_write(loader, (guchar *) data, len, &error);
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ g_free(data);
+ g_object_unref(loader);
+ return nullptr;
+ }
+
+ gdk_pixbuf_loader_close(loader, &error);
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ g_free(data);
+ g_object_unref(loader);
+ return nullptr;
+ }
+
+ buf = gdk_pixbuf_loader_get_pixbuf(loader);
+ }
+
+ if (buf) {
+ g_object_ref(buf);
+ pb = new Pixbuf(buf);
+ pb->_path = fn;
+ if (!is_svg) {
+ GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
+ gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
+ pb->_setMimeData((guchar *) data, len, fmt_name);
+ g_free(fmt_name);
+ g_object_unref(loader);
+ } else {
+ pb->_setMimeData((guchar *) data, len, "svg");
+ }
+ } else {
+ std::cerr << "Pixbuf::create_from_file: failed to load contents: " << fn << std::endl;
+ g_free(data);
+ }
+
+ // TODO: we could also read DPI, ICC profile, gamma correction, and other information
+ // from the file. This can be done by using format-specific libraries e.g. libpng.
+ }
+
+ return pb;
+}
+
+/**
+ * Converts the pixbuf to GdkPixbuf pixel format.
+ * The returned pixbuf can be used e.g. in calls to gdk_pixbuf_save().
+ */
+GdkPixbuf *Pixbuf::getPixbufRaw(bool convert_format)
+{
+ if (convert_format) {
+ ensurePixelFormat(PF_GDK);
+ }
+ return _pixbuf;
+}
+
+/**
+ * Converts the pixbuf to Cairo pixel format and returns an image surface
+ * which can be used as a source.
+ *
+ * The returned surface is owned by the GdkPixbuf and should not be freed.
+ * Calling this function causes the pixbuf to be unsuitable for use
+ * with GTK drawing functions until ensurePixelFormat(Pixbuf::PIXEL_FORMAT_PIXBUF) is called.
+ */
+cairo_surface_t *Pixbuf::getSurfaceRaw(bool convert_format)
+{
+ if (convert_format) {
+ ensurePixelFormat(PF_CAIRO);
+ }
+ return _surface;
+}
+
+/* Declaring this function in the header requires including <gdkmm/pixbuf.h>,
+ * which stupidly includes <glibmm.h> which in turn pulls in <glibmm/threads.h>.
+ * However, since glib 2.32, <glibmm/threads.h> has to be included before <glib.h>
+ * when compiling with G_DISABLE_DEPRECATED, as we do in non-release builds.
+ * This necessitates spamming a lot of files with #include <glibmm/threads.h>
+ * at the top.
+ *
+ * Since we don't really use gdkmm, do not define this function for now. */
+
+/*
+Glib::RefPtr<Gdk::Pixbuf> Pixbuf::getPixbuf(bool convert_format = true)
+{
+ g_object_ref(_pixbuf);
+ Glib::RefPtr<Gdk::Pixbuf> p(getPixbuf(convert_format));
+ return p;
+}
+*/
+
+Cairo::RefPtr<Cairo::Surface> Pixbuf::getSurface(bool convert_format)
+{
+ Cairo::RefPtr<Cairo::Surface> p(new Cairo::Surface(getSurfaceRaw(convert_format), false));
+ return p;
+}
+
+/** Retrieves the original compressed data for the surface, if any.
+ * The returned data belongs to the object and should not be freed. */
+guchar const *Pixbuf::getMimeData(gsize &len, std::string &mimetype) const
+{
+ static gchar const *mimetypes[] = {
+ CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, nullptr };
+ static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes));
+
+ guchar const *data = nullptr;
+
+ for (guint i = 0; i < mimetypes_len; ++i) {
+ unsigned long len_long = 0;
+ cairo_surface_get_mime_data(const_cast<cairo_surface_t*>(_surface), mimetypes[i], &data, &len_long);
+ if (data != nullptr) {
+ len = len_long;
+ mimetype = mimetypes[i];
+ break;
+ }
+ }
+
+ return data;
+}
+
+int Pixbuf::width() const {
+ return gdk_pixbuf_get_width(const_cast<GdkPixbuf*>(_pixbuf));
+}
+int Pixbuf::height() const {
+ return gdk_pixbuf_get_height(const_cast<GdkPixbuf*>(_pixbuf));
+}
+int Pixbuf::rowstride() const {
+ return gdk_pixbuf_get_rowstride(const_cast<GdkPixbuf*>(_pixbuf));
+}
+guchar const *Pixbuf::pixels() const {
+ return gdk_pixbuf_get_pixels(const_cast<GdkPixbuf*>(_pixbuf));
+}
+guchar *Pixbuf::pixels() {
+ return gdk_pixbuf_get_pixels(_pixbuf);
+}
+void Pixbuf::markDirty() {
+ cairo_surface_mark_dirty(_surface);
+}
+
+void Pixbuf::_forceAlpha()
+{
+ if (gdk_pixbuf_get_has_alpha(_pixbuf)) return;
+
+ GdkPixbuf *old = _pixbuf;
+ _pixbuf = gdk_pixbuf_add_alpha(old, FALSE, 0, 0, 0);
+ g_object_unref(old);
+}
+
+void Pixbuf::_setMimeData(guchar *data, gsize len, Glib::ustring const &format)
+{
+ gchar const *mimetype = nullptr;
+
+ if (format == "jpeg") {
+ mimetype = CAIRO_MIME_TYPE_JPEG;
+ } else if (format == "jpeg2000") {
+ mimetype = CAIRO_MIME_TYPE_JP2;
+ } else if (format == "png") {
+ mimetype = CAIRO_MIME_TYPE_PNG;
+ }
+
+ if (mimetype != nullptr) {
+ cairo_surface_set_mime_data(_surface, mimetype, data, len, g_free, data);
+ //g_message("Setting Cairo MIME data: %s", mimetype);
+ } else {
+ g_free(data);
+ //g_message("Not setting Cairo MIME data: unknown format %s", name.c_str());
+ }
+}
+
+void Pixbuf::ensurePixelFormat(PixelFormat fmt)
+{
+ if (_pixel_format == PF_GDK) {
+ if (fmt == PF_GDK) {
+ return;
+ }
+ if (fmt == PF_CAIRO) {
+ convert_pixels_pixbuf_to_argb32(
+ gdk_pixbuf_get_pixels(_pixbuf),
+ gdk_pixbuf_get_width(_pixbuf),
+ gdk_pixbuf_get_height(_pixbuf),
+ gdk_pixbuf_get_rowstride(_pixbuf));
+ _pixel_format = fmt;
+ return;
+ }
+ g_assert_not_reached();
+ }
+ if (_pixel_format == PF_CAIRO) {
+ if (fmt == PF_GDK) {
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(_pixbuf),
+ gdk_pixbuf_get_width(_pixbuf),
+ gdk_pixbuf_get_height(_pixbuf),
+ gdk_pixbuf_get_rowstride(_pixbuf));
+ _pixel_format = fmt;
+ return;
+ }
+ if (fmt == PF_CAIRO) {
+ return;
+ }
+ g_assert_not_reached();
+ }
+ g_assert_not_reached();
+}
+
+} // namespace Inkscape
+
+/*
+ * Can be called recursively.
+ * If optimize_stroke == false, the view Rect is not used.
+ */
+static void
+feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const & trans, Geom::Rect view, bool optimize_stroke)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ unsigned order = 0;
+ if (Geom::BezierCurve const* b = dynamic_cast<Geom::BezierCurve const*>(&c)) {
+ order = b->order();
+ }
+
+ // handle the three typical curve cases
+ switch (order) {
+ case 1:
+ {
+ Geom::Point end_tr = c.finalPoint() * trans;
+ if (!optimize_stroke) {
+ cairo_line_to(cr, end_tr[0], end_tr[1]);
+ } else {
+ Geom::Rect swept(c.initialPoint()*trans, end_tr);
+ if (swept.intersects(view)) {
+ cairo_line_to(cr, end_tr[0], end_tr[1]);
+ } else {
+ cairo_move_to(cr, end_tr[0], end_tr[1]);
+ }
+ }
+ }
+ break;
+ case 2:
+ {
+ Geom::QuadraticBezier const *quadratic_bezier = static_cast<Geom::QuadraticBezier const*>(&c);
+ std::vector<Geom::Point> points = quadratic_bezier->controlPoints();
+ points[0] *= trans;
+ points[1] *= trans;
+ points[2] *= trans;
+ // degree-elevate to cubic Bezier, since Cairo doesn't do quadratic Beziers
+ Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
+ Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
+ if (!optimize_stroke) {
+ cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
+ } else {
+ Geom::Rect swept(points[0], points[2]);
+ swept.expandTo(points[1]);
+ if (swept.intersects(view)) {
+ cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
+ } else {
+ cairo_move_to(cr, points[2][X], points[2][Y]);
+ }
+ }
+ }
+ break;
+ case 3:
+ {
+ Geom::CubicBezier const *cubic_bezier = static_cast<Geom::CubicBezier const*>(&c);
+ std::vector<Geom::Point> points = cubic_bezier->controlPoints();
+ //points[0] *= trans; // don't do this one here for fun: it is only needed for optimized strokes
+ points[1] *= trans;
+ points[2] *= trans;
+ points[3] *= trans;
+ if (!optimize_stroke) {
+ cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
+ } else {
+ points[0] *= trans; // didn't transform this point yet
+ Geom::Rect swept(points[0], points[3]);
+ swept.expandTo(points[1]);
+ swept.expandTo(points[2]);
+ if (swept.intersects(view)) {
+ cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
+ } else {
+ cairo_move_to(cr, points[3][X], points[3][Y]);
+ }
+ }
+ }
+ break;
+ default:
+ {
+ if (Geom::EllipticalArc const *a = dynamic_cast<Geom::EllipticalArc const*>(&c)) {
+ if (a->isChord()) {
+ Geom::Point endPoint(a->finalPoint());
+ cairo_line_to(cr, endPoint[0], endPoint[1]);
+ } else {
+ Geom::Affine xform = a->unitCircleTransform() * trans;
+ Geom::Point ang(a->initialAngle().radians(), a->finalAngle().radians());
+ // Apply the transformation to the current context
+ cairo_matrix_t cm;
+ cm.xx = xform[0];
+ cm.xy = xform[2];
+ cm.x0 = xform[4];
+ cm.yx = xform[1];
+ cm.yy = xform[3];
+ cm.y0 = xform[5];
+
+ cairo_save(cr);
+ cairo_transform(cr, &cm);
+
+ // Draw the circle
+ if (a->sweep()) {
+ cairo_arc(cr, 0, 0, 1, ang[0], ang[1]);
+ } else {
+ cairo_arc_negative(cr, 0, 0, 1, ang[0], ang[1]);
+ }
+ // Revert the current context
+ cairo_restore(cr);
+ }
+ } else {
+ // handles sbasis as well as all other curve types
+ // this is very slow
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ // recurse to convert the new path resulting from the sbasis to svgd
+ for (const auto & iter : sbasis_path) {
+ feed_curve_to_cairo(cr, iter, trans, view, optimize_stroke);
+ }
+ }
+ }
+ break;
+ }
+}
+
+
+/** Feeds path-creating calls to the cairo context translating them from the Path */
+static void
+feed_path_to_cairo (cairo_t *ct, Geom::Path const &path)
+{
+ if (path.empty())
+ return;
+
+ cairo_move_to(ct, path.initialPoint()[0], path.initialPoint()[1] );
+
+ for (Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
+ feed_curve_to_cairo(ct, *cit, Geom::identity(), Geom::Rect(), false); // optimize_stroke is false, so the view rect is not used
+ }
+
+ if (path.closed()) {
+ cairo_close_path(ct);
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the Path, with the given transform and shift */
+static void
+feed_path_to_cairo (cairo_t *ct, Geom::Path const &path, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
+{
+ if (!area)
+ return;
+ if (path.empty())
+ return;
+
+ // Transform all coordinates to coords within "area"
+ Geom::Point shift = area->min();
+ Geom::Rect view = *area;
+ view.expandBy (stroke_width);
+ view = view * (Geom::Affine)Geom::Translate(-shift);
+ // Pass transformation to feed_curve, so that we don't need to create a whole new path.
+ Geom::Affine transshift(trans * Geom::Translate(-shift));
+
+ Geom::Point initial = path.initialPoint() * transshift;
+ cairo_move_to(ct, initial[0], initial[1] );
+
+ for(Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
+ feed_curve_to_cairo(ct, *cit, transshift, view, optimize_stroke);
+ }
+
+ if (path.closed()) {
+ if (!optimize_stroke) {
+ cairo_close_path(ct);
+ } else {
+ cairo_line_to(ct, initial[0], initial[1]);
+ /* We cannot use cairo_close_path(ct) here because some parts of the path may have been
+ clipped and not drawn (maybe the before last segment was outside view area), which
+ would result in closing the "subpath" after the last interruption, not the entire path.
+
+ However, according to cairo documentation:
+ The behavior of cairo_close_path() is distinct from simply calling cairo_line_to() with the equivalent coordinate
+ in the case of stroking. When a closed sub-path is stroked, there are no caps on the ends of the sub-path. Instead,
+ there is a line join connecting the final and initial segments of the sub-path.
+
+ The correct fix will be possible when cairo introduces methods for moving without
+ ending/starting subpaths, which we will use for skipping invisible segments; then we
+ will be able to use cairo_close_path here. This issue also affects ps/eps/pdf export,
+ see bug 168129
+ */
+ }
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the PathVector, with the given transform and shift
+ * One must have done cairo_new_path(ct); before calling this function. */
+void
+feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
+{
+ if (!area)
+ return;
+ if (pathv.empty())
+ return;
+
+ for(const auto & it : pathv) {
+ feed_path_to_cairo(ct, it, trans, area, optimize_stroke, stroke_width);
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the PathVector
+ * One must have done cairo_new_path(ct); before calling this function. */
+void
+feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv)
+{
+ if (pathv.empty())
+ return;
+
+ for(const auto & it : pathv) {
+ feed_path_to_cairo(ct, it);
+ }
+}
+
+SPColorInterpolation
+get_cairo_surface_ci(cairo_surface_t *surface) {
+ void* data = cairo_surface_get_user_data( surface, &ink_color_interpolation_key );
+ if( data != nullptr ) {
+ return (SPColorInterpolation)GPOINTER_TO_INT( data );
+ } else {
+ return SP_CSS_COLOR_INTERPOLATION_AUTO;
+ }
+}
+
+/** Set the color_interpolation_value for a Cairo surface.
+ * Transform the surface between sRGB and linearRGB if necessary. */
+void
+set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci) {
+
+ if( cairo_surface_get_content( surface ) != CAIRO_CONTENT_ALPHA ) {
+
+ SPColorInterpolation ci_in = get_cairo_surface_ci( surface );
+
+ if( ci_in == SP_CSS_COLOR_INTERPOLATION_SRGB &&
+ ci == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
+ ink_cairo_surface_srgb_to_linear( surface );
+ }
+ if( ci_in == SP_CSS_COLOR_INTERPOLATION_LINEARRGB &&
+ ci == SP_CSS_COLOR_INTERPOLATION_SRGB ) {
+ ink_cairo_surface_linear_to_srgb( surface );
+ }
+
+ cairo_surface_set_user_data(surface, &ink_color_interpolation_key, GINT_TO_POINTER (ci), nullptr);
+ }
+}
+
+void
+copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out) {
+ cairo_surface_set_user_data(out, &ink_color_interpolation_key, cairo_surface_get_user_data(in, &ink_color_interpolation_key), nullptr);
+}
+
+void
+ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba)
+{
+ cairo_set_source_rgba(ct, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
+}
+
+void
+ink_cairo_set_source_color(cairo_t *ct, SPColor const &c, double opacity)
+{
+ cairo_set_source_rgba(ct, c.v.c[0], c.v.c[1], c.v.c[2], opacity);
+}
+
+void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm)
+{
+ m[0] = cm.xx;
+ m[2] = cm.xy;
+ m[4] = cm.x0;
+ m[1] = cm.yx;
+ m[3] = cm.yy;
+ m[5] = cm.y0;
+}
+
+void ink_matrix_to_cairo(cairo_matrix_t &cm, Geom::Affine const &m)
+{
+ cm.xx = m[0];
+ cm.xy = m[2];
+ cm.x0 = m[4];
+ cm.yx = m[1];
+ cm.yy = m[3];
+ cm.y0 = m[5];
+}
+
+void
+ink_cairo_transform(cairo_t *ct, Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ ink_matrix_to_cairo(cm, m);
+ cairo_transform(ct, &cm);
+}
+
+void
+ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ ink_matrix_to_cairo(cm, m);
+ cairo_pattern_set_matrix(cp, &cm);
+}
+
+/**
+ * Create an exact copy of a surface.
+ * Creates a surface that has the same type, content type, dimensions and contents
+ * as the specified surface.
+ */
+cairo_surface_t *
+ink_cairo_surface_copy(cairo_surface_t *s)
+{
+ cairo_surface_t *ns = ink_cairo_surface_create_identical(s);
+
+ if (cairo_surface_get_type(s) == CAIRO_SURFACE_TYPE_IMAGE) {
+ // use memory copy instead of using a Cairo context
+ cairo_surface_flush(s);
+ int stride = cairo_image_surface_get_stride(s);
+ int h = cairo_image_surface_get_height(s);
+ memcpy(cairo_image_surface_get_data(ns), cairo_image_surface_get_data(s), stride * h);
+ cairo_surface_mark_dirty(ns);
+ } else {
+ // generic implementation
+ cairo_t *ct = cairo_create(ns);
+ cairo_set_source_surface(ct, s, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ }
+
+ return ns;
+}
+
+/**
+ * Create a surface that differs only in pixel content.
+ * Creates a surface that has the same type, content type and dimensions
+ * as the specified surface. Pixel contents are not copied.
+ */
+cairo_surface_t *
+ink_cairo_surface_create_identical(cairo_surface_t *s)
+{
+ cairo_surface_t *ns = ink_cairo_surface_create_same_size(s, cairo_surface_get_content(s));
+ cairo_surface_set_user_data(ns, &ink_color_interpolation_key, cairo_surface_get_user_data(s, &ink_color_interpolation_key), nullptr);
+ return ns;
+}
+
+cairo_surface_t *
+ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c)
+{
+ // ink_cairo_surface_get_width()/height() returns value in pixels
+ // cairo_surface_create_similar() uses device units
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale( s, &x_scale, &y_scale );
+
+ assert (x_scale > 0);
+ assert (y_scale > 0);
+
+ cairo_surface_t *ns =
+ cairo_surface_create_similar(s, c,
+ ink_cairo_surface_get_width(s)/x_scale,
+ ink_cairo_surface_get_height(s)/y_scale);
+ return ns;
+}
+
+/**
+ * Extract the alpha channel into a new surface.
+ * Creates a surface with a content type of CAIRO_CONTENT_ALPHA that contains
+ * the alpha values of pixels from @a s.
+ */
+cairo_surface_t *
+ink_cairo_extract_alpha(cairo_surface_t *s)
+{
+ cairo_surface_t *alpha = ink_cairo_surface_create_same_size(s, CAIRO_CONTENT_ALPHA);
+
+ cairo_t *ct = cairo_create(alpha);
+ cairo_set_source_surface(ct, s, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ return alpha;
+}
+
+cairo_surface_t *
+ink_cairo_surface_create_output(cairo_surface_t *image, cairo_surface_t *bg)
+{
+ cairo_content_t imgt = cairo_surface_get_content(image);
+ cairo_content_t bgt = cairo_surface_get_content(bg);
+ cairo_surface_t *out = nullptr;
+
+ if (bgt == CAIRO_CONTENT_ALPHA && imgt == CAIRO_CONTENT_ALPHA) {
+ out = ink_cairo_surface_create_identical(bg);
+ } else {
+ out = ink_cairo_surface_create_same_size(bg, CAIRO_CONTENT_COLOR_ALPHA);
+ }
+
+ return out;
+}
+
+void
+ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest)
+{
+ if (cairo_surface_get_type(src) == CAIRO_SURFACE_TYPE_IMAGE &&
+ cairo_surface_get_type(dest) == CAIRO_SURFACE_TYPE_IMAGE &&
+ cairo_image_surface_get_format(src) == cairo_image_surface_get_format(dest) &&
+ cairo_image_surface_get_height(src) == cairo_image_surface_get_height(dest) &&
+ cairo_image_surface_get_width(src) == cairo_image_surface_get_width(dest) &&
+ cairo_image_surface_get_stride(src) == cairo_image_surface_get_stride(dest))
+ {
+ // use memory copy instead of using a Cairo context
+ cairo_surface_flush(src);
+ int stride = cairo_image_surface_get_stride(src);
+ int h = cairo_image_surface_get_height(src);
+ memcpy(cairo_image_surface_get_data(dest), cairo_image_surface_get_data(src), stride * h);
+ cairo_surface_mark_dirty(dest);
+ } else {
+ // generic implementation
+ cairo_t *ct = cairo_create(dest);
+ cairo_set_source_surface(ct, src, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ }
+}
+
+/**
+ * Return width in pixels.
+ */
+int
+ink_cairo_surface_get_width(cairo_surface_t *surface)
+{
+ // For now only image surface is handled.
+ // Later add others, e.g. cairo-gl
+ assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
+ return cairo_image_surface_get_width(surface);
+}
+
+/**
+ * Return height in pixels.
+ */
+int
+ink_cairo_surface_get_height(cairo_surface_t *surface)
+{
+ assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
+ return cairo_image_surface_get_height(surface);
+}
+
+static int ink_cairo_surface_average_color_internal(cairo_surface_t *surface, double &rf, double &gf, double &bf, double &af)
+{
+ rf = gf = bf = af = 0.0;
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ int stride = cairo_image_surface_get_stride(surface);
+ unsigned char *data = cairo_image_surface_get_data(surface);
+
+ /* TODO convert this to OpenMP somehow */
+ for (int y = 0; y < height; ++y, data += stride) {
+ for (int x = 0; x < width; ++x) {
+ guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ EXTRACT_ARGB32(px, a,r,g,b)
+ rf += r / 255.0;
+ gf += g / 255.0;
+ bf += b / 255.0;
+ af += a / 255.0;
+ }
+ }
+ return width * height;
+}
+
+guint32 ink_cairo_surface_average_color(cairo_surface_t *surface)
+{
+ double rf,gf,bf,af;
+ ink_cairo_surface_average_color_premul(surface, rf,gf,bf,af);
+ guint32 r = round(rf * 255);
+ guint32 g = round(gf * 255);
+ guint32 b = round(bf * 255);
+ guint32 a = round(af * 255);
+ ASSEMBLE_ARGB32(px, a,r,g,b);
+ return px;
+}
+
+void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
+{
+ int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
+
+ r /= a;
+ g /= a;
+ b /= a;
+ a /= count;
+
+ r = CLAMP(r, 0.0, 1.0);
+ g = CLAMP(g, 0.0, 1.0);
+ b = CLAMP(b, 0.0, 1.0);
+ a = CLAMP(a, 0.0, 1.0);
+}
+
+void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
+{
+ int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
+
+ r /= count;
+ g /= count;
+ b /= count;
+ a /= count;
+
+ r = CLAMP(r, 0.0, 1.0);
+ g = CLAMP(g, 0.0, 1.0);
+ b = CLAMP(b, 0.0, 1.0);
+ a = CLAMP(a, 0.0, 1.0);
+}
+
+static guint32 srgb_to_linear( const guint32 c, const guint32 a ) {
+
+ const guint32 c1 = unpremul_alpha( c, a );
+
+ double cc = c1/255.0;
+
+ if( cc < 0.04045 ) {
+ cc /= 12.92;
+ } else {
+ cc = pow( (cc+0.055)/1.055, 2.4 );
+ }
+ cc *= 255.0;
+
+ const guint32 c2 = (int)cc;
+
+ return premul_alpha( c2, a );
+}
+
+static guint32 linear_to_srgb( const guint32 c, const guint32 a ) {
+
+ const guint32 c1 = unpremul_alpha( c, a );
+
+ double cc = c1/255.0;
+
+ if( cc < 0.0031308 ) {
+ cc *= 12.92;
+ } else {
+ cc = pow( cc, 1.0/2.4 )*1.055-0.055;
+ }
+ cc *= 255.0;
+
+ const guint32 c2 = (int)cc;
+
+ return premul_alpha( c2, a );
+}
+
+struct SurfaceSrgbToLinear {
+
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a,r,g,b) ; // Unneeded semi-colon for indenting
+ if( a != 0 ) {
+ r = srgb_to_linear( r, a );
+ g = srgb_to_linear( g, a );
+ b = srgb_to_linear( b, a );
+ }
+ ASSEMBLE_ARGB32(out, a,r,g,b);
+ return out;
+ }
+private:
+ /* None */
+};
+
+int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface)
+{
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ // int stride = cairo_image_surface_get_stride(surface);
+ // unsigned char *data = cairo_image_surface_get_data(surface);
+
+ ink_cairo_surface_filter( surface, surface, SurfaceSrgbToLinear() );
+
+ /* TODO convert this to OpenMP somehow */
+ // for (int y = 0; y < height; ++y, data += stride) {
+ // for (int x = 0; x < width; ++x) {
+ // guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ // EXTRACT_ARGB32(px, a,r,g,b) ; // Unneeded semi-colon for indenting
+ // if( a != 0 ) {
+ // r = srgb_to_linear( r, a );
+ // g = srgb_to_linear( g, a );
+ // b = srgb_to_linear( b, a );
+ // }
+ // ASSEMBLE_ARGB32(px2, a,r,g,b);
+ // *reinterpret_cast<guint32*>(data + 4*x) = px2;
+ // }
+ // }
+ return width * height;
+}
+
+struct SurfaceLinearToSrgb {
+
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a,r,g,b) ; // Unneeded semi-colon for indenting
+ if( a != 0 ) {
+ r = linear_to_srgb( r, a );
+ g = linear_to_srgb( g, a );
+ b = linear_to_srgb( b, a );
+ }
+ ASSEMBLE_ARGB32(out, a,r,g,b);
+ return out;
+ }
+private:
+ /* None */
+};
+
+SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)
+{
+ // All of the blend modes are implemented in Cairo as of 1.10.
+ // For a detailed description, see:
+ // http://cairographics.org/operators/
+ auto res = SP_CSS_BLEND_NORMAL;
+ switch (cairo_operator) {
+ case CAIRO_OPERATOR_MULTIPLY:
+ res = SP_CSS_BLEND_MULTIPLY;
+ break;
+ case CAIRO_OPERATOR_SCREEN:
+ res = SP_CSS_BLEND_SCREEN;
+ break;
+ case CAIRO_OPERATOR_DARKEN:
+ res = SP_CSS_BLEND_DARKEN;
+ break;
+ case CAIRO_OPERATOR_LIGHTEN:
+ res = SP_CSS_BLEND_LIGHTEN;
+ break;
+ case CAIRO_OPERATOR_OVERLAY:
+ res = SP_CSS_BLEND_OVERLAY;
+ break;
+ case CAIRO_OPERATOR_COLOR_DODGE:
+ res = SP_CSS_BLEND_COLORDODGE;
+ break;
+ case CAIRO_OPERATOR_COLOR_BURN:
+ res = SP_CSS_BLEND_COLORBURN;
+ break;
+ case CAIRO_OPERATOR_HARD_LIGHT:
+ res = SP_CSS_BLEND_HARDLIGHT;
+ break;
+ case CAIRO_OPERATOR_SOFT_LIGHT:
+ res = SP_CSS_BLEND_SOFTLIGHT;
+ break;
+ case CAIRO_OPERATOR_DIFFERENCE:
+ res = SP_CSS_BLEND_DIFFERENCE;
+ break;
+ case CAIRO_OPERATOR_EXCLUSION:
+ res = SP_CSS_BLEND_EXCLUSION;
+ break;
+ case CAIRO_OPERATOR_HSL_HUE:
+ res = SP_CSS_BLEND_HUE;
+ break;
+ case CAIRO_OPERATOR_HSL_SATURATION:
+ res = SP_CSS_BLEND_SATURATION;
+ break;
+ case CAIRO_OPERATOR_HSL_COLOR:
+ res = SP_CSS_BLEND_COLOR;
+ break;
+ case CAIRO_OPERATOR_HSL_LUMINOSITY:
+ res = SP_CSS_BLEND_LUMINOSITY;
+ break;
+ case CAIRO_OPERATOR_OVER:
+ default:
+ res = SP_CSS_BLEND_NORMAL;
+ break;
+ }
+ return res;
+}
+
+cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
+{
+ // All of the blend modes are implemented in Cairo as of 1.10.
+ // For a detailed description, see:
+ // http://cairographics.org/operators/
+
+ cairo_operator_t res = CAIRO_OPERATOR_OVER;
+ switch (css_blend) {
+ case SP_CSS_BLEND_MULTIPLY:
+ res = CAIRO_OPERATOR_MULTIPLY;
+ break;
+ case SP_CSS_BLEND_SCREEN:
+ res = CAIRO_OPERATOR_SCREEN;
+ break;
+ case SP_CSS_BLEND_DARKEN:
+ res = CAIRO_OPERATOR_DARKEN;
+ break;
+ case SP_CSS_BLEND_LIGHTEN:
+ res = CAIRO_OPERATOR_LIGHTEN;
+ break;
+ case SP_CSS_BLEND_OVERLAY:
+ res = CAIRO_OPERATOR_OVERLAY;
+ break;
+ case SP_CSS_BLEND_COLORDODGE:
+ res = CAIRO_OPERATOR_COLOR_DODGE;
+ break;
+ case SP_CSS_BLEND_COLORBURN:
+ res = CAIRO_OPERATOR_COLOR_BURN;
+ break;
+ case SP_CSS_BLEND_HARDLIGHT:
+ res = CAIRO_OPERATOR_HARD_LIGHT;
+ break;
+ case SP_CSS_BLEND_SOFTLIGHT:
+ res = CAIRO_OPERATOR_SOFT_LIGHT;
+ break;
+ case SP_CSS_BLEND_DIFFERENCE:
+ res = CAIRO_OPERATOR_DIFFERENCE;
+ break;
+ case SP_CSS_BLEND_EXCLUSION:
+ res = CAIRO_OPERATOR_EXCLUSION;
+ break;
+ case SP_CSS_BLEND_HUE:
+ res = CAIRO_OPERATOR_HSL_HUE;
+ break;
+ case SP_CSS_BLEND_SATURATION:
+ res = CAIRO_OPERATOR_HSL_SATURATION;
+ break;
+ case SP_CSS_BLEND_COLOR:
+ res = CAIRO_OPERATOR_HSL_COLOR;
+ break;
+ case SP_CSS_BLEND_LUMINOSITY:
+ res = CAIRO_OPERATOR_HSL_LUMINOSITY;
+ break;
+ case SP_CSS_BLEND_NORMAL:
+ res = CAIRO_OPERATOR_OVER;
+ break;
+ default:
+ g_error("Invalid SPBlendMode %d", css_blend);
+ }
+ return res;
+}
+
+
+
+int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface)
+{
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ // int stride = cairo_image_surface_get_stride(surface);
+ // unsigned char *data = cairo_image_surface_get_data(surface);
+
+ ink_cairo_surface_filter( surface, surface, SurfaceLinearToSrgb() );
+
+ // /* TODO convert this to OpenMP somehow */
+ // for (int y = 0; y < height; ++y, data += stride) {
+ // for (int x = 0; x < width; ++x) {
+ // guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ // EXTRACT_ARGB32(px, a,r,g,b) ; // Unneeded semi-colon for indenting
+ // if( a != 0 ) {
+ // r = linear_to_srgb( r, a );
+ // g = linear_to_srgb( g, a );
+ // b = linear_to_srgb( b, a );
+ // }
+ // ASSEMBLE_ARGB32(px2, a,r,g,b);
+ // *reinterpret_cast<guint32*>(data + 4*x) = px2;
+ // }
+ // }
+ return width * height;
+}
+
+cairo_pattern_t *
+ink_cairo_pattern_create_checkerboard(guint32 rgba)
+{
+ int const w = 6;
+ int const h = 6;
+
+ double r = SP_RGBA32_R_F(rgba);
+ double g = SP_RGBA32_G_F(rgba);
+ double b = SP_RGBA32_B_F(rgba);
+
+ float hsl[3];
+ SPColor::rgb_to_hsl_floatv(hsl, r, g, b);
+ hsl[2] += hsl[2] < 0.08 ? 0.08 : -0.08; // 0.08 = 0.77-0.69, the original checkerboard colors.
+
+ float rgb2[3];
+ SPColor::hsl_to_rgb_floatv(rgb2, hsl[0], hsl[1], hsl[2]);
+
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2*w, 2*h);
+
+ cairo_t *ct = cairo_create(s);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgb(ct, r, g, b);
+ cairo_paint(ct);
+ cairo_set_source_rgb(ct, rgb2[0], rgb2[1], rgb2[2]);
+ cairo_rectangle(ct, 0, 0, w, h);
+ cairo_rectangle(ct, w, h, w, h);
+ cairo_fill(ct);
+ cairo_destroy(ct);
+
+ cairo_pattern_t *p = cairo_pattern_create_for_surface(s);
+ cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(p, CAIRO_FILTER_NEAREST);
+
+ cairo_surface_destroy(s);
+ return p;
+}
+
+/**
+ * Converts the Cairo surface to a GdkPixbuf pixel format,
+ * without allocating extra memory.
+ *
+ * This function is intended mainly for creating previews displayed by GTK.
+ * For loading images for display on the canvas, use the Inkscape::Pixbuf object.
+ *
+ * The returned GdkPixbuf takes ownership of the passed surface reference,
+ * so it should NOT be freed after calling this function.
+ */
+GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s)
+{
+ guchar *pixels = cairo_image_surface_get_data(s);
+ int w = cairo_image_surface_get_width(s);
+ int h = cairo_image_surface_get_height(s);
+ int rs = cairo_image_surface_get_stride(s);
+
+ convert_pixels_argb32_to_pixbuf(pixels, w, h, rs);
+
+ GdkPixbuf *pb = gdk_pixbuf_new_from_data(
+ pixels, GDK_COLORSPACE_RGB, TRUE, 8,
+ w, h, rs, ink_cairo_pixbuf_cleanup, s);
+
+ return pb;
+}
+
+/**
+ * Cleanup function for GdkPixbuf.
+ * This function should be passed as the GdkPixbufDestroyNotify parameter
+ * to gdk_pixbuf_new_from_data when creating a GdkPixbuf backed by
+ * a Cairo surface.
+ */
+void ink_cairo_pixbuf_cleanup(guchar * /*pixels*/, void *data)
+{
+ cairo_surface_t *surface = static_cast<cairo_surface_t*>(data);
+ cairo_surface_destroy(surface);
+}
+
+/* The following two functions use "from" instead of "to", because when you write:
+ val1 = argb32_from_pixbuf(val1);
+ the name of the format is closer to the value in that format. */
+
+guint32 argb32_from_pixbuf(guint32 c)
+{
+ guint32 o = 0;
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 a = (c & 0xff000000) >> 24;
+#else
+ guint32 a = (c & 0x000000ff);
+#endif
+ if (a != 0) {
+ // extract color components
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 r = (c & 0x000000ff);
+ guint32 g = (c & 0x0000ff00) >> 8;
+ guint32 b = (c & 0x00ff0000) >> 16;
+#else
+ guint32 r = (c & 0xff000000) >> 24;
+ guint32 g = (c & 0x00ff0000) >> 16;
+ guint32 b = (c & 0x0000ff00) >> 8;
+#endif
+ // premultiply
+ r = premul_alpha(r, a);
+ b = premul_alpha(b, a);
+ g = premul_alpha(g, a);
+ // combine into output
+ o = (a << 24) | (r << 16) | (g << 8) | (b);
+ }
+ return o;
+}
+
+guint32 pixbuf_from_argb32(guint32 c)
+{
+ guint32 a = (c & 0xff000000) >> 24;
+ if (a == 0) return 0;
+
+ // extract color components
+ guint32 r = (c & 0x00ff0000) >> 16;
+ guint32 g = (c & 0x0000ff00) >> 8;
+ guint32 b = (c & 0x000000ff);
+ // unpremultiply; adding a/2 gives correct rounding
+ // (taken from Cairo sources)
+ r = (r * 255 + a/2) / a;
+ b = (b * 255 + a/2) / a;
+ g = (g * 255 + a/2) / a;
+ // combine into output
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 o = (r) | (g << 8) | (b << 16) | (a << 24);
+#else
+ guint32 o = (r << 24) | (g << 16) | (b << 8) | (a);
+#endif
+ return o;
+}
+
+/**
+ * Convert pixel data from GdkPixbuf format to ARGB.
+ * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
+ * This involves premultiplying alpha and shuffling around the channels.
+ * Pixbuf data must have an alpha channel, otherwise the results are undefined
+ * (usually a segfault).
+ */
+void
+convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int stride)
+{
+ for (size_t i = 0; i < h; ++i) {
+ guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
+ for (int j = 0; j < w; ++j) {
+ *px = argb32_from_pixbuf(*px);
+ ++px;
+ }
+ }
+}
+
+/**
+ * Convert pixel data from ARGB to GdkPixbuf format.
+ * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
+ * This involves premultiplying alpha and shuffling around the channels.
+ */
+void
+convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride)
+{
+ for (size_t i = 0; i < h; ++i) {
+ guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
+ for (int j = 0; j < w; ++j) {
+ *px = pixbuf_from_argb32(*px);
+ ++px;
+ }
+ }
+}
+
+/**
+ * Converts GdkPixbuf's data to premultiplied ARGB.
+ * This function will convert a GdkPixbuf in place into Cairo's native pixel format.
+ * Note that this is a hack intended to save memory. When the pixbuf is in Cairo's format,
+ * using it with GTK will result in corrupted drawings.
+ */
+void
+ink_pixbuf_ensure_argb32(GdkPixbuf *pb)
+{
+ gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
+ if (pixel_format != nullptr && strcmp(pixel_format, "argb32") == 0) {
+ // nothing to do
+ return;
+ }
+
+ convert_pixels_pixbuf_to_argb32(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+ g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free);
+}
+
+/**
+ * Converts GdkPixbuf's data back to its native format.
+ * Once this is done, the pixbuf can be used with GTK again.
+ */
+void
+ink_pixbuf_ensure_normal(GdkPixbuf *pb)
+{
+ gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
+ if (pixel_format == nullptr || strcmp(pixel_format, "pixbuf") == 0) {
+ // nothing to do
+ return;
+ }
+
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+ g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("pixbuf"), g_free);
+}
+
+guint32 argb32_from_rgba(guint32 in)
+{
+ guint32 r, g, b, a;
+ a = (in & 0x000000ff);
+ r = premul_alpha((in & 0xff000000) >> 24, a);
+ g = premul_alpha((in & 0x00ff0000) >> 16, a);
+ b = premul_alpha((in & 0x0000ff00) >> 8, a);
+ ASSEMBLE_ARGB32(px, a, r, g, b)
+ return px;
+}
+
+
+/**
+ * Converts a pixbuf to a PNG data structure.
+ * For 8-but RGBA png, this is like copying.
+ *
+ */
+const guchar* pixbuf_to_png(guchar const**rows, guchar* px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
+{
+ int n_fields = 1 + (color_type&2) + (color_type&4)/4;
+ const guchar* new_data = (const guchar*)malloc((n_fields * bit_depth * num_rows * num_cols)/8 + 64);
+ char* ptr = (char*) new_data;
+ int pad=0; //used when we write image data smaller than one byte (for instance in black and white images where 1px = 1bit)
+ for(int row = 0; row < num_rows; ++row){
+ rows[row] = (const guchar*)ptr;
+ for(int col = 0; col < num_cols; ++col){
+ guint32 *pixel = reinterpret_cast<guint32*>(px + row*stride)+col;
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ //this part should probably be rewritten as (a tested, working) big endian, using htons() and ntohs()
+ guint64 a = (*pixel & 0xff000000) >> 24;
+ guint64 b = (*pixel & 0x00ff0000) >> 16;
+ guint64 g = (*pixel & 0x0000ff00) >> 8;
+ guint64 r = (*pixel & 0x000000ff);
+
+ //one of possible rgb to greyscale formulas. This one is called "luminance, "luminosity" or "luma"
+ guint16 gray = (guint16)((guint32)((0.2126*(r<<24) + 0.7152*(g<<24) + 0.0722*(b<<24)))>>16);
+
+ if (!pad) *((guint64*)ptr)=0;
+ if (color_type & 2) { //RGB
+ // for 8bit->16bit transition, I take the FF -> FFFF convention (multiplication by 0x101).
+ // If you prefer FF -> FF00 (multiplication by 0x100), remove the <<8, <<24, <<40 and <<56
+ if (color_type & 4) { //RGBA
+ if (bit_depth==8)
+ *((guint32*)ptr) = *pixel;
+ else
+ *((guint64*)ptr) = (guint64)((a<<56)+(a<<48)+(b<<40)+(b<<32)+(g<<24)+(g<<16)+(r<<8)+(r));
+ }
+ else{ //no alpha
+ if(bit_depth==8)
+ *((guint32*)ptr) = ((*pixel)<<8)>>8;
+ else
+ *((guint64*)ptr) = (guint64)((b<<40)+(b<<32)+(g<<24)+(g<<16)+(r<<8)+r);
+ }
+ } else { //Grayscale
+ int realpad = 8 - bit_depth - pad; // in PNG numbers are stored left to right, but in most significant bits first, so the first one processed is the ``big'' mask, etc.
+ if(bit_depth==16)
+ *(guint16*)ptr= ((gray & 0xff00)>>8) + ((gray &0x00ff)<<8);
+ else *((guint16*)ptr) += guint16(((gray >> (16-bit_depth))<<realpad) ); //note the "+="
+
+ if(color_type & 4) { //Alpha channel
+ if (bit_depth == 16)
+ *((guint32*)(ptr+2)) = a + (a<<8);
+ else
+ *((guint32*)(ptr)) += guint32((a << 8) >> (16 - bit_depth))<<(bit_depth + realpad);
+ }
+ }
+
+#else
+ // I don't have any access to a big-endian machine to test this. It should still work with default export settings.
+ guint64 r = (*pixel & 0xff000000) >> 24;
+ guint64 g = (*pixel & 0x00ff0000) >> 16;
+ guint64 b = (*pixel & 0x0000ff00) >> 8;
+ guint64 a = (*pixel & 0x000000ff);
+ guint32 gray = (guint32)(0.2126*(r<<24) + 0.7152*(g<<24) + 0.0722*(b<<24));
+ if(color_type & 2){
+ if(bit_depth==8)*ptr = *pixel; else *ptr = (guint64)((r<<56)+(g<<40)+(b<<24)+(a<<8));
+ } else {
+ *((guint32*)ptr) += guint32(gray>>pad);
+ if(color_type & 4) *((guint32*)ptr) += guint32((a>>pad)>> bit_depth);
+ }
+#endif
+ pad+=bit_depth*n_fields;
+ ptr+=(pad/8);
+ pad%=8;
+ }
+ if(pad){pad=0;ptr++;}//align bytes on rows
+ }
+ return new_data;
+}
+
+
+
+
+
+
+
+
+/*
+ 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/display/cairo-utils.h b/src/display/cairo-utils.h
new file mode 100644
index 0000000..232e666
--- /dev/null
+++ b/src/display/cairo-utils.h
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo integration helpers.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_CAIRO_UTILS_H
+#define SEEN_INKSCAPE_DISPLAY_CAIRO_UTILS_H
+
+#include <2geom/forward.h>
+#include <cairomm/cairomm.h>
+#include "style.h"
+
+struct SPColor;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+void ink_cairo_pixbuf_cleanup(unsigned char *, void *);
+void convert_pixbuf_argb32_to_normal(GdkPixbuf *pb);
+
+namespace Inkscape {
+
+/**
+ * RAII idiom for Cairo groups.
+ * Groups are temporary surfaces used when rendering e.g. masks and opacity.
+ * Use this class to ensure that each group push is matched with a pop.
+ */
+class CairoGroup {
+public:
+ CairoGroup(cairo_t *_ct);
+ ~CairoGroup();
+ void push();
+ void push_with_content(cairo_content_t content);
+ cairo_pattern_t *pop();
+ Cairo::RefPtr<Cairo::Pattern> popmm();
+ void pop_to_source();
+private:
+ cairo_t *ct;
+ bool pushed;
+};
+
+/** RAII idiom for Cairo state saving. */
+class CairoSave {
+public:
+ CairoSave(cairo_t *_ct, bool save=false)
+ : ct(_ct)
+ , saved(save)
+ {
+ if (save) {
+ cairo_save(ct);
+ }
+ }
+ void save() {
+ if (!saved) {
+ cairo_save(ct);
+ saved = true;
+ }
+ }
+ ~CairoSave() {
+ if (saved)
+ cairo_restore(ct);
+ }
+private:
+ cairo_t *ct;
+ bool saved;
+};
+
+/** Cairo context with Inkscape-specific operations. */
+class CairoContext : public Cairo::Context {
+public:
+ CairoContext(cairo_t *obj, bool ref = false);
+
+ void transform(Geom::Affine const &m);
+ void set_source_rgba32(guint32 color);
+ void append_path(Geom::PathVector const &pv);
+
+ static Cairo::RefPtr<CairoContext> create(Cairo::RefPtr<Cairo::Surface> const &target);
+};
+
+/** Class to hold image data for raster images.
+ * Allows easy interoperation with GdkPixbuf and Cairo. */
+class Pixbuf {
+public:
+ enum PixelFormat {
+ PF_CAIRO = 1,
+ PF_GDK = 2,
+ PF_LAST
+ };
+
+ explicit Pixbuf(cairo_surface_t *s);
+ explicit Pixbuf(GdkPixbuf *pb);
+ Pixbuf(Inkscape::Pixbuf const &other);
+ ~Pixbuf();
+
+ GdkPixbuf *getPixbufRaw(bool convert_format = true);
+ //Glib::RefPtr<Gdk::Pixbuf> getPixbuf(bool convert_format = true);
+
+ cairo_surface_t *getSurfaceRaw(bool convert_format = true);
+ Cairo::RefPtr<Cairo::Surface> getSurface(bool convert_format = true);
+
+ int width() const;
+ int height() const;
+ int rowstride() const;
+ guchar const *pixels() const;
+ guchar *pixels();
+ void markDirty();
+
+ bool hasMimeData() const;
+ guchar const *getMimeData(gsize &len, std::string &mimetype) const;
+ std::string const &originalPath() const { return _path; }
+ time_t modificationTime() const { return _mod_time; }
+
+ PixelFormat pixelFormat() const { return _pixel_format; }
+ void ensurePixelFormat(PixelFormat fmt);
+
+ static Pixbuf *create_from_data_uri(gchar const *uri, double svgdpi = 0);
+ static Pixbuf *create_from_file(std::string const &fn, double svgddpi = 0);
+ static Pixbuf *create_from_buffer(std::string const &, double svgddpi = 0, std::string const &fn = "");
+
+ private:
+ static Pixbuf *create_from_buffer(gchar *&&, gsize, double svgddpi = 0, std::string const &fn = "");
+
+ void _ensurePixelsARGB32();
+ void _ensurePixelsPixbuf();
+ void _forceAlpha();
+ void _setMimeData(guchar *data, gsize len, Glib::ustring const &format);
+
+ GdkPixbuf *_pixbuf;
+ cairo_surface_t *_surface;
+ time_t _mod_time;
+ std::string _path;
+ PixelFormat _pixel_format;
+ bool _cairo_store;
+};
+
+} // namespace Inkscape
+
+// TODO: these declarations may not be needed in the header
+extern cairo_user_data_key_t ink_color_interpolation_key;
+extern cairo_user_data_key_t ink_pixbuf_key;
+
+SPColorInterpolation get_cairo_surface_ci(cairo_surface_t *surface);
+void set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation cif);
+void copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out);
+void convert_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation cif);
+
+void ink_cairo_set_source_color(cairo_t *ct, SPColor const &color, double opacity);
+void ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba);
+void ink_cairo_transform(cairo_t *ct, Geom::Affine const &m);
+void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m);
+
+void ink_matrix_to_2geom(Geom::Affine &, cairo_matrix_t const &);
+void ink_matrix_to_cairo(cairo_matrix_t &, Geom::Affine const &);
+cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode blend_mode);
+SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator);
+cairo_surface_t *ink_cairo_surface_copy(cairo_surface_t *s);
+cairo_surface_t *ink_cairo_surface_create_identical(cairo_surface_t *s);
+cairo_surface_t *ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c);
+cairo_surface_t *ink_cairo_extract_alpha(cairo_surface_t *s);
+cairo_surface_t *ink_cairo_surface_create_output(cairo_surface_t *image, cairo_surface_t *bg);
+void ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest);
+int ink_cairo_surface_get_width(cairo_surface_t *surface);
+int ink_cairo_surface_get_height(cairo_surface_t *surface);
+guint32 ink_cairo_surface_average_color(cairo_surface_t *surface);
+void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a);
+void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a);
+
+double srgb_to_linear( const double c );
+int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface);
+int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface);
+
+cairo_pattern_t *ink_cairo_pattern_create_checkerboard(guint32 rgba = 0xC4C4C4FF);
+
+GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s);
+void convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int rs);
+void convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int rs);
+
+G_GNUC_CONST guint32 argb32_from_pixbuf(guint32 in);
+G_GNUC_CONST guint32 pixbuf_from_argb32(guint32 in);
+const guchar* pixbuf_to_png(guchar const**rows, guchar* px, int nrows, int ncols, int stride, int color_type, int bit_depth);
+
+/** Convert a pixel in 0xRRGGBBAA format to Cairo ARGB32 format. */
+G_GNUC_CONST guint32 argb32_from_rgba(guint32 in);
+
+
+G_GNUC_CONST inline guint32
+premul_alpha(const guint32 color, const guint32 alpha)
+{
+ const guint32 temp = alpha * color + 128;
+ return (temp + (temp >> 8)) >> 8;
+}
+G_GNUC_CONST inline guint32
+unpremul_alpha(const guint32 color, const guint32 alpha)
+{
+ // NOTE: you must check for alpha != 0 yourself.
+ return (255 * color + alpha/2) / alpha;
+}
+
+// TODO: move those to 2Geom
+void feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width);
+void feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv);
+
+#define EXTRACT_ARGB32(px,a,r,g,b) \
+ guint32 a, r, g, b; \
+ a = ((px) & 0xff000000) >> 24; \
+ r = ((px) & 0x00ff0000) >> 16; \
+ g = ((px) & 0x0000ff00) >> 8; \
+ b = ((px) & 0x000000ff);
+
+#define ASSEMBLE_ARGB32(px,a,r,g,b) \
+ guint32 px = (a << 24) | (r << 16) | (g << 8) | b;
+
+inline double srgb_to_linear( const double c ) {
+ if( c < 0.04045 ) {
+ return c / 12.92;
+ } else {
+ return pow( (c+0.055)/1.055, 2.4 );
+ }
+}
+
+
+namespace Inkscape {
+
+namespace Display
+{
+
+inline void ExtractARGB32(guint32 px, guint32 &a, guint32 &r, guint32 &g, guint &b)
+{
+ a = ((px) & 0xff000000) >> 24;
+ r = ((px) & 0x00ff0000) >> 16;
+ g = ((px) & 0x0000ff00) >> 8;
+ b = ((px) & 0x000000ff);
+}
+
+inline void ExtractRGB32(guint32 px, guint32 &r, guint32 &g, guint &b)
+{
+ r = ((px) & 0x00ff0000) >> 16;
+ g = ((px) & 0x0000ff00) >> 8;
+ b = ((px) & 0x000000ff);
+}
+
+inline guint AssembleARGB32(guint32 a, guint32 r, guint32 g, guint32 b)
+{
+ return (a << 24) | (r << 16) | (g << 8) | b;
+}
+
+} // namespace Display
+
+} // namespace Inkscape
+
+#endif // SEEN_INKSCAPE_DISPLAY_CAIRO_UTILS_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/display/canvas-arena.cpp b/src/display/canvas-arena.cpp
new file mode 100644
index 0000000..1ab617f
--- /dev/null
+++ b/src/display/canvas-arena.cpp
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include "display/sp-canvas-util.h"
+#include "helper/sp-marshal.h"
+#include "display/canvas-arena.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-item.h"
+#include "display/drawing-group.h"
+#include "display/drawing-surface.h"
+#include "preferences.h"
+
+using namespace Inkscape;
+
+enum {
+ ARENA_EVENT,
+ LAST_SIGNAL
+};
+
+static void sp_canvas_arena_destroy(SPCanvasItem *object);
+
+static void sp_canvas_arena_item_deleted(SPCanvasArena *arena, Inkscape::DrawingItem *item);
+static void sp_canvas_arena_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static double sp_canvas_arena_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+static void sp_canvas_arena_viewbox_changed (SPCanvasItem *item, Geom::IntRect const &new_area);
+static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event);
+
+static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event);
+
+static void sp_canvas_arena_request_update (SPCanvasArena *ca, DrawingItem *item);
+static void sp_canvas_arena_request_render (SPCanvasArena *ca, Geom::IntRect const &area);
+
+static guint signals[LAST_SIGNAL] = {0};
+
+struct CachePrefObserver : public Inkscape::Preferences::Observer {
+ CachePrefObserver(SPCanvasArena *arena)
+ : Inkscape::Preferences::Observer("/options/renderingcache")
+ , _arena(arena)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::vector<Inkscape::Preferences::Entry> v = prefs->getAllEntries(observed_path);
+ for (const auto & i : v) {
+ notify(i);
+ }
+ prefs->addObserver(*this);
+ }
+ void notify(Preferences::Entry const &v) override {
+ Glib::ustring name = v.getEntryName();
+ if (name == "size") {
+ _arena->drawing.setCacheBudget((1 << 20) * v.getIntLimited(64, 0, 4096));
+ }
+ }
+ SPCanvasArena *_arena;
+};
+
+G_DEFINE_TYPE(SPCanvasArena, sp_canvas_arena, SP_TYPE_CANVAS_ITEM);
+
+static void
+sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
+{
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ signals[ARENA_EVENT] = g_signal_new ("arena_event",
+ G_TYPE_FROM_CLASS(item_class),
+ G_SIGNAL_RUN_LAST,
+ ((glong)((guint8*)&(klass->arena_event) - (guint8*)klass)),
+ nullptr, nullptr,
+ sp_marshal_INT__POINTER_POINTER,
+ G_TYPE_INT, 2, G_TYPE_POINTER, G_TYPE_POINTER);
+
+ item_class->destroy = sp_canvas_arena_destroy;
+ item_class->update = sp_canvas_arena_update;
+ item_class->render = sp_canvas_arena_render;
+ item_class->point = sp_canvas_arena_point;
+ item_class->event = sp_canvas_arena_event;
+ item_class->viewbox_changed = sp_canvas_arena_viewbox_changed;
+}
+
+static void
+sp_canvas_arena_init (SPCanvasArena *arena)
+{
+ arena->sticky = FALSE;
+
+ new (&arena->drawing) Inkscape::Drawing(arena);
+
+ Inkscape::DrawingGroup *root = new DrawingGroup(arena->drawing);
+ root->setPickChildren(true);
+ arena->drawing.setRoot(root);
+
+ arena->observer = new CachePrefObserver(arena);
+
+ arena->drawing.signal_request_update.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_request_update),
+ arena));
+ arena->drawing.signal_request_render.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_request_render),
+ arena));
+ arena->drawing.signal_item_deleted.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_item_deleted),
+ arena));
+
+ arena->active = nullptr;
+}
+
+static void sp_canvas_arena_destroy(SPCanvasItem *object)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA(object);
+
+ delete arena->observer;
+ arena->drawing.~Drawing();
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->destroy(object);
+}
+
+static void
+sp_canvas_arena_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->update(item, affine, flags);
+
+ arena->ctx.ctm = affine;
+
+ unsigned reset = flags & SP_CANVAS_UPDATE_AFFINE ? DrawingItem::STATE_ALL : 0;
+ arena->drawing.update(Geom::IntRect::infinite(), DrawingItem::STATE_ALL, reset);
+
+ Geom::OptIntRect b = arena->drawing.root()->visualBounds();
+ if (b) {
+ item->x1 = b->left() - 1;
+ item->y1 = b->top() - 1;
+ item->x2 = b->right() + 1;
+ item->y2 = b->bottom() + 1;
+ }
+
+ if (arena->cursor) {
+ /* Mess with enter/leave notifiers */
+ DrawingItem *new_arena = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ if (new_arena != arena->active) {
+ GdkEventCrossing ec;
+ ec.window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+ ec.send_event = TRUE;
+ ec.subwindow = ec.window;
+ ec.time = GDK_CURRENT_TIME;
+ ec.x = arena->c[Geom::X];
+ ec.y = arena->c[Geom::Y];
+ /* fixme: */
+ if (arena->active) {
+ ec.type = GDK_LEAVE_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ arena->active = new_arena;
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ }
+}
+
+static void
+sp_canvas_arena_item_deleted(SPCanvasArena *arena, Inkscape::DrawingItem *item)
+{
+ if (arena->active == item) {
+ arena->active = nullptr;
+ }
+}
+
+static void
+sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ // todo: handle NR_ARENA_ITEM_RENDER_NO_CACHE
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ Geom::OptIntRect r = buf->rect;
+ if (!r || r->hasZeroArea()) return;
+
+ Inkscape::DrawingContext dc(buf->ct, r->min());
+
+ arena->drawing.update(Geom::IntRect::infinite());
+ arena->drawing.render(dc, *r);
+}
+
+static double
+sp_canvas_arena_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ arena->drawing.update(Geom::IntRect::infinite(), DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX);
+ DrawingItem *picked = arena->drawing.pick(p, arena->drawing.delta, arena->sticky);
+
+ arena->picked = picked;
+
+ if (picked) {
+ *actual_item = item;
+ return 0.0;
+ }
+
+ return 1e18;
+}
+
+static void
+sp_canvas_arena_viewbox_changed (SPCanvasItem *item, Geom::IntRect const &new_area)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA(item);
+ // make the cache limit larger than screen to facilitate smooth scrolling
+ Geom::IntRect expanded = new_area;
+ Geom::IntPoint expansion(new_area.width()/2, new_area.height()/2);
+ expanded.expandBy(expansion);
+ arena->drawing.setCacheLimit(expanded);
+}
+
+// What is this used for? It appears to allow one to get signals on entering/leaving elements which
+// is intend to be used by Inkview to change the cursor when over an anchor. However arena->active
+// appears to only be set to root.
+static gint
+sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
+{
+ Inkscape::DrawingItem *new_arena;
+ /* fixme: This sucks, we have to handle enter/leave notifiers */
+
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ if (!arena->cursor) {
+ if (arena->active) {
+ //g_warning ("Cursor entered to arena with already active item");
+ }
+ arena->cursor = TRUE;
+
+ /* TODO ... event -> arena transform? */
+ arena->c = Geom::Point(event->crossing.x, event->crossing.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ arena->drawing.update(Geom::IntRect::infinite(),
+ DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX, 0);
+ arena->active = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ ret = sp_canvas_arena_send_event (arena, event);
+ }
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ if (arena->cursor) {
+ ret = sp_canvas_arena_send_event (arena, event);
+ arena->active = nullptr;
+ arena->cursor = FALSE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ /* TODO ... event -> arena transform? */
+ arena->c = Geom::Point(event->motion.x, event->motion.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ arena->drawing.update(Geom::IntRect::infinite(),
+ DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX);
+ new_arena = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ if (new_arena != arena->active) {
+ GdkEventCrossing ec;
+ ec.window = event->motion.window;
+ ec.send_event = event->motion.send_event;
+ ec.subwindow = event->motion.window;
+ ec.time = event->motion.time;
+ ec.x = event->motion.x;
+ ec.y = event->motion.y;
+ /* fixme: */
+ if (arena->active) {
+ ec.type = GDK_LEAVE_NOTIFY;
+ ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ arena->active = new_arena;
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ ret = ret || sp_canvas_arena_send_event (arena, event);
+ break;
+
+ case GDK_SCROLL: {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
+ bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
+ if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
+ /* Zoom is emitted by the canvas as well, ignore here */
+ return FALSE;
+ }
+ ret = sp_canvas_arena_send_event (arena, event);
+ break;
+ }
+
+ default:
+ /* Just send event */
+ ret = sp_canvas_arena_send_event (arena, event);
+ break;
+ }
+
+ return ret;
+}
+
+static gint
+sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
+{
+ gint ret = FALSE;
+
+ /* Send event to arena */
+ g_signal_emit (G_OBJECT (arena), signals[ARENA_EVENT], 0, arena->active, event, &ret);
+
+ return ret;
+}
+
+static void
+sp_canvas_arena_request_update (SPCanvasArena *ca, DrawingItem */*item*/)
+{
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (ca));
+}
+
+static void sp_canvas_arena_request_render(SPCanvasArena *ca, Geom::IntRect const &area)
+{
+ SPCanvas *canvas = SP_CANVAS_ITEM(ca)->canvas;
+ canvas->requestRedraw(area.left(), area.top(), area.right(), area.bottom());
+}
+
+void
+sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: repick? */
+ ca->delta = delta;
+}
+
+void
+sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: repick? */
+ ca->sticky = sticky;
+}
+
+void
+sp_canvas_arena_render_surface (SPCanvasArena *ca, cairo_surface_t *surface, Geom::IntRect const &r)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ Inkscape::DrawingContext dc(surface, r.min());
+ ca->drawing.update(Geom::IntRect::infinite());
+ ca->drawing.render(dc, r);
+}
+
+/*
+ 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/display/canvas-arena.h b/src/display/canvas-arena.h
new file mode 100644
index 0000000..8d40964
--- /dev/null
+++ b/src/display/canvas-arena.h
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_ARENA_H
+#define SEEN_SP_CANVAS_ARENA_H
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/rect.h>
+
+#include "display/drawing.h"
+#include "display/drawing-item.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-item.h"
+
+#define SP_TYPE_CANVAS_ARENA (sp_canvas_arena_get_type ())
+#define SP_CANVAS_ARENA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CANVAS_ARENA, SPCanvasArena))
+#define SP_CANVAS_ARENA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_ARENA, SPCanvasArenaClass))
+#define SP_IS_CANVAS_ARENA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CANVAS_ARENA))
+#define SP_IS_CANVAS_ARENA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_ARENA))
+
+typedef struct _SPCanvasArena SPCanvasArena;
+typedef struct _SPCanvasArenaClass SPCanvasArenaClass;
+typedef struct _cairo_surface cairo_surface_t;
+struct CachePrefObserver;
+
+namespace Inkscape {
+
+class Drawing;
+class DrawingItem;
+
+} // namespace Inkscape
+
+struct _SPCanvasArena {
+ SPCanvasItem item;
+
+ guint cursor : 1;
+ guint sticky : 1;
+ Geom::Point c; // what is this?
+
+ Inkscape::Drawing drawing;
+ Inkscape::UpdateContext ctx;
+
+ Inkscape::DrawingItem *active;
+ /* fixme: */
+ Inkscape::DrawingItem *picked;
+ CachePrefObserver *observer;
+ double delta;
+};
+
+struct _SPCanvasArenaClass {
+ SPCanvasItemClass parent_class;
+
+ gint (* arena_event) (SPCanvasArena *carena, Inkscape::DrawingItem *item, GdkEvent *event);
+};
+
+GType sp_canvas_arena_get_type ();
+
+void sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta);
+void sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky);
+
+void sp_canvas_arena_render_surface (SPCanvasArena *ca, cairo_surface_t *surface, Geom::IntRect const &area);
+
+#endif // SEEN_SP_CANVAS_ARENA_H
diff --git a/src/display/canvas-axonomgrid.cpp b/src/display/canvas-axonomgrid.cpp
new file mode 100644
index 0000000..428f659
--- /dev/null
+++ b/src/display/canvas-axonomgrid.cpp
@@ -0,0 +1,758 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright (C) 2006-2012 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * Current limits are: one axis (y-axis) is always vertical. The other two
+ * axes are bound to a certain range of angles. The z-axis always has an angle
+ * smaller than 90 degrees (measured from horizontal, 0 degrees being a line extending
+ * to the right). The x-axis will always have an angle between 0 and 90 degrees.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <gtkmm/grid.h>
+
+#include <glibmm/i18n.h>
+
+#include "display/canvas-axonomgrid.h"
+
+#include "ui/widget/registered-widget.h"
+#include "desktop.h"
+
+#include "display/cairo-utils.h"
+#include "display/canvas-grid.h"
+#include "display/sp-canvas-util.h"
+#include "display/sp-canvas.h"
+#include "document.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "object/sp-namedview.h"
+#include "object/sp-object.h"
+#include "object/sp-root.h"
+#include "svg/svg-color.h"
+#include "2geom/line.h"
+#include "2geom/angle.h"
+#include "helper/mathfns.h"
+#include "util/units.h"
+
+using Inkscape::Util::unit_table;
+
+enum Dim3 { X=0, Y, Z };
+
+/**
+ * This function calls Cairo to render a line on a particular canvas buffer.
+ * Coordinates are interpreted as SCREENcoordinates
+ */
+static void
+sp_caxonomgrid_drawline (SPCanvasBuf *buf, gint x0, gint y0, gint x1, gint y1, guint32 rgba)
+{
+ cairo_move_to(buf->ct, 0.5 + x0, 0.5 + y0);
+ cairo_line_to(buf->ct, 0.5 + x1, 0.5 + y1);
+ ink_cairo_set_source_rgba32(buf->ct, rgba);
+ cairo_stroke(buf->ct);
+}
+
+static void
+sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)
+{
+ if ((x < buf->rect.left()) || (x >= buf->rect.right()))
+ return;
+
+ cairo_move_to(buf->ct, 0.5 + x, 0.5 + ys);
+ cairo_line_to(buf->ct, 0.5 + x, 0.5 + ye);
+ ink_cairo_set_source_rgba32(buf->ct, rgba);
+ cairo_stroke(buf->ct);
+}
+
+namespace Inkscape {
+
+
+CanvasAxonomGrid::CanvasAxonomGrid (SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument * in_doc)
+ : CanvasGrid(nv, in_repr, in_doc, GRID_AXONOMETRIC)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gridunit = unit_table.getUnit(prefs->getString("/options/grids/axonom/units"));
+ if (!gridunit) {
+ gridunit = unit_table.getUnit("px");
+ }
+ origin[Geom::X] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/origin_x", 0.0), gridunit, "px");
+ origin[Geom::Y] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/origin_y", 0.0), gridunit, "px");
+ color = prefs->getInt("/options/grids/axonom/color", GRID_DEFAULT_COLOR);
+ empcolor = prefs->getInt("/options/grids/axonom/empcolor", GRID_DEFAULT_EMPCOLOR);
+ empspacing = prefs->getInt("/options/grids/axonom/empspacing", 5);
+ lengthy = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/axonom/spacing_y", 1.0), gridunit, "px");
+ angle_deg[X] = prefs->getDouble("/options/grids/axonom/angle_x", 30.0);
+ angle_deg[Z] = prefs->getDouble("/options/grids/axonom/angle_z", 30.0);
+ angle_deg[Y] = 0;
+
+ angle_rad[X] = Geom::rad_from_deg(angle_deg[X]);
+ tan_angle[X] = tan(angle_rad[X]);
+ angle_rad[Z] = Geom::rad_from_deg(angle_deg[Z]);
+ tan_angle[Z] = tan(angle_rad[Z]);
+
+ snapper = new CanvasAxonomGridSnapper(this, &namedview->snap_manager, 0);
+
+ if (repr) readRepr();
+}
+
+CanvasAxonomGrid::~CanvasAxonomGrid ()
+{
+ if (snapper) delete snapper;
+}
+
+static gboolean sp_nv_read_opacity(gchar const *str, guint32 *color)
+{
+ if (!str) {
+ return FALSE;
+ }
+
+ gchar *u;
+ gdouble v = g_ascii_strtod(str, &u);
+ if (!u) {
+ return FALSE;
+ }
+ v = CLAMP(v, 0.0, 1.0);
+
+ *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
+
+ return TRUE;
+}
+
+
+
+void
+CanvasAxonomGrid::readRepr()
+{
+ SPRoot *root = doc->getRoot();
+ double scale_x = 1.0;
+ double scale_y = 1.0;
+ if( root->viewBox_set ) {
+ scale_x = root->width.computed / root->viewBox.width();
+ scale_y = root->height.computed / root->viewBox.height();
+ if (Geom::are_near(scale_x / scale_y, 1.0, Geom::EPSILON)) {
+ // scaling is uniform, try to reduce numerical error
+ scale_x = (scale_x + scale_y)/2.0;
+ double scale_none = Inkscape::Util::Quantity::convert(1, doc->getDisplayUnit(), "px");
+ if (Geom::are_near(scale_x / scale_none, 1.0, Geom::EPSILON))
+ scale_x = scale_none; // objects are same size, reduce numerical error
+ scale_y = scale_x;
+ }
+ }
+
+ gchar const *value;
+
+ if ( (value = repr->attribute("originx")) ) {
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ origin[Geom::X] = q.value("px");
+ } else {
+ // Grid in 'user units'
+ origin[Geom::X] = q.quantity * scale_x;
+ }
+ }
+
+ if ( (value = repr->attribute("originy")) ) {
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ origin[Geom::Y] = q.value("px");
+ } else {
+ // Grid in 'user units'
+ origin[Geom::Y] = q.quantity * scale_y;
+ }
+ }
+
+ if ( (value = repr->attribute("spacingy")) ) {
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ lengthy = q.value("px");
+ } else {
+ // Grid in 'user units'
+ lengthy = q.quantity * scale_y; // We do not handle scale_x != scale_y
+ }
+ if (lengthy < 0.0500) lengthy = 0.0500;
+ }
+
+ if ( (value = repr->attribute("gridanglex")) ) {
+ angle_deg[X] = g_ascii_strtod(value, nullptr);
+ if (angle_deg[X] < 0.) angle_deg[X] = 0.;
+ if (angle_deg[X] > 89.0) angle_deg[X] = 89.0;
+ angle_rad[X] = Geom::rad_from_deg(angle_deg[X]);
+ tan_angle[X] = tan(angle_rad[X]);
+ }
+
+ if ( (value = repr->attribute("gridanglez")) ) {
+ angle_deg[Z] = g_ascii_strtod(value, nullptr);
+ if (angle_deg[Z] < 0.) angle_deg[Z] = 0.;
+ if (angle_deg[Z] > 89.0) angle_deg[Z] = 89.0;
+ angle_rad[Z] = Geom::rad_from_deg(angle_deg[Z]);
+ tan_angle[Z] = tan(angle_rad[Z]);
+ }
+
+ if ( (value = repr->attribute("color")) ) {
+ color = (color & 0xff) | sp_svg_read_color(value, color);
+ }
+
+ if ( (value = repr->attribute("empcolor")) ) {
+ empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
+ }
+
+ if ( (value = repr->attribute("opacity")) ) {
+ sp_nv_read_opacity(value, &color);
+ }
+ if ( (value = repr->attribute("empopacity")) ) {
+ sp_nv_read_opacity(value, &empcolor);
+ }
+
+ if ( (value = repr->attribute("empspacing")) ) {
+ empspacing = atoi(value);
+ }
+
+ if ( (value = repr->attribute("visible")) ) {
+ visible = (strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("enabled")) ) {
+ g_assert(snapper != nullptr);
+ snapper->setEnabled(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("snapvisiblegridlinesonly")) ) {
+ g_assert(snapper != nullptr);
+ snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("units")) ) {
+ gridunit = unit_table.getUnit(value); // Display unit identifier in grid menu
+ }
+
+ for (auto i:canvasitems) {
+ sp_canvas_item_request_update(i);
+ }
+ return;
+}
+
+/**
+ * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
+ */
+void
+CanvasAxonomGrid::onReprAttrChanged(Inkscape::XML::Node */*repr*/, gchar const */*key*/, gchar const */*oldval*/, gchar const */*newval*/, bool /*is_interactive*/)
+{
+ readRepr();
+
+ if ( ! (_wr.isUpdating()) )
+ updateWidgets();
+}
+
+Gtk::Widget *
+CanvasAxonomGrid::newSpecificWidget()
+{
+ _rumg = Gtk::manage( new Inkscape::UI::Widget::RegisteredUnitMenu(
+ _("Grid _units:"), "units", _wr, repr, doc) );
+ _rsu_ox = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("_Origin X:"), _("X coordinate of grid origin"), "originx",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_x) );
+ _rsu_oy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("O_rigin Y:"), _("Y coordinate of grid origin"), "originy",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
+ _rsu_sy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("Spacing _Y:"), _("Base length of z-axis"), "spacingy",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
+ _rsu_ax = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalar(
+ _("Angle X:"), _("Angle of x-axis"), "gridanglex", _wr, repr, doc ) );
+ _rsu_az = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalar(
+ _("Angle Z:"), _("Angle of z-axis"), "gridanglez", _wr, repr, doc ) );
+
+ _rcp_gcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
+ _("Minor grid line _color:"), _("Minor grid line color"), _("Color of the minor grid lines"),
+ "color", "opacity", _wr, repr, doc));
+ _rcp_gmcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
+ _("Ma_jor grid line color:"), _("Major grid line color"),
+ _("Color of the major (highlighted) grid lines"),
+ "empcolor", "empopacity", _wr, repr, doc));
+
+ _rsi = Gtk::manage( new Inkscape::UI::Widget::RegisteredSuffixedInteger(
+ _("_Major grid line every:"), "", _("lines"), "empspacing", _wr, repr, doc ) );
+
+ _rumg->set_hexpand();
+ _rsu_ox->set_hexpand();
+ _rsu_oy->set_hexpand();
+ _rsu_sy->set_hexpand();
+ _rsu_ax->set_hexpand();
+ _rsu_az->set_hexpand();
+ _rcp_gcol->set_hexpand();
+ _rcp_gmcol->set_hexpand();
+ _rsi->set_hexpand();
+
+ // set widget values
+ _wr.setUpdating (true);
+
+ _rsu_ox->setDigits(5);
+ _rsu_ox->setIncrements(0.1, 1.0);
+
+ _rsu_oy->setDigits(5);
+ _rsu_oy->setIncrements(0.1, 1.0);
+
+ _rsu_sy->setDigits(5);
+ _rsu_sy->setIncrements(0.1, 1.0);
+
+ _rumg->setUnit (gridunit->abbr);
+
+ gdouble val;
+ val = origin[Geom::X];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_ox->setValue (val);
+ val = origin[Geom::Y];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_oy->setValue (val);
+ val = lengthy;
+ double gridy = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sy->setValue (gridy);
+
+ _rsu_ax->setValue(angle_deg[X]);
+ _rsu_az->setValue(angle_deg[Z]);
+
+ _rcp_gcol->setRgba32 (color);
+ _rcp_gmcol->setRgba32 (empcolor);
+ _rsi->setValue (empspacing);
+
+ _wr.setUpdating (false);
+
+ _rsu_ox->setProgrammatically = false;
+ _rsu_oy->setProgrammatically = false;
+
+ Gtk::Box *column = new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4);
+ column->pack_start(*_rumg, true, false);
+ column->pack_start(*_rsu_ox, true, false);
+ column->pack_start(*_rsu_oy, true, false);
+ column->pack_start(*_rsu_sy, true, false);
+ column->pack_start(*_rsu_ax, true, false);
+ column->pack_start(*_rsu_az, true, false);
+ column->pack_start(*_rcp_gcol, true, false);
+ column->pack_start(*_rcp_gmcol, true, false);
+ column->pack_start(*_rsi, true, false);
+
+ return column;
+}
+
+
+/**
+ * Update dialog widgets from object's values.
+ */
+void
+CanvasAxonomGrid::updateWidgets()
+{
+ if (_wr.isUpdating()) return;
+
+ //no widgets (grid created with the document, not with the dialog)
+ if (!_rcb_visible) return;
+
+ _wr.setUpdating (true);
+
+ _rcb_visible->setActive(visible);
+ if (snapper != nullptr) {
+ _rcb_enabled->setActive(snapper->getEnabled());
+ _rcb_snap_visible_only->setActive(snapper->getSnapVisibleOnly());
+ }
+
+ _rumg->setUnit (gridunit->abbr);
+
+ gdouble val;
+
+ val = origin[Geom::X];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_ox->setValue (val);
+
+ val = origin[Geom::Y];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_oy->setValue (val);
+
+ val = lengthy;
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sy->setValue (val);
+
+ _rsu_ax->setValue(angle_deg[X]);
+ _rsu_az->setValue(angle_deg[Z]);
+
+ _rsu_ox->setProgrammatically = false;
+ _rsu_oy->setProgrammatically = false;
+ _rsu_sy->setProgrammatically = false;
+ _rsu_ax->setProgrammatically = false;
+ _rsu_az->setProgrammatically = false;
+
+ _rcp_gcol->setRgba32 (color);
+ _rcp_gmcol->setRgba32 (empcolor);
+ _rsi->setValue (empspacing);
+
+ _wr.setUpdating (false);
+}
+
+
+
+void
+CanvasAxonomGrid::Update (Geom::Affine const &affine, unsigned int /*flags*/)
+{
+ ow = origin * affine;
+ sw = Geom::Point(fabs(affine[0]),fabs(affine[3]));
+ sw *= lengthy;
+
+ scaled = false;
+
+ for(int dim = 0; dim < 2; dim++) {
+ gint scaling_factor = empspacing;
+
+ if (scaling_factor <= 1)
+ scaling_factor = 5;
+
+ int watchdog = 0;
+ while ( (sw[dim] < 8.0) & (watchdog < 100) ) {
+ scaled = true;
+ sw[dim] *= scaling_factor;
+ // First pass, go up to the major line spacing, then
+ // keep increasing by two.
+ scaling_factor = 2;
+ watchdog++;
+ }
+
+ }
+
+ spacing_ylines = sw[Geom::X] /(tan_angle[X] + tan_angle[Z]);
+ lyw = sw[Geom::Y];
+ lxw_x = Geom::are_near(tan_angle[X],0.) ? Geom::infinity() : sw[Geom::X] / tan_angle[X];
+ lxw_z = Geom::are_near(tan_angle[Z],0.) ? Geom::infinity() : sw[Geom::X] / tan_angle[Z];
+
+ if (empspacing == 0) {
+ scaled = true;
+ }
+}
+
+void
+CanvasAxonomGrid::Render (SPCanvasBuf *buf)
+{
+ //set correct coloring, depending preference (when zoomed out, always major coloring or minor coloring)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint32 _empcolor;
+ guint32 _color = color;
+ bool preference = prefs->getBool("/options/grids/no_emphasize_when_zoomedout", false);
+ if( scaled && preference ) {
+ _empcolor = color;
+ } else {
+ _empcolor = empcolor;
+ }
+ bool xrayactive = prefs->getBool("/desktop/xrayactive", false);
+ if (xrayactive) { //this allow good looking on xray zones
+ guint32 bg = namedview->pagecolor;
+ _color = SP_RGBA32_F_COMPOSE(
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_R_F(_color)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_G_F(_color)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_B_F(_color)), 0.0, 1.0),
+ 1.0);
+ _empcolor = SP_RGBA32_F_COMPOSE(
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_R_F(_empcolor)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_G_F(_empcolor)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_B_F(_empcolor)), 0.0, 1.0),
+ 1.0);
+ }
+ cairo_save(buf->ct);
+ cairo_translate(buf->ct, -buf->rect.left(), -buf->rect.top());
+ cairo_set_line_width(buf->ct, 1.0);
+ cairo_set_line_cap(buf->ct, CAIRO_LINE_CAP_SQUARE);
+
+ // gc = gridcoordinates (the coordinates calculated from the grids origin 'grid->ow'.
+ // sc = screencoordinates ( for example "buf->rect.left()" is in screencoordinates )
+ // bc = buffer patch coordinates (x=0 on left side of page, y=0 on bottom of page)
+
+ // tl = topleft ; br = bottomright
+ Geom::Point buf_tl_gc;
+ Geom::Point buf_br_gc;
+ buf_tl_gc[Geom::X] = buf->rect.left() - ow[Geom::X];
+ buf_tl_gc[Geom::Y] = buf->rect.top() - ow[Geom::Y];
+ buf_br_gc[Geom::X] = buf->rect.right() - ow[Geom::X];
+ buf_br_gc[Geom::Y] = buf->rect.bottom() - ow[Geom::Y];
+
+ // render the three separate line groups representing the main-axes
+
+ // x-axis always goes from topleft to bottomright. (0,0) - (1,1)
+ gdouble const xintercept_y_bc = (buf_tl_gc[Geom::X] * tan_angle[X]) - buf_tl_gc[Geom::Y] ;
+ gdouble const xstart_y_sc = ( xintercept_y_bc - floor(xintercept_y_bc/lyw)*lyw ) + buf->rect.top();
+ gint const xlinestart = round( (xstart_y_sc - buf_tl_gc[Geom::X]*tan_angle[X] - ow[Geom::Y]) / lyw );
+ gint xlinenum = xlinestart;
+ // lines starting on left side.
+ for (gdouble y = xstart_y_sc; y < buf->rect.bottom(); y += lyw, xlinenum++) {
+ gint const x0 = buf->rect.left();
+ gint const y0 = round(y);
+ gint x1 = x0 + round( (buf->rect.bottom() - y) / tan_angle[X] );
+ gint y1 = buf->rect.bottom();
+ if ( Geom::are_near(tan_angle[X],0.) ) {
+ x1 = buf->rect.right();
+ y1 = y0;
+ }
+
+ if (!scaled && (xlinenum % empspacing) != 0) {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
+ } else {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
+ }
+ }
+ // lines starting from top side
+ if (!Geom::are_near(tan_angle[X],0.))
+ {
+ gdouble const xstart_x_sc = buf->rect.left() + (lxw_x - (xstart_y_sc - buf->rect.top()) / tan_angle[X]) ;
+ xlinenum = xlinestart-1;
+ for (gdouble x = xstart_x_sc; x < buf->rect.right(); x += lxw_x, xlinenum--) {
+ gint const y0 = buf->rect.top();
+ gint const y1 = buf->rect.bottom();
+ gint const x0 = round(x);
+ gint const x1 = x0 + round( (y1 - y0) / tan_angle[X] );
+
+ if (!scaled && (xlinenum % empspacing) != 0) {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
+ } else {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
+ }
+ }
+ }
+
+ // y-axis lines (vertical)
+ gdouble const ystart_x_sc = floor (buf_tl_gc[Geom::X] / spacing_ylines) * spacing_ylines + ow[Geom::X];
+ gint const ylinestart = round((ystart_x_sc - ow[Geom::X]) / spacing_ylines);
+ gint ylinenum = ylinestart;
+ for (gdouble x = ystart_x_sc; x < buf->rect.right(); x += spacing_ylines, ylinenum++) {
+ gint const x0 = floor(x); // sp_grid_vline will add 0.5 again, so we'll pre-emptively use floor()
+ // instead of round() to avoid biasing the vertical lines to the right by half a pixel; see
+ // CanvasXYGrid::Render() for more details
+
+ if (!scaled && (ylinenum % empspacing) != 0) {
+ sp_grid_vline (buf, x0, buf->rect.top(), buf->rect.bottom() - 1, _color);
+ } else {
+ sp_grid_vline (buf, x0, buf->rect.top(), buf->rect.bottom() - 1, _empcolor);
+ }
+ }
+
+ // z-axis always goes from bottomleft to topright. (0,1) - (1,0)
+ gdouble const zintercept_y_bc = (buf_tl_gc[Geom::X] * -tan_angle[Z]) - buf_tl_gc[Geom::Y] ;
+ gdouble const zstart_y_sc = ( zintercept_y_bc - floor(zintercept_y_bc/lyw)*lyw ) + buf->rect.top();
+ gint const zlinestart = round( (zstart_y_sc + buf_tl_gc[Geom::X]*tan_angle[Z] - ow[Geom::Y]) / lyw );
+ gint zlinenum = zlinestart;
+ // lines starting from left side
+ gdouble next_y = zstart_y_sc;
+ for (gdouble y = zstart_y_sc; y < buf->rect.bottom(); y += lyw, zlinenum++, next_y = y) {
+ gint const x0 = buf->rect.left();
+ gint const y0 = round(y);
+ gint x1 = x0 + round( (y - buf->rect.top() ) / tan_angle[Z] );
+ gint y1 = buf->rect.top();
+ if ( Geom::are_near(tan_angle[Z],0.) ) {
+ x1 = buf->rect.right();
+ y1 = y0;
+ }
+
+ if (!scaled && (zlinenum % empspacing) != 0) {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
+ } else {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
+ }
+ }
+ // draw lines from bottom-up
+ if (!Geom::are_near(tan_angle[Z],0.))
+ {
+ gdouble const zstart_x_sc = buf->rect.left() + (next_y - buf->rect.bottom()) / tan_angle[Z] ;
+ for (gdouble x = zstart_x_sc; x < buf->rect.right(); x += lxw_z, zlinenum++) {
+ gint const y0 = buf->rect.bottom();
+ gint const y1 = buf->rect.top();
+ gint const x0 = round(x);
+ gint const x1 = x0 + round(buf->rect.height() / tan_angle[Z] );
+
+ if (!scaled && (zlinenum % empspacing) != 0) {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _color);
+ } else {
+ sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, _empcolor);
+ }
+ }
+ }
+
+ cairo_restore(buf->ct);
+}
+
+CanvasAxonomGridSnapper::CanvasAxonomGridSnapper(CanvasAxonomGrid *grid, SnapManager *sm, Geom::Coord const d) : LineSnapper(sm, d)
+{
+ this->grid = grid;
+}
+
+/**
+ * \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels
+ */
+Geom::Coord CanvasAxonomGridSnapper::getSnapperTolerance() const
+{
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ double const zoom = dt ? dt->current_zoom() : 1;
+ return _snapmanager->snapprefs.getGridTolerance() / zoom;
+}
+
+bool CanvasAxonomGridSnapper::getSnapperAlwaysSnap() const
+{
+ return _snapmanager->snapprefs.getGridTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp
+}
+
+LineSnapper::LineList
+CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const
+{
+ LineList s;
+
+ if ( grid == nullptr ) {
+ return s;
+ }
+
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ double ta_x = grid->tan_angle[X];
+ double ta_z = grid->tan_angle[Z];
+
+ if (dt && dt->is_yaxisdown()) {
+ std::swap(ta_x, ta_z);
+ }
+
+ double spacing_h;
+ double spacing_v;
+
+ if (getSnapVisibleOnly()) {
+ // Only snapping to visible grid lines
+ spacing_h = grid->spacing_ylines; // this is the spacing of the visible grid lines measured in screen pixels
+ spacing_v = grid->lyw; // vertical
+ // convert screen pixels to px
+ // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
+ if (dt) {
+ spacing_h /= dt->current_zoom();
+ spacing_v /= dt->current_zoom();
+ }
+ } else {
+ // Snapping to any grid line, whether it's visible or not
+ spacing_h = grid->lengthy /(grid->tan_angle[X] + grid->tan_angle[Z]);
+ spacing_v = grid->lengthy;
+
+ }
+
+ // In an axonometric grid, any point will be surrounded by 6 grid lines:
+ // - 2 vertical grid lines, one left and one right from the point
+ // - 2 angled z grid lines, one above and one below the point
+ // - 2 angled x grid lines, one above and one below the point
+
+ // Calculate the x coordinate of the vertical grid lines
+ Geom::Coord x_max = Inkscape::Util::round_to_upper_multiple_plus(p[Geom::X], spacing_h, grid->origin[Geom::X]);
+ Geom::Coord x_min = Inkscape::Util::round_to_lower_multiple_plus(p[Geom::X], spacing_h, grid->origin[Geom::X]);
+
+ // Calculate the y coordinate of the intersection of the angled grid lines with the y-axis
+ double y_proj_along_z = p[Geom::Y] - ta_z * (p[Geom::X] - grid->origin[Geom::X]);
+ double y_proj_along_x = p[Geom::Y] + ta_x * (p[Geom::X] - grid->origin[Geom::X]);
+ double y_proj_along_z_max = Inkscape::Util::round_to_upper_multiple_plus(y_proj_along_z, spacing_v, grid->origin[Geom::Y]);
+ double y_proj_along_z_min = Inkscape::Util::round_to_lower_multiple_plus(y_proj_along_z, spacing_v, grid->origin[Geom::Y]);
+ double y_proj_along_x_max = Inkscape::Util::round_to_upper_multiple_plus(y_proj_along_x, spacing_v, grid->origin[Geom::Y]);
+ double y_proj_along_x_min = Inkscape::Util::round_to_lower_multiple_plus(y_proj_along_x, spacing_v, grid->origin[Geom::Y]);
+
+ // Calculate the versor for the angled grid lines
+ Geom::Point vers_x = Geom::Point(1, -ta_x);
+ Geom::Point vers_z = Geom::Point(1, ta_z);
+
+ // Calculate the normal for the angled grid lines
+ Geom::Point norm_x = Geom::rot90(vers_x);
+ Geom::Point norm_z = Geom::rot90(vers_z);
+
+ // The four angled grid lines form a parallelogram, enclosing the point
+ // One of the two vertical grid lines divides this parallelogram in two triangles
+ // We will now try to find out in which half (i.e. triangle) our point is, and return
+ // only the three grid lines defining that triangle
+
+ // The vertical grid line is at the intersection of two angled grid lines.
+ // Now go find that intersection!
+ Geom::Point p_x(0, y_proj_along_x_max);
+ Geom::Line line_x(p_x, p_x + vers_x);
+ Geom::Point p_z(0, y_proj_along_z_max);
+ Geom::Line line_z(p_z, p_z + vers_z);
+
+ Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default
+ try
+ {
+ inters = Geom::intersection(line_x, line_z);
+ }
+ catch (Geom::InfiniteSolutions &e)
+ {
+ // We're probably dealing with parallel lines; this is useless!
+ return s;
+ }
+
+ // Determine which half of the parallelogram to use
+ bool use_left_half = true;
+ bool use_right_half = true;
+
+ if (inters) {
+ Geom::Point inters_pt = line_x.pointAt((*inters).ta);
+ use_left_half = (p[Geom::X] - grid->origin[Geom::X]) < inters_pt[Geom::X];
+ use_right_half = !use_left_half;
+ }
+
+ // Return the three grid lines which define the triangle that encloses our point
+ // If we didn't find an intersection above, all 6 grid lines will be returned
+ if (use_left_half) {
+ s.push_back(std::make_pair(norm_z, Geom::Point(grid->origin[Geom::X], y_proj_along_z_max)));
+ s.push_back(std::make_pair(norm_x, Geom::Point(grid->origin[Geom::X], y_proj_along_x_min)));
+ s.push_back(std::make_pair(Geom::Point(1, 0), Geom::Point(x_max, 0)));
+ }
+
+ if (use_right_half) {
+ s.push_back(std::make_pair(norm_z, Geom::Point(grid->origin[Geom::X], y_proj_along_z_min)));
+ s.push_back(std::make_pair(norm_x, Geom::Point(grid->origin[Geom::X], y_proj_along_x_max)));
+ s.push_back(std::make_pair(Geom::Point(1, 0), Geom::Point(x_min, 0)));
+ }
+
+ return s;
+}
+
+void CanvasAxonomGridSnapper::_addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) const
+{
+ SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line);
+ isr.grid_lines.push_back(dummy);
+}
+
+void CanvasAxonomGridSnapper::_addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
+{
+ SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
+ isr.points.push_back(dummy);
+}
+
+void CanvasAxonomGridSnapper::_addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
+{
+ SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID_PERPENDICULAR, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
+ isr.points.push_back(dummy);
+}
+
+bool CanvasAxonomGridSnapper::ThisSnapperMightSnap() const
+{
+ return _snap_enabled && _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_GRID);
+}
+
+
+}; // 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/display/canvas-axonomgrid.h b/src/display/canvas-axonomgrid.h
new file mode 100644
index 0000000..ce4a19e
--- /dev/null
+++ b/src/display/canvas-axonomgrid.h
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef CANVAS_AXONOMGRID_H
+#define CANVAS_AXONOMGRID_H
+
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright (C) 2006-2012 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "line-snapper.h"
+#include "canvas-grid.h"
+
+struct SPCanvasBuf;
+class SPNamedView;
+
+namespace Inkscape {
+namespace XML {
+ class Node;
+};
+
+class CanvasAxonomGrid : public CanvasGrid {
+public:
+ CanvasAxonomGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument * in_doc);
+ ~CanvasAxonomGrid() override;
+
+ void Update (Geom::Affine const &affine, unsigned int flags) override;
+ void Render (SPCanvasBuf *buf) override;
+
+ void readRepr() override;
+ void onReprAttrChanged (Inkscape::XML::Node * repr, char const *key, char const *oldval, char const *newval, bool is_interactive) override;
+
+ double lengthy; /**< The lengths of the primary y-axis */
+ double angle_deg[3]; /**< Angle of each axis (note that angle[2] == 0) */
+ double angle_rad[3]; /**< Angle of each axis (note that angle[2] == 0) */
+ double tan_angle[3]; /**< tan(angle[.]) */
+
+ bool scaled; /**< Whether the grid is in scaled mode */
+
+protected:
+ friend class CanvasAxonomGridSnapper;
+
+ Geom::Point ow; /**< Transformed origin by the affine for the zoom */
+ double lyw; /**< Transformed length y by the affine for the zoom */
+ double lxw_x;
+ double lxw_z;
+ double spacing_ylines;
+
+ Geom::Point sw; /**< the scaling factors of the affine transform */
+
+ Gtk::Widget * newSpecificWidget() override;
+
+private:
+ CanvasAxonomGrid(const CanvasAxonomGrid&) = delete;
+ CanvasAxonomGrid& operator=(const CanvasAxonomGrid&) = delete;
+
+ void updateWidgets();
+
+ Inkscape::UI::Widget::RegisteredUnitMenu *_rumg;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_ox;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_oy;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_sy;
+ Inkscape::UI::Widget::RegisteredScalar *_rsu_ax;
+ Inkscape::UI::Widget::RegisteredScalar *_rsu_az;
+ Inkscape::UI::Widget::RegisteredColorPicker *_rcp_gcol;
+ Inkscape::UI::Widget::RegisteredColorPicker *_rcp_gmcol;
+ Inkscape::UI::Widget::RegisteredSuffixedInteger *_rsi;
+};
+
+
+
+class CanvasAxonomGridSnapper : public LineSnapper
+{
+public:
+ CanvasAxonomGridSnapper(CanvasAxonomGrid *grid, SnapManager *sm, Geom::Coord const d);
+ bool ThisSnapperMightSnap() const override;
+
+ Geom::Coord getSnapperTolerance() const override; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom)
+ bool getSnapperAlwaysSnap() const override; //if true, then the snapper will always snap, regardless of its tolerance
+
+private:
+ LineList _getSnapLines(Geom::Point const &p) const override;
+ void _addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, const Geom::Point &point_on_line) const override;
+ void _addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const override;
+ void _addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const override;
+
+ CanvasAxonomGrid *grid;
+};
+
+
+}; //namespace Inkscape
+
+
+
+#endif
+
+
diff --git a/src/display/canvas-bpath.cpp b/src/display/canvas-bpath.cpp
new file mode 100644
index 0000000..327bb1c
--- /dev/null
+++ b/src/display/canvas-bpath.cpp
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Simple bezier bpath CanvasItem for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include <sstream>
+#include <cstring>
+#include "desktop.h"
+
+#include "color.h"
+#include "display/sp-canvas-group.h"
+#include "display/sp-canvas-util.h"
+#include "display/canvas-bpath.h"
+#include "display/curve.h"
+#include "display/cairo-utils.h"
+#include "helper/geom.h"
+#include "display/sp-canvas.h"
+
+static void sp_canvas_bpath_destroy(SPCanvasItem *object);
+
+static void sp_canvas_bpath_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static double sp_canvas_bpath_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+
+G_DEFINE_TYPE(SPCanvasBPath, sp_canvas_bpath, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvas_bpath_class_init(SPCanvasBPathClass *klass)
+{
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ item_class->destroy = sp_canvas_bpath_destroy;
+ item_class->update = sp_canvas_bpath_update;
+ item_class->render = sp_canvas_bpath_render;
+ item_class->point = sp_canvas_bpath_point;
+}
+
+static void
+sp_canvas_bpath_init (SPCanvasBPath * bpath)
+{
+ bpath->fill_rgba = 0x00000000;
+ bpath->fill_rule = SP_WIND_RULE_EVENODD;
+
+ bpath->stroke_rgba = 0x00000000;
+ bpath->stroke_width = 1.0;
+ bpath->stroke_linejoin = SP_STROKE_LINEJOIN_MITER;
+ bpath->stroke_linecap = SP_STROKE_LINECAP_BUTT;
+ bpath->stroke_miterlimit = 11.0;
+ bpath->phantom_line = false;
+}
+
+static void sp_canvas_bpath_destroy(SPCanvasItem *object)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (object);
+
+ if (cbp->curve) {
+ cbp->curve = cbp->curve->unref();
+ }
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_bpath_parent_class)->destroy)
+ (* SP_CANVAS_ITEM_CLASS(sp_canvas_bpath_parent_class)->destroy) (object);
+}
+
+static void sp_canvas_bpath_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH(item);
+
+ item->canvas->requestRedraw((int)item->x1 - 1, (int)item->y1 - 1, (int)item->x2 + 1 , (int)item->y2 + 1);
+
+ if (reinterpret_cast<SPCanvasItemClass *>(sp_canvas_bpath_parent_class)->update) {
+ reinterpret_cast<SPCanvasItemClass *>(sp_canvas_bpath_parent_class)->update(item, affine, flags);
+ }
+
+ sp_canvas_item_reset_bounds (item);
+
+ if (!cbp->curve) return;
+
+ cbp->affine = affine;
+
+ Geom::OptRect bbox = bounds_exact_transformed(cbp->curve->get_pathvector(), affine);
+
+ if (bbox) {
+ item->x1 = (int)floor(bbox->min()[Geom::X]) - 1;
+ item->y1 = (int)floor(bbox->min()[Geom::Y]) - 1;
+ item->x2 = (int)ceil(bbox->max()[Geom::X]) + 1;
+ item->y2 = (int)ceil(bbox->max()[Geom::Y]) + 1;
+ } else {
+ item->x1 = 0;
+ item->y1 = 0;
+ item->x2 = 0;
+ item->y2 = 0;
+ }
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+}
+
+static void
+sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);
+
+ Geom::Rect area = buf->rect;
+
+ if ( !cbp->curve ||
+ ((cbp->stroke_rgba & 0xff) == 0 && (cbp->fill_rgba & 0xff) == 0 ) ||
+ cbp->curve->get_segment_count() < 1)
+ return;
+
+ if (!buf->ct)
+ return;
+
+ bool dofill = ((cbp->fill_rgba & 0xff) != 0);
+ bool dostroke = ((cbp->stroke_rgba & 0xff) != 0);
+
+ cairo_set_tolerance(buf->ct, 0.5);
+ cairo_new_path(buf->ct);
+
+ feed_pathvector_to_cairo (buf->ct, cbp->curve->get_pathvector(), cbp->affine, area,
+ /* optimized_stroke = */ !dofill, 1);
+
+ if (dofill) {
+ // RGB / BGR
+ ink_cairo_set_source_rgba32(buf->ct, cbp->fill_rgba);
+ cairo_set_fill_rule(buf->ct, cbp->fill_rule == SP_WIND_RULE_EVENODD? CAIRO_FILL_RULE_EVEN_ODD
+ : CAIRO_FILL_RULE_WINDING);
+ cairo_fill_preserve(buf->ct);
+ }
+
+ if (dostroke && cbp->phantom_line) {
+ ink_cairo_set_source_rgba32(buf->ct, 0xffffff4d);
+ cairo_set_line_width(buf->ct, 2);
+ if (cbp->dashes[0] != 0 && cbp->dashes[1] != 0) {
+ cairo_set_dash (buf->ct, cbp->dashes, 2, 0);
+ }
+ cairo_stroke(buf->ct);
+ cairo_set_tolerance(buf->ct, 0.5);
+ cairo_new_path(buf->ct);
+ feed_pathvector_to_cairo (buf->ct, cbp->curve->get_pathvector(), cbp->affine, area,
+ /* optimized_stroke = */ !dofill, 1);
+ ink_cairo_set_source_rgba32(buf->ct, cbp->stroke_rgba);
+ cairo_set_line_width(buf->ct, 1);
+ cairo_stroke(buf->ct);
+ } else if (dostroke) {
+ ink_cairo_set_source_rgba32(buf->ct, cbp->stroke_rgba);
+ cairo_set_line_width(buf->ct, 1);
+ if (cbp->dashes[0] != 0 && cbp->dashes[1] != 0) {
+ cairo_set_dash (buf->ct, cbp->dashes, 2, 0);
+ }
+ cairo_stroke(buf->ct);
+ } else {
+ cairo_new_path(buf->ct);
+ }
+}
+
+static double
+sp_canvas_bpath_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);
+
+ if ( !cbp->curve ||
+ ((cbp->stroke_rgba & 0xff) == 0 && (cbp->fill_rgba & 0xff) == 0 ) ||
+ cbp->curve->get_segment_count() < 1)
+ return Geom::infinity();
+
+ double width = 0.5;
+ Geom::Rect viewbox = item->canvas->getViewbox();
+ viewbox.expandBy (width);
+ double dist = Geom::infinity();
+ pathv_matrix_point_bbox_wind_distance(cbp->curve->get_pathvector(), cbp->affine, p, nullptr, nullptr, &dist, 0.5, &viewbox);
+
+ if (dist <= 1.0) {
+ *actual_item = item;
+ }
+
+ return dist;
+}
+
+SPCanvasItem *
+sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve, bool phantom_line)
+{
+ g_return_val_if_fail (parent != nullptr, NULL);
+ g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL);
+
+ SPCanvasItem *item = sp_canvas_item_new (parent, SP_TYPE_CANVAS_BPATH, nullptr);
+
+ sp_canvas_bpath_set_bpath (SP_CANVAS_BPATH (item), curve, phantom_line);
+
+ return item;
+}
+
+void
+sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve, bool phantom_line)
+{
+ g_return_if_fail (cbp != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));
+
+ cbp->phantom_line = phantom_line;
+ if (cbp->curve) {
+ cbp->curve = cbp->curve->unref();
+ }
+
+ if (curve) {
+ cbp->curve = curve->ref();
+ }
+
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
+}
+
+void
+sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule)
+{
+ g_return_if_fail (cbp != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));
+
+ cbp->fill_rgba = rgba;
+ cbp->fill_rule = rule;
+
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
+}
+
+void
+sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap, double dash, double gap)
+{
+ g_return_if_fail (cbp != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));
+
+ cbp->stroke_rgba = rgba;
+ cbp->stroke_width = MAX (width, 0.1);
+ cbp->stroke_linejoin = join;
+ cbp->stroke_linecap = cap;
+ cbp->dashes[0] = dash;
+ cbp->dashes[1] = gap;
+
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
+}
+
+/*
+ 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/display/canvas-bpath.h b/src/display/canvas-bpath.h
new file mode 100644
index 0000000..272c95b
--- /dev/null
+++ b/src/display/canvas-bpath.h
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_BPATH_H
+#define SEEN_SP_CANVAS_BPATH_H
+
+/*
+ * Simple bezier bpath CanvasItem for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ * Copyright (C) 2010 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include <cstdint>
+#include <glib.h>
+
+#include "sp-canvas-item.h"
+
+struct SPCanvasBPath;
+struct SPCanvasBPathClass;
+struct SPCanvasGroup;
+class SPCurve;
+
+#define SP_TYPE_CANVAS_BPATH (sp_canvas_bpath_get_type ())
+#define SP_CANVAS_BPATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CANVAS_BPATH, SPCanvasBPath))
+#define SP_CANVAS_BPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_BPATH, SPCanvasBPathClass))
+#define SP_IS_CANVAS_BPATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CANVAS_BPATH))
+#define SP_IS_CANVAS_BPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_BPATH))
+
+#define bpath_liv
+
+class Shape;
+
+/* stroke-linejoin */
+
+enum SPStrokeJoinType : std::uint_least8_t {
+ SP_STROKE_LINEJOIN_MITER,
+ SP_STROKE_LINEJOIN_ROUND,
+ SP_STROKE_LINEJOIN_BEVEL
+};
+
+/* stroke-linecap */
+
+enum SPStrokeCapType : std::uint_least8_t {
+ SP_STROKE_LINECAP_BUTT,
+ SP_STROKE_LINECAP_ROUND,
+ SP_STROKE_LINECAP_SQUARE
+};
+
+
+/* fill-rule */
+/* clip-rule */
+
+enum SPWindRule : std::uint_least8_t {
+ SP_WIND_RULE_NONZERO,
+ SP_WIND_RULE_INTERSECT,
+ SP_WIND_RULE_EVENODD,
+ SP_WIND_RULE_POSITIVE
+};
+
+
+struct SPCanvasBPath {
+ SPCanvasItem item;
+
+ /* Line def */
+ SPCurve *curve;
+ Geom::Affine affine;
+
+ /* Fill attributes */
+ guint32 fill_rgba;
+ SPWindRule fill_rule;
+
+ /* Line attributes */
+ guint32 stroke_rgba;
+ gdouble stroke_width;
+ gdouble dashes[2];
+ SPStrokeJoinType stroke_linejoin;
+ SPStrokeCapType stroke_linecap;
+ gdouble stroke_miterlimit;
+ bool phantom_line;
+ /* State */
+ Shape *fill_shp;
+ Shape *stroke_shp;
+};
+
+struct SPCanvasBPathClass {
+ SPCanvasItemClass parent_class;
+};
+
+GType sp_canvas_bpath_get_type ();
+
+SPCanvasItem *sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve, bool phantom_line = false);
+
+void sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve, bool phantom_line = false);
+void sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule);
+void sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap, double dash=0, double gap=0);
+
+#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/display/canvas-debug.cpp b/src/display/canvas-debug.cpp
new file mode 100644
index 0000000..03db61f
--- /dev/null
+++ b/src/display/canvas-debug.cpp
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A simple surface for debugging the canvas. Shows how tiles are drawn.
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "canvas-debug.h"
+#include "sp-canvas.h"
+#include "cairo-utils.h"
+#include "ui/event-debug.h"
+
+namespace {
+
+static void sp_canvas_debug_destroy(SPCanvasItem *item);
+static void sp_canvas_debug_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvas_debug_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static int sp_canvas_debug_event (SPCanvasItem *item, GdkEvent *event);
+
+} // namespace
+
+G_DEFINE_TYPE(SPCanvasDebug, sp_canvas_debug, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvas_debug_class_init (SPCanvasDebugClass *klass)
+{
+ klass->destroy = sp_canvas_debug_destroy;
+ klass->update = sp_canvas_debug_update;
+ klass->render = sp_canvas_debug_render;
+ klass->event = sp_canvas_debug_event;
+}
+
+static void sp_canvas_debug_init (SPCanvasDebug *debug)
+{
+ debug->pickable = true; // So we can receive events.
+}
+
+namespace {
+static void sp_canvas_debug_destroy (SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_DEBUG (object));
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_debug_parent_class)->destroy) {
+ SP_CANVAS_ITEM_CLASS(sp_canvas_debug_parent_class)->destroy(object);
+ }
+}
+
+static void sp_canvas_debug_update( SPCanvasItem *item, Geom::Affine const &/*affine*/, unsigned int /*flags*/ )
+{
+ // We cover the entire canvas
+ item->x1 = -G_MAXINT;
+ item->y1 = -G_MAXINT;
+ item->x2 = G_MAXINT;
+ item->y2 = G_MAXINT;
+}
+
+static void sp_canvas_debug_render( SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ if (!buf->ct) {
+ return;
+ }
+
+ cairo_set_line_width (buf->ct, 2);
+
+ // Draw box around buffer (for debugging)
+ cairo_new_path (buf->ct);
+ cairo_move_to (buf->ct, 0, 0);
+ cairo_line_to (buf->ct, buf->rect.width(), 0);
+ cairo_line_to (buf->ct, buf->rect.width(), buf->rect.height());
+ cairo_line_to (buf->ct, 0, buf->rect.height());
+ cairo_close_path (buf->ct);
+ ink_cairo_set_source_rgba32 (buf->ct, 0xff7f7f7f);
+ cairo_stroke (buf->ct);
+}
+
+static int sp_canvas_debug_event (SPCanvasItem *item, GdkEvent *event)
+{
+ ui_dump_event (event, Glib::ustring("sp_canvas_debug_event"));
+ return false; // We don't use any events...
+}
+
+} // namespace
+
+/*
+ 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/display/canvas-debug.h b/src/display/canvas-debug.h
new file mode 100644
index 0000000..19ccd15
--- /dev/null
+++ b/src/display/canvas-debug.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_DEBUG_H
+#define SEEN_SP_CANVAS_DEBUG_H
+
+/*
+ * A simple surface for debugging the canvas. Shows how tiles are drawn.
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-canvas-item.h"
+
+class SPItem;
+
+#define SP_TYPE_CANVAS_DEBUG (sp_canvas_debug_get_type ())
+#define SP_CANVAS_DEBUG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CANVAS_DEBUG, SPCanvasDebug))
+#define SP_IS_CANVAS_DEBUG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CANVAS_DEBUG))
+
+struct SPCanvasDebug : public SPCanvasItem {
+};
+
+GType sp_canvas_debug_get_type ();
+
+struct SPCanvasDebugClass : public SPCanvasItemClass{};
+
+#endif // SEEN_SP_CANVAS_DEBUG_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/display/canvas-grid.cpp b/src/display/canvas-grid.cpp
new file mode 100644
index 0000000..0148b46
--- /dev/null
+++ b/src/display/canvas-grid.cpp
@@ -0,0 +1,1143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Cartesian grid item for the Inkscape canvas.
+ *//*
+ * Authors:
+ * see git history
+ * Copyright (C) Johan Engelen 2006-2007 <johan@shouraizou.nl>
+ * Copyright (C) Lauris Kaplinski 2000
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ * Copyright (C) Tavmong Bah 2017 <tavmjong@free.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/* As a general comment, I am not exactly proud of how things are done.
+ * (for example the 'enable' widget and readRepr things)
+ * It does seem to work however. I intend to clean up and sort things out later, but that can take forever...
+ * Don't be shy to correct things.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <gtkmm/grid.h>
+
+#include <glibmm/i18n.h>
+
+#include "desktop.h"
+#include "sp-canvas-util.h"
+#include "helper/mathfns.h"
+
+#include "display/cairo-utils.h"
+#include "display/canvas-axonomgrid.h"
+#include "display/canvas-grid.h"
+#include "display/sp-canvas-group.h"
+#include "document.h"
+#include "util/units.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "object/sp-namedview.h"
+#include "object/sp-object.h"
+#include "object/sp-root.h"
+#include "svg/svg-color.h"
+#include "svg/stringstream.h"
+#include "helper/mathfns.h"
+#include "xml/node-event-vector.h"
+#include "verbs.h"
+#include "display/sp-canvas.h"
+
+using Inkscape::DocumentUndo;
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+
+static gchar const *const grid_name[] = {
+ N_("Rectangular grid"),
+ N_("Axonometric grid")
+};
+static gchar const *const grid_svgname[] = {
+ "xygrid",
+ "axonomgrid"
+};
+
+
+// ##########################################################
+// Grid CanvasItem
+static void grid_canvasitem_destroy(SPCanvasItem *object);
+static void grid_canvasitem_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void grid_canvasitem_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+G_DEFINE_TYPE(GridCanvasItem, grid_canvasitem, SP_TYPE_CANVAS_ITEM);
+
+static void grid_canvasitem_class_init(GridCanvasItemClass *klass)
+{
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ item_class->destroy = grid_canvasitem_destroy;
+ item_class->update = grid_canvasitem_update;
+ item_class->render = grid_canvasitem_render;
+}
+
+static void
+grid_canvasitem_init (GridCanvasItem *griditem)
+{
+ griditem->grid = nullptr;
+}
+
+static void grid_canvasitem_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (INKSCAPE_IS_GRID_CANVASITEM (object));
+
+ if (SP_CANVAS_ITEM_CLASS(grid_canvasitem_parent_class)->destroy)
+ (* SP_CANVAS_ITEM_CLASS(grid_canvasitem_parent_class)->destroy) (object);
+}
+
+/**
+*/
+static void
+grid_canvasitem_render (SPCanvasItem * item, SPCanvasBuf * buf)
+{
+ GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
+
+ if ( gridcanvasitem->grid && gridcanvasitem->grid->isVisible() ) {
+ sp_canvas_prepare_buffer (buf);
+ gridcanvasitem->grid->Render(buf);
+ }
+}
+
+static void
+grid_canvasitem_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ GridCanvasItem *gridcanvasitem = INKSCAPE_GRID_CANVASITEM (item);
+
+ if (SP_CANVAS_ITEM_CLASS(grid_canvasitem_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(grid_canvasitem_parent_class)->update(item, affine, flags);
+
+ if (gridcanvasitem->grid) {
+ gridcanvasitem->grid->Update(affine, flags);
+
+ item->canvas->requestRedraw(-1000000, -1000000,
+ 1000000, 1000000);
+
+ item->x1 = item->y1 = -1000000;
+ item->x2 = item->y2 = 1000000;
+ }
+}
+
+
+
+// ##########################################################
+// CanvasGrid
+
+ static Inkscape::XML::NodeEventVector const _repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ CanvasGrid::on_repr_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+ };
+
+CanvasGrid::CanvasGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument *in_doc, GridType type)
+ : visible(true), gridtype(type), legacy(false), pixel(false)
+{
+ repr = in_repr;
+ doc = in_doc;
+ if (repr) {
+ repr->addListener (&_repr_events, this);
+ }
+
+ namedview = nv;
+}
+
+CanvasGrid::~CanvasGrid()
+{
+ if (repr) {
+ repr->removeListenerByData (this);
+ }
+ for (auto i:canvasitems)
+ sp_canvas_item_destroy(i);
+ canvasitems.clear();
+}
+
+const char *
+CanvasGrid::getName() const
+{
+ return _(grid_name[gridtype]);
+}
+
+const char *
+CanvasGrid::getSVGName() const
+{
+ return grid_svgname[gridtype];
+}
+
+GridType
+CanvasGrid::getGridType() const
+{
+ return gridtype;
+}
+
+
+char const *
+CanvasGrid::getName(GridType type)
+{
+ return _(grid_name[type]);
+}
+
+char const *
+CanvasGrid::getSVGName(GridType type)
+{
+ return grid_svgname[type];
+}
+
+GridType
+CanvasGrid::getGridTypeFromSVGName(char const *typestr)
+{
+ if (!typestr) return GRID_RECTANGULAR;
+
+ gint t = 0;
+ for (t = GRID_MAXTYPENR; t >= 0; t--) { //this automatically defaults to grid0 which is rectangular grid
+ if (!strcmp(typestr, grid_svgname[t])) break;
+ }
+ return (GridType) t;
+}
+
+GridType
+CanvasGrid::getGridTypeFromName(char const *typestr)
+{
+ if (!typestr) return GRID_RECTANGULAR;
+
+ gint t = 0;
+ for (t = GRID_MAXTYPENR; t >= 0; t--) { //this automatically defaults to grid0 which is rectangular grid
+ if (!strcmp(typestr, _(grid_name[t]))) break;
+ }
+ return (GridType) t;
+}
+
+
+/*
+* writes an <inkscape:grid> child to repr.
+*/
+void
+CanvasGrid::writeNewGridToRepr(Inkscape::XML::Node * repr, SPDocument * doc, GridType gridtype)
+{
+ if (!repr) return;
+ if (gridtype > GRID_MAXTYPENR) return;
+
+ // first create the child xml node, then hook it to repr. This order is important, to not set off listeners to repr before the new node is complete.
+
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *newnode;
+ newnode = xml_doc->createElement("inkscape:grid");
+ newnode->setAttribute("type", getSVGName(gridtype));
+
+ repr->appendChild(newnode);
+ Inkscape::GC::release(newnode);
+
+ DocumentUndo::done(doc, SP_VERB_DIALOG_NAMEDVIEW, _("Create new grid"));
+}
+
+/*
+* Creates a new CanvasGrid object of type gridtype
+*/
+CanvasGrid*
+CanvasGrid::NewGrid(SPNamedView * nv, Inkscape::XML::Node * repr, SPDocument * doc, GridType gridtype)
+{
+ if (!repr) return nullptr;
+ if (!doc) {
+ g_error("CanvasGrid::NewGrid - doc==NULL");
+ return nullptr;
+ }
+
+ switch (gridtype) {
+ case GRID_RECTANGULAR:
+ return dynamic_cast<CanvasGrid*>(new CanvasXYGrid(nv, repr, doc));
+ case GRID_AXONOMETRIC:
+ return dynamic_cast<CanvasGrid*>(new CanvasAxonomGrid(nv, repr, doc));
+ }
+
+ return nullptr;
+}
+
+
+/**
+* creates a new grid canvasitem for the SPDesktop given as parameter. Keeps a link to this canvasitem in the canvasitems list.
+*/
+GridCanvasItem *
+CanvasGrid::createCanvasItem(SPDesktop * desktop)
+{
+ if (!desktop) return nullptr;
+// Johan: I think for multiple desktops it is best if each has their own canvasitem,
+// but share the same CanvasGrid object; that is what this function is for.
+
+ // check if there is already a canvasitem on this desktop linking to this grid
+ for (auto i:canvasitems) {
+ if ( desktop->getGridGroup() == SP_CANVAS_GROUP(i->parent) ) {
+ return nullptr;
+ }
+ }
+
+ GridCanvasItem * item = INKSCAPE_GRID_CANVASITEM( sp_canvas_item_new(desktop->getGridGroup(), INKSCAPE_TYPE_GRID_CANVASITEM, nullptr) );
+ item->grid = this;
+ sp_canvas_item_show(SP_CANVAS_ITEM(item));
+
+ g_object_ref(item); // since we're keeping a link to this item, we need to bump up the ref count
+ canvasitems.push_back(item);
+
+ return item;
+}
+
+Gtk::Widget *
+CanvasGrid::newWidget()
+{
+ Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
+ Gtk::Label * namelabel = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_CENTER) );
+
+ Glib::ustring str("<b>");
+ str += getName();
+ str += "</b>";
+ namelabel->set_markup(str);
+ vbox->pack_start(*namelabel, false, false);
+
+ _rcb_enabled = Gtk::manage( new Inkscape::UI::Widget::RegisteredCheckButton(
+ _("_Enabled"),
+ _("Makes the grid available for working with on the canvas."),
+ "enabled", _wr, false, repr, doc) );
+
+ _rcb_snap_visible_only = Gtk::manage( new Inkscape::UI::Widget::RegisteredCheckButton(
+ _("Snap to visible _grid lines only"),
+ _("When zoomed out, not all grid lines will be displayed. Only the visible ones will be snapped to"),
+ "snapvisiblegridlinesonly", _wr, false, repr, doc) );
+
+ _rcb_visible = Gtk::manage( new Inkscape::UI::Widget::RegisteredCheckButton(
+ _("_Visible"),
+ _("Determines whether the grid is displayed or not. Objects are still snapped to invisible grids."),
+ "visible", _wr, false, repr, doc) );
+
+ _as_alignment = Gtk::manage( new Inkscape::UI::Widget::AlignmentSelector() );
+ _as_alignment->on_alignmentClicked().connect(sigc::mem_fun(*this, &CanvasGrid::align_clicked));
+
+ Gtk::Box *left = new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4);
+ left->pack_start(*_rcb_enabled, false, false);
+ left->pack_start(*_rcb_visible, false, false);
+ left->pack_start(*_rcb_snap_visible_only, false, false);
+
+ if (getGridType() == GRID_RECTANGULAR) {
+ _rcb_dotted = Gtk::manage( new Inkscape::UI::Widget::RegisteredCheckButton(
+ _("_Show dots instead of lines"), _("If set, displays dots at gridpoints instead of gridlines"),
+ "dotted", _wr, false, repr, doc) );
+ _rcb_dotted->setActive(render_dotted);
+ left->pack_start(*_rcb_dotted, false, false);
+ }
+
+ left->pack_start(*Gtk::manage(new Gtk::Label(_("Align to page:"))), false, false);
+ left->pack_start(*_as_alignment, false, false);
+
+ auto right = newSpecificWidget();
+ right->set_hexpand(false);
+
+ Gtk::Box *inner = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
+ inner->pack_start(*left, true, true);
+ inner->pack_start(*right, false, false);
+ vbox->pack_start(*inner, false, false);
+ vbox->set_border_width(4);
+
+ std::list<Gtk::Widget*> slaves;
+ for (auto &item : left->get_children()) {
+ if (item != _rcb_enabled) {
+ slaves.push_back(item);
+ }
+ }
+ slaves.push_back(right);
+ _rcb_enabled->setSlaveWidgets(slaves);
+
+ // set widget values
+ _wr.setUpdating (true);
+ _rcb_visible->setActive(visible);
+ if (snapper != nullptr) {
+ _rcb_enabled->setActive(snapper->getEnabled());
+ _rcb_snap_visible_only->setActive(snapper->getSnapVisibleOnly());
+ }
+ _wr.setUpdating (false);
+ return dynamic_cast<Gtk::Widget *> (vbox);
+}
+
+void
+CanvasGrid::on_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar const *oldval, gchar const *newval, bool is_interactive, void *data)
+{
+ if (!data)
+ return;
+
+ (static_cast<CanvasGrid*>(data))->onReprAttrChanged(repr, key, oldval, newval, is_interactive);
+}
+
+bool CanvasGrid::isEnabled() const
+{
+ if (snapper == nullptr) {
+ return false;
+ }
+
+ return snapper->getEnabled();
+}
+
+// Used to shift origin when page size changed to fit drawing.
+void CanvasGrid::setOrigin(Geom::Point const &origin_px)
+{
+ SPRoot *root = doc->getRoot();
+ double scale_x = 1.0;
+ double scale_y = 1.0;
+ if( root->viewBox_set ) {
+ scale_x = root->viewBox.width() / root->width.computed;
+ scale_y = root->viewBox.height() / root->height.computed;
+ }
+
+ // Write out in 'user-units'
+ Inkscape::SVGOStringStream os_x, os_y;
+ os_x << origin_px[Geom::X] * scale_x;
+ os_y << origin_px[Geom::Y] * scale_y;
+ repr->setAttribute("originx", os_x.str());
+ repr->setAttribute("originy", os_y.str());
+}
+
+void CanvasGrid::align_clicked(int align)
+{
+ Geom::Point dimensions = doc->getDimensions();
+ dimensions[Geom::X] *= align % 3 * 0.5;
+ dimensions[Geom::Y] *= align / 3 * 0.5;
+ dimensions *= doc->doc2dt();
+ setOrigin(dimensions);
+}
+
+
+
+// ##########################################################
+// CanvasXYGrid
+
+CanvasXYGrid::CanvasXYGrid (SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument * in_doc)
+ : CanvasGrid(nv, in_repr, in_doc, GRID_RECTANGULAR)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gridunit = unit_table.getUnit(prefs->getString("/options/grids/xy/units"));
+ if (!gridunit) {
+ gridunit = unit_table.getUnit("px");
+ }
+ origin[Geom::X] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/xy/origin_x", 0.0), gridunit, "px");
+ origin[Geom::Y] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/xy/origin_y", 0.0), gridunit, "px");
+ color = prefs->getInt("/options/grids/xy/color", GRID_DEFAULT_COLOR);
+ empcolor = prefs->getInt("/options/grids/xy/empcolor", GRID_DEFAULT_EMPCOLOR);
+ empspacing = prefs->getInt("/options/grids/xy/empspacing", 5);
+ spacing[Geom::X] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/xy/spacing_x", 0.0), gridunit, "px");
+ spacing[Geom::Y] = Inkscape::Util::Quantity::convert(prefs->getDouble("/options/grids/xy/spacing_y", 0.0), gridunit, "px");
+ render_dotted = prefs->getBool("/options/grids/xy/dotted", false);
+
+ snapper = new CanvasXYGridSnapper(this, &namedview->snap_manager, 0);
+
+ if (repr) readRepr();
+}
+
+CanvasXYGrid::~CanvasXYGrid ()
+{
+ if (snapper) delete snapper;
+}
+
+static gboolean sp_nv_read_opacity(gchar const *str, guint32 *color)
+{
+ if (!str) {
+ return FALSE;
+ }
+
+ gchar *u;
+ gdouble v = g_ascii_strtod(str, &u);
+ if (!u) {
+ return FALSE;
+ }
+ v = CLAMP(v, 0.0, 1.0);
+
+ *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999);
+
+ return TRUE;
+}
+
+/** If the passed int is invalid (<=0), then set the widget and the int
+ to use the given old value.
+
+ @param oldVal Old value to use if the new one is invalid.
+ @param pTarget The int to validate.
+ @param widget Widget associated with the int.
+*/
+static void validateInt(gint oldVal,
+ gint* pTarget)
+{
+ // Avoid nullness.
+ if ( pTarget == nullptr )
+ return;
+
+ // Invalid new value?
+ if ( *pTarget <= 0 ) {
+ // If the old value is somehow invalid as well, then default to 1.
+ if ( oldVal <= 0 )
+ oldVal = 1;
+
+ // Reset the int and associated widget to the old value.
+ *pTarget = oldVal;
+ } //if
+
+} //validateInt
+
+void
+CanvasXYGrid::readRepr()
+{
+ SPRoot *root = doc->getRoot();
+ double scale_x = 1.0;
+ double scale_y = 1.0;
+ if( root->viewBox_set ) {
+ scale_x = root->width.computed / root->viewBox.width();
+ scale_y = root->height.computed / root->viewBox.height();
+ if (Geom::are_near(scale_x / scale_y, 1.0, Geom::EPSILON)) {
+ // scaling is uniform, try to reduce numerical error
+ scale_x = (scale_x + scale_y)/2.0;
+ double scale_none = Inkscape::Util::Quantity::convert(1, doc->getDisplayUnit(), "px");
+ if (Geom::are_near(scale_x / scale_none, 1.0, Geom::EPSILON))
+ scale_x = scale_none; // objects are same size, reduce numerical error
+ scale_y = scale_x;
+ }
+ }
+
+ gchar const *value;
+
+ if ( (value = repr->attribute("originx")) ) {
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ origin[Geom::X] = q.value("px");
+ legacy = true;
+ if (q.unit->abbr == "px" ) {
+ pixel = true;
+ }
+ } else {
+ // Grid in 'user units'
+ origin[Geom::X] = q.quantity * scale_x;
+ }
+ }
+
+ if ( (value = repr->attribute("originy")) ) {
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ origin[Geom::Y] = q.value("px");
+ legacy = true;
+ if (q.unit->abbr == "px" ) {
+ pixel = true;
+ }
+ } else {
+ // Grid in 'user units'
+ origin[Geom::Y] = q.quantity * scale_y;
+ }
+ }
+
+ if ( (value = repr->attribute("spacingx")) ) {
+
+ // Ensure a valid default value
+ if( spacing[Geom::X] <= 0.0 )
+ spacing[Geom::X] = 1.0;
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+ // Ensure a valid new value
+ if( q.quantity > 0 ) {
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ spacing[Geom::X] = q.value("px");
+ legacy = true;
+ if (q.unit->abbr == "px" ) {
+ pixel = true;
+ }
+ } else {
+ // Grid in 'user units'
+ spacing[Geom::X] = q.quantity * scale_x;
+ }
+ }
+ }
+
+ if ( (value = repr->attribute("spacingy")) ) {
+
+ // Ensure a valid default value
+ if( spacing[Geom::Y] <= 0.0 )
+ spacing[Geom::Y] = 1.0;
+
+ Inkscape::Util::Quantity q = unit_table.parseQuantity(value);
+ // Ensure a valid new value
+ if( q.quantity > 0 ) {
+ if( q.unit->type == UNIT_TYPE_LINEAR ) {
+ // Legacy grid not in 'user units'
+ spacing[Geom::Y] = q.value("px");
+ legacy = true;
+ if (q.unit->abbr == "px" ) {
+ pixel = true;
+ }
+ } else {
+ // Grid in 'user units'
+ spacing[Geom::Y] = q.quantity * scale_y;
+ }
+ }
+ }
+
+ if ( (value = repr->attribute("color")) ) {
+ color = (color & 0xff) | sp_svg_read_color(value, color);
+ }
+
+ if ( (value = repr->attribute("empcolor")) ) {
+ empcolor = (empcolor & 0xff) | sp_svg_read_color(value, empcolor);
+ }
+
+ if ( (value = repr->attribute("opacity")) ) {
+ sp_nv_read_opacity(value, &color);
+ }
+ if ( (value = repr->attribute("empopacity")) ) {
+ sp_nv_read_opacity(value, &empcolor);
+ }
+
+ if ( (value = repr->attribute("empspacing")) ) {
+ gint oldVal = empspacing;
+ empspacing = atoi(value);
+ validateInt( oldVal, &empspacing);
+ }
+
+ if ( (value = repr->attribute("dotted")) ) {
+ render_dotted = (strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("visible")) ) {
+ visible = (strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("enabled")) ) {
+ g_assert(snapper != nullptr);
+ snapper->setEnabled(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("snapvisiblegridlinesonly")) ) {
+ g_assert(snapper != nullptr);
+ snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0);
+ }
+
+ if ( (value = repr->attribute("units")) ) {
+ gridunit = unit_table.getUnit(value); // Display unit identifier in grid menu
+ }
+
+ for (auto i:canvasitems) {
+ sp_canvas_item_request_update(i);
+ }
+
+ return;
+}
+
+/**
+ * Called when XML node attribute changed; updates dialog widgets if change was not done by widgets themselves.
+ */
+void
+CanvasXYGrid::onReprAttrChanged(Inkscape::XML::Node */*repr*/, gchar const */*key*/, gchar const */*oldval*/, gchar const */*newval*/, bool /*is_interactive*/)
+{
+ readRepr();
+ updateWidgets();
+}
+
+
+Gtk::Widget *
+CanvasXYGrid::newSpecificWidget()
+{
+ _rumg = Gtk::manage( new Inkscape::UI::Widget::RegisteredUnitMenu(
+ _("Grid _units:"), "units", _wr, repr, doc) );
+ _rsu_ox = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("_Origin X:"), _("X coordinate of grid origin"), "originx",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_x) );
+ _rsu_oy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("O_rigin Y:"), _("Y coordinate of grid origin"), "originy",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
+ _rsu_sx = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("Spacing _X:"), _("Distance between vertical grid lines"), "spacingx",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_x) );
+ _rsu_sy = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalarUnit(
+ _("Spacing _Y:"), _("Distance between horizontal grid lines"), "spacingy",
+ *_rumg, _wr, repr, doc, Inkscape::UI::Widget::RSU_y) );
+
+ _rcp_gcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
+ _("Minor grid line _color:"), _("Minor grid line color"), _("Color of the minor grid lines"),
+ "color", "opacity", _wr, repr, doc) );
+
+ _rcp_gmcol = Gtk::manage( new Inkscape::UI::Widget::RegisteredColorPicker(
+ _("Ma_jor grid line color:"), _("Major grid line color"),
+ _("Color of the major (highlighted) grid lines"), "empcolor", "empopacity",
+ _wr, repr, doc) );
+
+ _rsi = Gtk::manage( new Inkscape::UI::Widget::RegisteredSuffixedInteger(
+ _("_Major grid line every:"), "", _("lines"), "empspacing", _wr, repr, doc) );
+
+ _rumg->set_hexpand();
+ _rsu_ox->set_hexpand();
+ _rsu_oy->set_hexpand();
+ _rsu_sx->set_hexpand();
+ _rsu_sy->set_hexpand();
+ _rcp_gcol->set_hexpand();
+ _rcp_gmcol->set_hexpand();
+ _rsi->set_hexpand();
+
+ // set widget values
+ _wr.setUpdating (true);
+
+ _rsu_ox->setDigits(5);
+ _rsu_ox->setIncrements(0.1, 1.0);
+
+ _rsu_oy->setDigits(5);
+ _rsu_oy->setIncrements(0.1, 1.0);
+
+ _rsu_sx->setDigits(5);
+ _rsu_sx->setIncrements(0.1, 1.0);
+
+ _rsu_sy->setDigits(5);
+ _rsu_sy->setIncrements(0.1, 1.0);
+
+ _rumg->setUnit (gridunit->abbr);
+
+ gdouble val;
+ val = origin[Geom::X];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_ox->setValue (val);
+ val = origin[Geom::Y];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_oy->setValue (val);
+ val = spacing[Geom::X];
+ double gridx = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sx->setValue (gridx);
+ val = spacing[Geom::Y];
+ double gridy = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sy->setValue (gridy);
+
+ _rcp_gcol->setRgba32 (color);
+ _rcp_gmcol->setRgba32 (empcolor);
+ _rsi->setValue (empspacing);
+
+ _wr.setUpdating (false);
+
+ _rsu_ox->setProgrammatically = false;
+ _rsu_oy->setProgrammatically = false;
+ _rsu_sx->setProgrammatically = false;
+ _rsu_sy->setProgrammatically = false;
+
+ Gtk::Box *column = new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4);
+ column->pack_start(*_rumg, true, false);
+ column->pack_start(*_rsu_ox, true, false);
+ column->pack_start(*_rsu_oy, true, false);
+ column->pack_start(*_rsu_sx, true, false);
+ column->pack_start(*_rsu_sy, true, false);
+ column->pack_start(*_rcp_gcol, true, false);
+ column->pack_start(*_rcp_gmcol, true, false);
+ column->pack_start(*_rsi, true, false);
+
+ return column;
+}
+
+
+/**
+ * Update dialog widgets from object's values.
+ */
+void
+CanvasXYGrid::updateWidgets()
+{
+ if (_wr.isUpdating()) return;
+
+ //no widgets (grid created with the document, not with the dialog)
+ if (!_rcb_visible) return;
+
+ _wr.setUpdating (true);
+
+ _rcb_visible->setActive(visible);
+ if (snapper != nullptr) {
+ _rcb_enabled->setActive(snapper->getEnabled());
+ _rcb_snap_visible_only->setActive(snapper->getSnapVisibleOnly());
+ }
+
+ _rumg->setUnit (gridunit->abbr);
+
+ gdouble val;
+
+ val = origin[Geom::X];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_ox->setValue (val);
+
+ val = origin[Geom::Y];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_oy->setValue (val);
+
+ val = spacing[Geom::X];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sx->setValue (val);
+
+ val = spacing[Geom::Y];
+ val = Inkscape::Util::Quantity::convert(val, "px", gridunit);
+ _rsu_sy->setValue (val);
+
+ _rsu_ox->setProgrammatically = false;
+ _rsu_oy->setProgrammatically = false;
+ _rsu_sx->setProgrammatically = false;
+ _rsu_sy->setProgrammatically = false;
+
+ _rcp_gcol->setRgba32 (color);
+ _rcp_gmcol->setRgba32 (empcolor);
+ _rsi->setValue (empspacing);
+ _rcb_dotted->setActive (render_dotted);
+
+ _wr.setUpdating (false);
+}
+
+// For correcting old SVG Inkscape files
+void
+CanvasXYGrid::Scale (Geom::Scale const &scale ) {
+ origin *= scale;
+ spacing *= scale;
+
+ // Write out in 'user-units'
+ Inkscape::SVGOStringStream os_x, os_y, ss_x, ss_y;
+ os_x << origin[Geom::X];
+ os_y << origin[Geom::Y];
+ ss_x << spacing[Geom::X];
+ ss_y << spacing[Geom::Y];
+ repr->setAttribute("originx", os_x.str());
+ repr->setAttribute("originy", os_y.str());
+ repr->setAttribute("spacingx", ss_x.str());
+ repr->setAttribute("spacingy", ss_y.str());
+}
+
+void
+CanvasXYGrid::Update (Geom::Affine const &affine, unsigned int /*flags*/)
+{
+ ow = origin * affine;
+ sw[0] = Geom::Point(spacing[0], 0) * affine.withoutTranslation();
+ sw[1] = Geom::Point(0, spacing[1]) * affine.withoutTranslation();
+
+ // Find suitable grid spacing for display
+ for(int dim = 0; dim < 2; dim++) {
+ gint scaling_factor = empspacing;
+
+ if (scaling_factor <= 1)
+ scaling_factor = 5;
+
+ scaled[dim] = false;
+ while (fabs(sw[dim].length()) < 8.0) {
+ scaled[dim] = true;
+ sw[dim] *= scaling_factor;
+ /* First pass, go up to the major line spacing, then
+ keep increasing by two. */
+ scaling_factor = 2;
+ }
+ }
+}
+
+
+// Find intersections of line with rectangle. There should be zero or two.
+// If line is degenerate with rectangle side, two corner points are returned.
+static std::vector<Geom::Point>
+intersect_line_rectangle( Geom::Line const &line, Geom::Rect const &rect )
+{
+ std::vector<Geom::Point> intersections;
+ for (unsigned i = 0; i < 4; ++i) {
+ Geom::LineSegment side( rect.corner(i), rect.corner((i+1)%4) );
+ try {
+ Geom::OptCrossing oc = Geom::intersection(line, side);
+ if (oc) {
+ intersections.push_back( line.pointAt((*oc).ta));
+ }
+ } catch (Geom::InfiniteSolutions) {
+ intersections.clear();
+ intersections.push_back( side.pointAt(0) );
+ intersections.push_back( side.pointAt(1) );
+ return intersections;
+ }
+ }
+ return intersections;
+}
+
+// Find the signed distance of a point to a line. The distance is negative if
+// the point lies to the left of the line considering the line's versor.
+static double
+signed_distance( Geom::Point const &point, Geom::Line const &line ) {
+ Geom::Coord t = line.nearestTime( point );
+ Geom::Point p = line.pointAt(t);
+ double distance = Geom::distance( p, point );
+ if ( Geom::cross( Geom::Line( p, point ).versor(), line.versor() ) < 0.0 ) {
+ distance = -distance;
+ }
+ return distance;
+}
+
+void
+CanvasXYGrid::Render (SPCanvasBuf *buf)
+{
+
+ // no_emphasize_when_zoomedout determines color (minor or major) when only major grid lines/dots shown.
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint32 _empcolor;
+ guint32 _color = color;
+ bool no_emp_when_zoomed_out = prefs->getBool("/options/grids/no_emphasize_when_zoomedout", false);
+ if( (scaled[Geom::X] || scaled[Geom::Y]) && no_emp_when_zoomed_out ) {
+ _empcolor = color;
+ } else {
+ _empcolor = empcolor;
+ }
+
+ bool xrayactive = prefs->getBool("/desktop/xrayactive", false);
+ if (xrayactive) {
+ guint32 bg = namedview->pagecolor;
+ _color = SP_RGBA32_F_COMPOSE(
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_R_F(_color)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_G_F(_color)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_color)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_color) * SP_RGBA32_B_F(_color)), 0.0, 1.0),
+ 1.0);
+ _empcolor = SP_RGBA32_F_COMPOSE(
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_R_F(_empcolor)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_G_F(_empcolor)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(_empcolor)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(_empcolor) * SP_RGBA32_B_F(_empcolor)), 0.0, 1.0),
+ 1.0);
+ }
+ cairo_save(buf->ct);
+ cairo_translate(buf->ct, -buf->rect.left(), -buf->rect.top());
+ cairo_set_line_width(buf->ct, 1.0);
+ cairo_set_line_cap(buf->ct, CAIRO_LINE_CAP_SQUARE);
+
+ // Adding a 2 px margin to the buffer rectangle to avoid missing intersections (in case of rounding errors, and due to adding 0.5 below)
+ Geom::IntRect buf_rect_with_margin = buf->rect;
+ buf_rect_with_margin.expandBy(2);
+
+ for (unsigned dim = 0; dim < 2; ++dim) {
+
+ // std::cout << "\n " << (dim==0?"Horizontal":"Vertical") << " ------------" << std::endl;
+
+ // Construct an axis line through origin with direction normal to grid spacing.
+ Geom::Line axis = Geom::Line::from_origin_and_vector( ow, sw[dim] );
+ Geom::Line orth = Geom::Line::from_origin_and_vector( ow, sw[(dim+1)%2] );
+
+ double spacing = sw[(dim+1)%2].length(); // Spacing between grid lines.
+ double dash = sw[dim].length(); // Total length of dash pattern.
+
+ // std::cout << " axis: " << axis.origin() << ", " << axis.vector() << std::endl;
+ // std::cout << " spacing: " << spacing << std::endl;
+ // std::cout << " dash period: " << dash << std::endl;
+
+ // Find the minimum and maximum distances of the buffer corners from axis.
+ double min = 1000000;
+ double max = -1000000;
+ for (unsigned c = 0; c < 4; ++c) {
+
+ // We need signed distance... lib2geom offers only positive distance.
+ double distance = signed_distance( buf_rect_with_margin.corner(c), axis );
+
+ // Correct it for coordinate flips (inverts handedness).
+ if (Geom::cross( axis.vector(), orth.vector() ) > 0 ) {
+ distance = -distance;
+ }
+
+ if (distance < min)
+ min = distance;
+ if (distance > max)
+ max = distance;
+ }
+ int start = floor( min/spacing );
+ int stop = floor( max/spacing );
+
+ // std::cout << " rect: " << buf->rect << std::endl;
+ // std::cout << " min: " << min << " max: " << max << std::endl;
+ // std::cout << " start: " << start << " stop: " << stop << std::endl;
+
+ // Loop over grid lines that intersected buf rectangle.
+ for (int j = start+1; j <= stop; ++j) {
+
+ Geom::Line grid_line = Geom::make_parallel_line( ow + j * sw[(dim+1)%2], axis );
+
+ std::vector<Geom::Point> x = intersect_line_rectangle( grid_line, buf_rect_with_margin );
+
+ // If we have two intersections, grid line intersects buffer rectangle.
+ if (x.size() == 2 ) {
+ // Make sure lines are always drawn in the same direction (or dashes misplaced).
+ Geom::Line vector( x[0], x[1]);
+ if (Geom::dot( vector.vector(), axis.vector() ) < 0.0) {
+ std::swap(x[0], x[1]);
+ }
+
+ // Set up line. Need to use floor()+0.5 such that Cairo will draw us lines with a width of a single pixel, without any aliasing.
+ // For this we need to position the lines at exactly half pixels, see https://www.cairographics.org/FAQ/#sharp_lines
+ // Must be consistent with the pixel alignment of the guide lines, see CanvasXYGrid::Render(), and the drawing of the rulers
+ cairo_move_to(buf->ct, floor(x[0][Geom::X]) + 0.5, floor(x[0][Geom::Y]) + 0.5);
+ cairo_line_to(buf->ct, floor(x[1][Geom::X]) + 0.5, floor(x[1][Geom::Y]) + 0.5);
+
+ // Set dash pattern and color.
+ if (render_dotted) {
+ // alpha needs to be larger than in the line case to maintain a similar
+ // visual impact but setting it to the maximal value makes the dots
+ // dominant in some cases. Solution, increase the alpha by a factor of
+ // 4. This then allows some user adjustment.
+ guint32 _empdot = (_empcolor & 0xff) << 2;
+ if (_empdot > 0xff)
+ _empdot = 0xff;
+ _empdot += (_empcolor & 0xffffff00);
+
+ guint32 _colordot = (_color & 0xff) << 2;
+ if (_colordot > 0xff)
+ _colordot = 0xff;
+ _colordot += (_color & 0xffffff00);
+
+ // Dash pattern must use spacing from orthogonal direction.
+ // Offset is to center dash on orthogonal lines.
+ double offset = fmod( signed_distance( x[0], orth ), sw[dim].length());
+ if (Geom::cross( axis.vector(), orth.vector() ) > 0 ) {
+ offset = -offset;
+ }
+
+ double dashes[2];
+ if (!scaled[dim] && (j % empspacing) != 0) {
+ // Minor lines
+ dashes[0] = 1;
+ dashes[1] = dash -1;
+ offset -= 0.5;
+ ink_cairo_set_source_rgba32(buf->ct, _colordot);
+ } else {
+ // Major lines
+ dashes[0] = 3;
+ dashes[1] = dash - 3;
+ offset -= 1.5; // Center dash on intersection.
+ ink_cairo_set_source_rgba32(buf->ct, _empdot);
+ }
+
+ cairo_set_line_cap(buf->ct, CAIRO_LINE_CAP_BUTT);
+ cairo_set_dash(buf->ct, dashes, 2, -offset);
+
+ } else {
+
+ // Solid lines
+
+ // Set color
+ if (!scaled[dim] && (j % empspacing) != 0) {
+ ink_cairo_set_source_rgba32(buf->ct, _color );
+ } else {
+ ink_cairo_set_source_rgba32(buf->ct, _empcolor );
+ }
+ }
+
+ cairo_stroke(buf->ct);
+
+ } else {
+ std::cerr << "CanvasXYGrid::render: Grid line doesn't intersect!" << std::endl;
+ }
+ }
+ }
+
+ cairo_restore(buf->ct);
+}
+
+CanvasXYGridSnapper::CanvasXYGridSnapper(CanvasXYGrid *grid, SnapManager *sm, Geom::Coord const d) : LineSnapper(sm, d)
+{
+ this->grid = grid;
+}
+
+/**
+ * \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels
+ */
+Geom::Coord CanvasXYGridSnapper::getSnapperTolerance() const
+{
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ double const zoom = dt ? dt->current_zoom() : 1;
+ return _snapmanager->snapprefs.getGridTolerance() / zoom;
+}
+
+bool CanvasXYGridSnapper::getSnapperAlwaysSnap() const
+{
+ return _snapmanager->snapprefs.getGridTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp
+}
+
+LineSnapper::LineList
+CanvasXYGridSnapper::_getSnapLines(Geom::Point const &p) const
+{
+ LineList s;
+
+ if ( grid == nullptr ) {
+ return s;
+ }
+
+ for (unsigned int i = 0; i < 2; ++i) {
+
+ double spacing;
+
+ if (getSnapVisibleOnly()) {
+ // Only snapping to visible grid lines
+ spacing = grid->sw[i].length(); // this is the spacing of the visible grid lines measured in screen pixels
+ // convert screen pixels to px
+ // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ if (dt) {
+ spacing /= dt->current_zoom();
+ }
+ } else {
+ // Snapping to any grid line, whether it's visible or not
+ spacing = grid->spacing[i];
+ }
+
+ Geom::Coord rounded;
+ Geom::Point point_on_line;
+ Geom::Point cvec(0.,0.);
+ cvec[i] = 1.;
+
+ rounded = Inkscape::Util::round_to_upper_multiple_plus(p[i], spacing, grid->origin[i]);
+ point_on_line = i ? Geom::Point(0, rounded) : Geom::Point(rounded, 0);
+ s.push_back(std::make_pair(cvec, point_on_line));
+
+ rounded = Inkscape::Util::round_to_lower_multiple_plus(p[i], spacing, grid->origin[i]);
+ point_on_line = i ? Geom::Point(0, rounded) : Geom::Point(rounded, 0);
+ s.push_back(std::make_pair(cvec, point_on_line));
+ }
+
+ return s;
+}
+
+void CanvasXYGridSnapper::_addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) const
+{
+ SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line);
+ isr.grid_lines.push_back(dummy);
+}
+
+void CanvasXYGridSnapper::_addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
+{
+ SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
+ isr.points.push_back(dummy);
+}
+
+void CanvasXYGridSnapper::_addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const
+{
+ SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID_PERPENDICULAR, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true);
+ isr.points.push_back(dummy);
+}
+
+/**
+ * \return true if this Snapper will snap at least one kind of point.
+ */
+bool CanvasXYGridSnapper::ThisSnapperMightSnap() const
+{
+ return _snap_enabled && _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_GRID);
+}
+
+} // 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/display/canvas-grid.h b/src/display/canvas-grid.h
new file mode 100644
index 0000000..73fe587
--- /dev/null
+++ b/src/display/canvas-grid.h
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Cartesian grid item for the Inkscape canvas.
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Copyright (C) Johan Engelen 2006-2007 <johan@shouraizou.nl>
+ * Copyright (C) Lauris Kaplinski 2000
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_CANVAS_GRID_H
+#define INKSCAPE_CANVAS_GRID_H
+
+#include "sp-canvas-item.h"
+#include "ui/widget/alignment-selector.h"
+#include "ui/widget/registered-widget.h"
+#include "ui/widget/registry.h"
+#include "line-snapper.h"
+
+class SPDesktop;
+class SPNamedView;
+struct SPCanvasBuf;
+class SPDocument;
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+class Snapper;
+
+namespace XML {
+class Node;
+}
+
+namespace Util {
+class Unit;
+}
+
+enum GridType {
+ GRID_RECTANGULAR = 0,
+ GRID_AXONOMETRIC = 1
+};
+#define GRID_MAXTYPENR 1
+
+#define INKSCAPE_TYPE_GRID_CANVASITEM (Inkscape::grid_canvasitem_get_type ())
+#define INKSCAPE_GRID_CANVASITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INKSCAPE_TYPE_GRID_CANVASITEM, GridCanvasItem))
+#define INKSCAPE_GRID_CANVASITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INKSCAPE_TYPE_GRID_CANVASITEM, GridCanvasItem))
+#define INKSCAPE_IS_GRID_CANVASITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INKSCAPE_TYPE_GRID_CANVASITEM))
+#define INKSCAPE_IS_GRID_CANVASITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INKSCAPE_TYPE_GRID_CANVASITEM))
+
+#define GRID_DEFAULT_COLOR 0x3F3FFF20
+#define GRID_DEFAULT_EMPCOLOR 0x3F3FFF40
+
+class CanvasGrid;
+
+/** All the variables that are tracked for a grid specific canvas item. */
+struct GridCanvasItem : public SPCanvasItem{
+ CanvasGrid *grid; // the owning grid object
+};
+
+struct GridCanvasItemClass {
+ SPCanvasItemClass parent_class;
+};
+
+/* Standard Gtk function */
+GType grid_canvasitem_get_type ();
+
+
+
+class CanvasGrid {
+public:
+ virtual ~CanvasGrid();
+
+ // TODO: see effect.h and effect.cpp from live_effects how to link enums to SVGname to typename properly. (johan)
+ const char * getName() const;
+ const char * getSVGName() const;
+ GridType getGridType() const;
+ static const char * getName(GridType type);
+ static const char * getSVGName(GridType type);
+ static GridType getGridTypeFromSVGName(const char * typestr);
+ static GridType getGridTypeFromName(const char * typestr);
+
+ static CanvasGrid* NewGrid(SPNamedView * nv, Inkscape::XML::Node * repr, SPDocument *doc, GridType gridtype);
+ static void writeNewGridToRepr(Inkscape::XML::Node * repr, SPDocument * doc, GridType gridtype);
+
+ GridCanvasItem * createCanvasItem(SPDesktop * desktop);
+
+ virtual void Update (Geom::Affine const &affine, unsigned int flags) = 0;
+ virtual void Render (SPCanvasBuf *buf) = 0;
+
+ virtual void readRepr() = 0;
+ virtual void onReprAttrChanged (Inkscape::XML::Node * /*repr*/, char const */*key*/, char const */*oldval*/, char const */*newval*/, bool /*is_interactive*/) = 0;
+
+ Gtk::Widget * newWidget();
+
+ void setOrigin(Geom::Point const &origin_px); /**< writes new origin (specified in px units) to SVG */
+ Geom::Point origin; /**< Origin of the grid */
+
+ guint32 color; /**< Color for normal lines */
+ guint32 empcolor; /**< Color for emphasis lines */
+ gint empspacing; /**< Spacing between emphasis lines */
+
+ Inkscape::Util::Unit const* gridunit; /**< points to Unit object in UnitTable (so don't delete it) */
+
+ Inkscape::XML::Node * repr;
+ SPDocument *doc;
+
+ Inkscape::Snapper* snapper;
+
+ static void on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data);
+
+ bool isLegacy() const { return legacy; }
+ bool isPixel() const { return pixel; }
+
+ bool isVisible() const { return (isEnabled() &&visible); };
+ bool isEnabled() const;
+
+ void align_clicked(int align);
+
+protected:
+ CanvasGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument *in_doc, GridType type);
+
+ virtual Gtk::Widget * newSpecificWidget() = 0;
+
+ std::vector<SPCanvasItem*> canvasitems; // list of created canvasitems
+
+ SPNamedView * namedview;
+
+ Inkscape::UI::Widget::Registry _wr;
+ bool visible;
+ bool render_dotted;
+
+ GridType gridtype;
+
+ // For dealing with old Inkscape SVG files (pre 0.92)
+ bool legacy;
+ bool pixel;
+
+ Inkscape::UI::Widget::RegisteredCheckButton *_rcb_enabled = nullptr;
+ Inkscape::UI::Widget::RegisteredCheckButton *_rcb_snap_visible_only = nullptr;
+ Inkscape::UI::Widget::RegisteredCheckButton *_rcb_visible = nullptr;
+ Inkscape::UI::Widget::RegisteredCheckButton *_rcb_dotted = nullptr;
+ Inkscape::UI::Widget::AlignmentSelector *_as_alignment = nullptr;
+
+private:
+ CanvasGrid(const CanvasGrid&) = delete;
+ CanvasGrid& operator=(const CanvasGrid&) = delete;
+};
+
+
+class CanvasXYGrid : public CanvasGrid {
+public:
+ CanvasXYGrid(SPNamedView * nv, Inkscape::XML::Node * in_repr, SPDocument * in_doc);
+ ~CanvasXYGrid() override;
+
+ virtual void Scale (Geom::Scale const &scale);
+ void Update (Geom::Affine const &affine, unsigned int flags) override;
+ void Render (SPCanvasBuf *buf) override;
+
+ void readRepr() override;
+ void onReprAttrChanged (Inkscape::XML::Node * repr, char const *key, char const *oldval, char const *newval, bool is_interactive) override;
+
+ Geom::Point spacing; /**< Spacing between elements of the grid */
+ bool scaled[2]; /**< Whether the grid is in scaled mode, which can
+ be different in the X or Y direction, hence two
+ variables */
+ Geom::Point ow; /**< Transformed origin by the affine for the zoom */
+ Geom::Point sw[2]; /**< Transformed spacing by the affine for the zoom */
+
+protected:
+ Gtk::Widget * newSpecificWidget() override;
+
+private:
+ CanvasXYGrid(const CanvasXYGrid&) = delete;
+ CanvasXYGrid& operator=(const CanvasXYGrid&) = delete;
+
+ void updateWidgets();
+
+ Inkscape::UI::Widget::RegisteredUnitMenu *_rumg = nullptr;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_ox = nullptr;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_oy = nullptr;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_sx = nullptr;
+ Inkscape::UI::Widget::RegisteredScalarUnit *_rsu_sy = nullptr;
+ Inkscape::UI::Widget::RegisteredColorPicker *_rcp_gcol = nullptr;
+ Inkscape::UI::Widget::RegisteredColorPicker *_rcp_gmcol = nullptr;
+ Inkscape::UI::Widget::RegisteredSuffixedInteger *_rsi = nullptr;
+};
+
+
+
+class CanvasXYGridSnapper : public LineSnapper
+{
+public:
+ CanvasXYGridSnapper(CanvasXYGrid *grid, SnapManager *sm, Geom::Coord const d);
+ bool ThisSnapperMightSnap() const override;
+
+ Geom::Coord getSnapperTolerance() const override; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom)
+ bool getSnapperAlwaysSnap() const override; //if true, then the snapper will always snap, regardless of its tolerance
+
+private:
+ LineList _getSnapLines(Geom::Point const &p) const override;
+ void _addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, const Geom::Point &point_on_line) const override;
+ void _addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const override;
+ void _addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const override;
+ CanvasXYGrid *grid;
+};
+
+}; /* namespace Inkscape */
+
+
+
+
+#endif
diff --git a/src/display/canvas-rotate.cpp b/src/display/canvas-rotate.cpp
new file mode 100644
index 0000000..0a5fcb5
--- /dev/null
+++ b/src/display/canvas-rotate.cpp
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Temporary surface for previewing rotated canvas.
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "inkscape.h"
+#include "desktop.h"
+
+#include "canvas-rotate.h"
+#include "sp-canvas.h"
+#include "cairo-utils.h"
+#include "ui/event-debug.h"
+
+#include "2geom/point.h"
+#include "2geom/rect.h"
+
+#include <gtk/gtk.h>
+
+namespace {
+
+static void sp_canvas_rotate_destroy(SPCanvasItem *item);
+static void sp_canvas_rotate_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvas_rotate_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static int sp_canvas_rotate_event (SPCanvasItem *item, GdkEvent *event);
+
+} // namespace
+
+void sp_canvas_rotate_paint (SPCanvasRotate *canvas_rotate, cairo_surface_t *background);
+
+G_DEFINE_TYPE(SPCanvasRotate, sp_canvas_rotate, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvas_rotate_class_init (SPCanvasRotateClass *klass)
+{
+ klass->destroy = sp_canvas_rotate_destroy;
+ //klass->update = sp_canvas_rotate_update;
+ //klass->render = sp_canvas_rotate_render;
+ klass->event = sp_canvas_rotate_event;
+}
+
+static void sp_canvas_rotate_init (SPCanvasRotate *rotate)
+{
+ rotate->pickable = true; // So we can receive events.
+ rotate->angle = 0.0;
+ rotate->start_angle = -1000;
+ rotate->surface_copy = nullptr;
+ rotate->surface_rotated = nullptr;
+}
+
+namespace {
+static void sp_canvas_rotate_destroy (SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ROTATE (object));
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_rotate_parent_class)->destroy) {
+ SP_CANVAS_ITEM_CLASS(sp_canvas_rotate_parent_class)->destroy(object);
+ }
+}
+
+// NOT USED... TOO SLOW
+static void sp_canvas_rotate_update( SPCanvasItem *item, Geom::Affine const &/*affine*/, unsigned int /*flags*/ )
+{
+ SPCanvasRotate *cr = SP_CANVAS_ROTATE(item);
+
+ if (cr->surface_copy == nullptr) {
+ // std::cout << "sp_canvas_rotate_update: surface_copy is NULL" << std::endl;
+ return;
+ }
+
+ // Destroy surface_rotated if it already exists.
+ if (cr->surface_rotated != nullptr) {
+ cairo_surface_destroy (cr->surface_rotated);
+ cr->surface_rotated = nullptr;
+ }
+
+ // Create rotated surface
+ cr->surface_rotated = ink_cairo_surface_create_identical(cr->surface_copy);
+ double width = cairo_image_surface_get_width (cr->surface_rotated);
+ double height = cairo_image_surface_get_height (cr->surface_rotated);
+ cairo_t *context = cairo_create( cr->surface_rotated );
+ cairo_set_operator( context, CAIRO_OPERATOR_SOURCE );
+ cairo_translate( context, width/2.0, height/2.0 );
+ cairo_rotate( context, Geom::rad_from_deg(-cr->angle) );
+ cairo_translate( context, -width/2.0, -height/2.0 );
+ cairo_set_source_surface( context, cr->surface_copy, 0, 0 );
+ cairo_paint( context );
+ cairo_destroy( context);
+
+ // We cover the entire canvas
+ item->x1 = -G_MAXINT;
+ item->y1 = -G_MAXINT;
+ item->x2 = G_MAXINT;
+ item->y2 = G_MAXINT;
+
+ item->canvas->requestRedraw(item->x1, item->y1, item->x2, item->y2);
+}
+
+// NOT USED... TOO SLOW
+static void sp_canvas_rotate_render( SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ // std::cout << "sp_canvas_rotate_render:" << std::endl;
+ // std::cout << " buf->rect: " << buf->rect << std::endl;
+ // std::cout << " buf->canvas_rect: " << buf->canvas_rect << std::endl;
+ SPCanvasRotate *cr = SP_CANVAS_ROTATE(item);
+ auto ct = buf->ct;
+
+ if (!ct) {
+ return;
+ }
+
+ if (cr->surface_rotated == nullptr ) {
+ // std::cout << " surface_rotated is NULL" << std::endl;
+ return;
+ }
+
+ // Draw rotated canvas
+ cairo_save (ct);
+ cairo_translate (ct,
+ buf->canvas_rect.left() - buf->rect.left(),
+ buf->canvas_rect.top() - buf->rect.top() );
+ cairo_set_operator (ct, CAIRO_OPERATOR_SOURCE );
+ cairo_set_source_surface (ct, cr->surface_rotated, 0, 0 );
+ cairo_paint (ct);
+ cairo_restore (ct);
+
+
+ // Draw line from center to cursor
+ cairo_save (ct);
+ cairo_translate (ct, -buf->rect.left(), -buf->rect.top());
+ cairo_new_path (ct);
+ cairo_move_to (ct, cr->center[Geom::X], cr->center[Geom::Y]);
+ cairo_rel_line_to (ct, cr->cursor[Geom::X], cr->cursor[Geom::Y]);
+ cairo_set_line_width (ct, 2);
+ ink_cairo_set_source_rgba32 (ct, 0xff00007f);
+ cairo_stroke (ct);
+ cairo_restore (ct);
+
+}
+
+static int sp_canvas_rotate_event (SPCanvasItem *item, GdkEvent *event)
+{
+ SPCanvasRotate *cr = SP_CANVAS_ROTATE(item);
+
+// ui_dump_event (event, Glib::ustring("sp_canvas_rotate_event"));
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ Geom::Rect viewbox = desktop->canvas->getViewbox(); // Not SVG viewbox!
+ cr->center = viewbox.midpoint();
+
+ switch (event->type) {
+ case GDK_MOTION_NOTIFY:
+ {
+ Geom::Point cursor( event->motion.x, event->motion.y );
+
+ // Both cursor and center are in window coordinates
+ Geom::Point rcursor( cursor - cr->center );
+ double angle = Geom::deg_from_rad( Geom::atan2(rcursor) );
+
+ // Set start angle
+ if (cr->start_angle < -360) {
+ cr->start_angle = angle;
+ }
+
+ double rotation_snap = 15;
+
+ double delta_angle = cr->start_angle - angle;
+
+ if (event->motion.state & GDK_SHIFT_MASK &&
+ event->motion.state & GDK_CONTROL_MASK) {
+ delta_angle = 0;
+ } else if (event->motion.state & GDK_SHIFT_MASK) {
+ delta_angle = round(delta_angle/rotation_snap) * rotation_snap;
+ } else if (event->motion.state & GDK_CONTROL_MASK) {
+ // ?
+ } else if (event->motion.state & GDK_MOD1_MASK) {
+ // Decimal raw angle
+ } else {
+ delta_angle = floor(delta_angle);
+ }
+
+ cr->angle = delta_angle;
+
+ // Correct line for snapping of angle
+ double distance = rcursor.length();
+ cr->cursor = Geom::Point::polar( Geom::rad_from_deg(angle), distance );
+
+ // Update screen
+ // sp_canvas_item_request_update( item );
+ sp_canvas_rotate_paint (cr, cr->canvas->_backing_store);
+ break;
+ }
+ case GDK_BUTTON_RELEASE:
+
+ // Rotate the actual canvas
+ desktop->rotate_relative_center_point (desktop->w2d(cr->center),
+ (desktop->w2d().det() > 0 ? -1 : 1) *
+ Geom::rad_from_deg(cr->angle) );
+
+ // We're done
+ sp_canvas_item_ungrab (item);
+ sp_canvas_item_hide (item);
+
+ cr->start_angle = -1000;
+ if (cr->surface_copy != nullptr) {
+ cairo_surface_destroy( cr->surface_copy );
+ cr->surface_copy = nullptr;
+ }
+ if (cr->surface_rotated != nullptr) {
+ cairo_surface_destroy( cr->surface_rotated );
+ cr->surface_rotated = nullptr;
+ }
+ // sp_canvas_item_show (desktop->drawing);
+
+ break;
+ case GDK_KEY_PRESS:
+ // std::cout << " Key press: " << std::endl;
+ break;
+ case GDK_KEY_RELEASE:
+ // std::cout << " Key release: " << std::endl;
+ break;
+ default:
+ // ui_dump_event (event, "sp_canvas_rotate_event: unwanted event: ");
+ break;
+ }
+
+ if (event->type == GDK_KEY_PRESS) return false;
+
+ return true;
+}
+
+} // namespace
+
+void sp_canvas_rotate_start (SPCanvasRotate *canvas_rotate, cairo_surface_t *background)
+{
+ if (background == nullptr) {
+ std::cerr << "sp_canvas_rotate_start: background is NULL!" << std::endl;
+ return;
+ }
+
+ canvas_rotate->angle = 0.0;
+
+ // Copy current background
+ canvas_rotate->surface_copy = ink_cairo_surface_copy( background );
+
+ // Paint canvas with background... since we are hiding drawing.
+ sp_canvas_item_request_update( canvas_rotate );
+}
+
+// Paint the canvas ourselves for speed....
+void sp_canvas_rotate_paint (SPCanvasRotate *canvas_rotate, cairo_surface_t *background)
+{
+ if (background == nullptr) {
+ std::cerr << "sp_canvas_rotate_paint: background is NULL!" << std::endl;
+ return;
+ }
+
+ double width = cairo_image_surface_get_width (background);
+ double height = cairo_image_surface_get_height (background);
+
+ // Draw rotated canvas
+ cairo_t *context = cairo_create( background );
+
+ cairo_save (context);
+ cairo_set_operator( context, CAIRO_OPERATOR_SOURCE );
+ cairo_translate( context, width/2.0, height/2.0 );
+ cairo_rotate( context, Geom::rad_from_deg(-canvas_rotate->angle) );
+ cairo_translate( context, -width/2.0, -height/2.0 );
+ cairo_set_source_surface( context, canvas_rotate->surface_copy, 0, 0 );
+ cairo_paint( context );
+ cairo_restore( context );
+ cairo_destroy( context );
+
+ gtk_widget_queue_draw (GTK_WIDGET (canvas_rotate->canvas));
+}
+/*
+ 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/display/canvas-rotate.h b/src/display/canvas-rotate.h
new file mode 100644
index 0000000..b45eae2
--- /dev/null
+++ b/src/display/canvas-rotate.h
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_ROTATE_H
+#define SEEN_SP_CANVAS_ROTATE_H
+
+/*
+ * Temporary surface for previewing rotated canvas.
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-canvas-item.h"
+#include "2geom/line.h"
+
+class SPItem;
+
+#define SP_TYPE_CANVAS_ROTATE (sp_canvas_rotate_get_type ())
+#define SP_CANVAS_ROTATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CANVAS_ROTATE, SPCanvasRotate))
+#define SP_IS_CANVAS_ROTATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CANVAS_ROTATE))
+
+struct SPCanvasRotate : public SPCanvasItem {
+ Geom::Point center; // Center of screen
+ Geom::Point cursor; // Position of cursor relative to center (after angle snapping)
+ double angle; // Rotation angle in degrees
+ double start_angle; // Starting angle of cursor
+ cairo_surface_t *surface_copy; // Copy of original surface
+ cairo_surface_t *surface_rotated; // Copy of original surface, rotated
+};
+
+void sp_canvas_rotate_start( SPCanvasRotate *canvas_rotate, cairo_surface_t *background );
+
+GType sp_canvas_rotate_get_type ();
+
+struct SPCanvasRotateClass : public SPCanvasItemClass{};
+
+#endif // SEEN_SP_CANVAS_ROTATE_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/display/canvas-temporary-item-list.cpp b/src/display/canvas-temporary-item-list.cpp
new file mode 100644
index 0000000..caa9609
--- /dev/null
+++ b/src/display/canvas-temporary-item-list.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Provides a class that can contain active TemporaryItem's on a desktop
+ * Code inspired by message-stack.cpp
+ *
+ * Authors:
+ * Johan Engelen
+ *
+ * Copyright (C) Johan Engelen 2008 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/canvas-temporary-item.h"
+#include "display/canvas-temporary-item-list.h"
+
+namespace Inkscape {
+namespace Display {
+
+TemporaryItemList::TemporaryItemList(SPDesktop *desktop)
+ : desktop(desktop)
+{
+
+}
+
+TemporaryItemList::~TemporaryItemList()
+{
+ // delete all items in list so the timeouts are removed
+ for (auto tempitem : itemlist) {
+ delete tempitem;
+ }
+ itemlist.clear();
+}
+
+/* Note that TemporaryItem or TemporaryItemList is responsible for deletion and such, so this return pointer can safely be ignored. */
+TemporaryItem *
+TemporaryItemList::add_item(SPCanvasItem *item, unsigned int lifetime)
+{
+ // beware of strange things happening due to very short timeouts
+ TemporaryItem * tempitem = new TemporaryItem(item, lifetime);
+ itemlist.push_back(tempitem);
+ tempitem->signal_timeout.connect( sigc::mem_fun(*this, &TemporaryItemList::_item_timeout) );
+ return tempitem;
+}
+
+void
+TemporaryItemList::delete_item( TemporaryItem * tempitem )
+{
+ // check if the item is in the list, if so, delete it. (in other words, don't wait for the item to delete itself)
+ bool in_list = false;
+ for (auto & it : itemlist) {
+ if ( it == tempitem ) {
+ in_list = true;
+ break;
+ }
+ }
+ if (in_list) {
+ itemlist.remove(tempitem);
+ delete tempitem;
+ }
+}
+
+void
+TemporaryItemList::_item_timeout(TemporaryItem * tempitem)
+{
+ itemlist.remove(tempitem);
+ // no need to delete the item, it does that itself after signal_timeout.emit() completes
+}
+
+} //namespace Display
+} /* 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/display/canvas-temporary-item-list.h b/src/display/canvas-temporary-item-list.h
new file mode 100644
index 0000000..4681c59
--- /dev/null
+++ b/src/display/canvas-temporary-item-list.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INKSCAPE_CANVAS_TEMPORARY_ITEM_LIST_H
+#define INKSCAPE_CANVAS_TEMPORARY_ITEM_LIST_H
+
+/*
+ * Authors:
+ * Johan Engelen
+ *
+ * Copyright (C) Johan Engelen 2008 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <list>
+
+struct SPCanvasItem;
+class SPDesktop;
+
+namespace Inkscape {
+namespace Display {
+
+class TemporaryItem;
+
+/**
+ * Provides a class that can contain active TemporaryItem[s] on a desktop.
+ */
+class TemporaryItemList {
+public:
+ TemporaryItemList(SPDesktop *desktop);
+ virtual ~TemporaryItemList();
+
+ TemporaryItem* add_item (SPCanvasItem *item, unsigned int lifetime);
+ void delete_item (TemporaryItem * tempitem);
+
+protected:
+ SPDesktop *desktop; /** Desktop we are on. */
+
+ std::list<TemporaryItem *> itemlist; /** list of temp items */
+
+ void _item_timeout (TemporaryItem * tempitem);
+
+private:
+ TemporaryItemList(const TemporaryItemList&) = delete;
+ TemporaryItemList& operator=(const TemporaryItemList&) = delete;
+};
+
+} //namespace Display
+} //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 :
diff --git a/src/display/canvas-temporary-item.cpp b/src/display/canvas-temporary-item.cpp
new file mode 100644
index 0000000..3826687
--- /dev/null
+++ b/src/display/canvas-temporary-item.cpp
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Provides a class that can contain active TemporaryItem's on a desktop
+ * When the object is deleted, it also deletes the canvasitem it contains!
+ * This object should be created/managed by a TemporaryItemList.
+ * After its lifetime, it fires the timeout signal, afterwards *it deletes itself*.
+ *
+ * (part of code inspired by message-stack.cpp)
+ *
+ * Authors:
+ * Johan Engelen
+ *
+ * Copyright (C) Johan Engelen 2008 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/canvas-temporary-item.h"
+
+#include <glib.h>
+#include "display/sp-canvas-item.h"
+
+namespace Inkscape {
+namespace Display {
+
+/** lifetime is measured in milliseconds
+ */
+TemporaryItem::TemporaryItem(SPCanvasItem *item, guint lifetime, bool deselect_destroy)
+ : canvasitem(item),
+ timeout_id(0),
+ destroy_on_deselect(deselect_destroy)
+{
+ if (lifetime > 0 && destroy_on_deselect) {
+ g_print ("Warning: lifetime should be 0 when destroy_on_deselect is true\n");
+ lifetime = 0;
+ }
+ // zero lifetime means stay forever, so do not add timeout event.
+ if (lifetime > 0) {
+ timeout_id = g_timeout_add(lifetime, &TemporaryItem::_timeout, this);
+ }
+}
+
+TemporaryItem::~TemporaryItem()
+{
+ // when it has not expired yet...
+ if (timeout_id) {
+ g_source_remove(timeout_id);
+ timeout_id = 0;
+ }
+
+ if (canvasitem) {
+ // destroying the item automatically hides it
+ sp_canvas_item_destroy(canvasitem);
+ canvasitem = nullptr;
+ }
+}
+
+/* static method */
+int TemporaryItem::_timeout(void* data) {
+ TemporaryItem *tempitem = static_cast<TemporaryItem *>(data);
+ tempitem->timeout_id = 0;
+ tempitem->signal_timeout.emit(tempitem);
+ delete tempitem;
+ return FALSE;
+}
+
+
+} //namespace Display
+} /* 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/display/canvas-temporary-item.h b/src/display/canvas-temporary-item.h
new file mode 100644
index 0000000..4e60e43
--- /dev/null
+++ b/src/display/canvas-temporary-item.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INKSCAPE_CANVAS_TEMPORARY_ITEM_H
+#define INKSCAPE_CANVAS_TEMPORARY_ITEM_H
+
+/*
+ * Authors:
+ * Johan Engelen
+ *
+ * Copyright (C) Johan Engelen 2008 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <sigc++/signal.h>
+
+struct SPCanvasItem;
+
+namespace Inkscape {
+namespace Display {
+
+/**
+ * Provides a class to put a canvasitem temporarily on-canvas.
+ */
+class TemporaryItem {
+public:
+ TemporaryItem(SPCanvasItem *item, unsigned int lifetime, bool destroy_on_deselect = false);
+ virtual ~TemporaryItem();
+
+ TemporaryItem(const TemporaryItem&) = delete;
+ TemporaryItem& operator=(const TemporaryItem&) = delete;
+
+ sigc::signal<void, TemporaryItem *> signal_timeout;
+
+protected:
+ friend class TemporaryItemList;
+
+ SPCanvasItem * canvasitem; /** The item we are holding on to */
+ unsigned int timeout_id; /** ID by which glib knows the timeout event */
+ bool destroy_on_deselect; // only destroy when parent item is deselected, not when mouse leaves
+
+ static int _timeout(void* data); ///< callback for when lifetime expired
+};
+
+} //namespace Display
+} //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 :
diff --git a/src/display/canvas-text.cpp b/src/display/canvas-text.cpp
new file mode 100644
index 0000000..e725a1f
--- /dev/null
+++ b/src/display/canvas-text.cpp
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Canvas text
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ * Copyright (C) 2008 Maximilian Albert
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <sstream>
+#include <cstring>
+
+#include "sp-canvas-util.h"
+#include "canvas-text.h"
+#include "display/cairo-utils.h"
+#include "desktop.h"
+#include "display/sp-canvas.h"
+
+static void sp_canvastext_destroy(SPCanvasItem *object);
+
+static void sp_canvastext_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvastext_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+G_DEFINE_TYPE(SPCanvasText, sp_canvastext, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvastext_class_init(SPCanvasTextClass *klass)
+{
+ SPCanvasItemClass *item_class = SP_CANVAS_ITEM_CLASS(klass);
+
+ item_class->destroy = sp_canvastext_destroy;
+ item_class->update = sp_canvastext_update;
+ item_class->render = sp_canvastext_render;
+}
+
+static void
+sp_canvastext_init (SPCanvasText *canvastext)
+{
+ canvastext->anchor_position = TEXT_ANCHOR_CENTER;
+ canvastext->anchor_pos_x_manual = 0;
+ canvastext->anchor_pos_y_manual = 0;
+ canvastext->anchor_offset_x = 0;
+ canvastext->anchor_offset_y = 0;
+ canvastext->rgba = 0x33337fff;
+ canvastext->rgba_stroke = 0xffffffff;
+ canvastext->rgba_background = 0x0000007f;
+ canvastext->background = false;
+ canvastext->s[Geom::X] = canvastext->s[Geom::Y] = 0.0;
+ canvastext->affine = Geom::identity();
+ canvastext->fontsize = 10.0;
+ canvastext->item = nullptr;
+ canvastext->desktop = nullptr;
+ canvastext->text = nullptr;
+ canvastext->outline = false;
+ canvastext->background = false;
+ canvastext->border = 3; // must be a constant, and not proportional to any width, height, or fontsize to allow alignment with other text boxes
+}
+
+static void sp_canvastext_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CANVASTEXT (object));
+
+ SPCanvasText *canvastext = SP_CANVASTEXT (object);
+
+ g_free(canvastext->text);
+ canvastext->text = nullptr;
+ canvastext->item = nullptr;
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvastext_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_canvastext_parent_class)->destroy(object);
+}
+
+static void
+sp_canvastext_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCanvasText *cl = SP_CANVASTEXT (item);
+
+ if (!buf->ct)
+ return;
+
+ cairo_select_font_face(buf->ct, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(buf->ct, cl->fontsize);
+
+ if (cl->background){
+ cairo_text_extents_t extents;
+ cairo_text_extents(buf->ct, cl->text, &extents);
+
+ cairo_rectangle(buf->ct, item->x1 - buf->rect.left(),
+ item->y1 - buf->rect.top(),
+ item->x2 - item->x1,
+ item->y2 - item->y1);
+
+ ink_cairo_set_source_rgba32(buf->ct, cl->rgba_background);
+ cairo_fill(buf->ct);
+ }
+
+ Geom::Point s = cl->s * cl->affine;
+ double offsetx = s[Geom::X] - cl->anchor_offset_x - buf->rect.left();
+ double offsety = s[Geom::Y] - cl->anchor_offset_y - buf->rect.top();
+
+ cairo_move_to(buf->ct, round(offsetx), round(offsety));
+ cairo_text_path(buf->ct, cl->text);
+
+ if (cl->outline){
+ ink_cairo_set_source_rgba32(buf->ct, cl->rgba_stroke);
+ cairo_set_line_width (buf->ct, 2.0);
+ cairo_stroke_preserve(buf->ct);
+ }
+ ink_cairo_set_source_rgba32(buf->ct, cl->rgba);
+ cairo_fill(buf->ct);
+}
+
+static void
+sp_canvastext_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCanvasText *cl = SP_CANVASTEXT (item);
+
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvastext_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(sp_canvastext_parent_class)->update(item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ cl->affine = affine;
+
+ Geom::Point s = cl->s * affine;
+ // Point s specifies the position of the anchor, which is at the bounding box of the text itself (i.e. not at the border of the filled background rectangle)
+ // The relative position of the anchor can be set using e.g. anchor_position = TEXT_ANCHOR_LEFT
+
+ // Set up a temporary cairo_t to measure the text extents; it would be better to compute this in the render()
+ // method but update() seems to be called before so we don't have the information available when we need it
+ cairo_surface_t *tmp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ cairo_t* tmp_buf = cairo_create(tmp_surface);
+
+ cairo_select_font_face(tmp_buf, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(tmp_buf, cl->fontsize);
+ cairo_text_extents_t extents;
+ cairo_text_extents(tmp_buf, cl->text, &extents);
+ double border = cl->border;
+
+ item->x1 = s[Geom::X] + extents.x_bearing - border;
+ item->y1 = s[Geom::Y] + extents.y_bearing - border;
+ item->x2 = item->x1 + extents.width + 2*border;
+ item->y2 = item->y1 + extents.height + 2*border;
+
+ /* FROM: http://lists.cairographics.org/archives/cairo-bugs/2009-March/003014.html
+ - Glyph surfaces: In most font rendering systems, glyph surfaces
+ have an origin at (0,0) and a bounding box that is typically
+ represented as (x_bearing,y_bearing,width,height). Depending on
+ which way y progresses in the system, y_bearing may typically be
+ negative (for systems similar to cairo, with origin at top left),
+ or be positive (in systems like PDF with origin at bottom left).
+ No matter which is the case, it is important to note that
+ (x_bearing,y_bearing) is the coordinates of top-left of the glyph
+ relative to the glyph origin. That is, for example:
+
+ Scaled-glyph space:
+
+ (x_bearing,y_bearing) <-- negative numbers
+ +----------------+
+ | . |
+ | . |
+ |......(0,0) <---|-- glyph origin
+ | |
+ | |
+ +----------------+
+ (width+x_bearing,height+y_bearing)
+
+ Note the similarity of the origin to the device space. That is
+ exactly how we use the device_offset to represent scaled glyphs:
+ to use the device-space origin as the glyph origin.
+ */
+
+ // adjust update region according to anchor shift
+
+
+ switch (cl->anchor_position){
+ case TEXT_ANCHOR_LEFT:
+ cl->anchor_offset_x = 0;
+ cl->anchor_offset_y = -extents.height/2;
+ break;
+ case TEXT_ANCHOR_RIGHT:
+ cl->anchor_offset_x = extents.width;
+ cl->anchor_offset_y = -extents.height/2;
+ break;
+ case TEXT_ANCHOR_BOTTOM:
+ cl->anchor_offset_x = extents.width/2;
+ cl->anchor_offset_y = 0;
+ break;
+ case TEXT_ANCHOR_TOP:
+ cl->anchor_offset_x = extents.width/2;
+ cl->anchor_offset_y = -extents.height;
+ break;
+ case TEXT_ANCHOR_ZERO:
+ cl->anchor_offset_x = 0;
+ cl->anchor_offset_y = 0;
+ break;
+ case TEXT_ANCHOR_MANUAL:
+ cl->anchor_offset_x = (1 + cl->anchor_pos_x_manual) * extents.width/2;
+ cl->anchor_offset_y = -(1 + cl->anchor_pos_y_manual) * extents.height/2;
+ break;
+ case TEXT_ANCHOR_CENTER:
+ default:
+ cl->anchor_offset_x = extents.width/2;
+ cl->anchor_offset_y = -extents.height/2;
+ break;
+ }
+
+ cl->anchor_offset_x += extents.x_bearing;
+ cl->anchor_offset_y += extents.height + extents.y_bearing;
+
+ item->x1 -= cl->anchor_offset_x;
+ item->x2 -= cl->anchor_offset_x;
+ item->y1 -= cl->anchor_offset_y;
+ item->y2 -= cl->anchor_offset_y;
+
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+}
+
+SPCanvasText *sp_canvastext_new(SPCanvasGroup *parent, SPDesktop *desktop, Geom::Point pos, gchar const *new_text)
+{
+ // Pos specifies the position of the anchor, which is at the bounding box of the text itself (i.e. not at the border of the filled background rectangle)
+ // The relative position of the anchor can be set using e.g. anchor_position = TEXT_ANCHOR_LEFT
+ SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_CANVASTEXT, nullptr);
+
+ SPCanvasText *ct = SP_CANVASTEXT(item);
+
+ ct->desktop = desktop;
+
+ ct->s = pos;
+ g_free(ct->text);
+ ct->text = g_strdup(new_text);
+
+ return ct;
+}
+
+
+void
+sp_canvastext_set_rgba32 (SPCanvasText *ct, guint32 rgba, guint32 rgba_stroke)
+{
+ g_return_if_fail (ct != nullptr);
+ g_return_if_fail (SP_IS_CANVASTEXT (ct));
+
+ if (rgba != ct->rgba || rgba_stroke != ct->rgba_stroke) {
+ ct->rgba = rgba;
+ ct->rgba_stroke = rgba_stroke;
+ SPCanvasItem *item = SP_CANVAS_ITEM (ct);
+ sp_canvas_item_request_update( item );
+ }
+}
+
+#define EPSILON 1e-6
+#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON)
+
+void
+sp_canvastext_set_coords (SPCanvasText *ct, gdouble x0, gdouble y0)
+{
+ sp_canvastext_set_coords(ct, Geom::Point(x0, y0));
+}
+
+void
+sp_canvastext_set_coords (SPCanvasText *ct, const Geom::Point start)
+{
+ g_return_if_fail (ct && ct->desktop);
+ g_return_if_fail (SP_IS_CANVASTEXT (ct));
+
+ Geom::Point pos = ct->desktop->doc2dt(start);
+
+ if (DIFFER (pos[0], ct->s[Geom::X]) || DIFFER (pos[1], ct->s[Geom::Y])) {
+ ct->s[Geom::X] = pos[0];
+ ct->s[Geom::Y] = pos[1];
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (ct));
+ }
+}
+
+void
+sp_canvastext_set_text (SPCanvasText *ct, gchar const * new_text)
+{
+ g_free (ct->text);
+ ct->text = g_strdup(new_text);
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (ct));
+}
+
+void
+sp_canvastext_set_number_as_text (SPCanvasText *ct, int num)
+{
+ std::ostringstream number;
+ number << num;
+ sp_canvastext_set_text(ct, number.str().c_str());
+}
+
+void
+sp_canvastext_set_fontsize (SPCanvasText *ct, double size)
+{
+ ct->fontsize = size;
+}
+
+void
+sp_canvastext_set_anchor_manually (SPCanvasText *ct, double anchor_x, double anchor_y)
+{
+ ct->anchor_pos_x_manual = anchor_x;
+ ct->anchor_pos_y_manual = anchor_y;
+ ct->anchor_position = TEXT_ANCHOR_MANUAL;
+}
+
+
+/*
+ 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/display/canvas-text.h b/src/display/canvas-text.h
new file mode 100644
index 0000000..257f360
--- /dev/null
+++ b/src/display/canvas-text.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVASTEXT_H
+#define SEEN_SP_CANVASTEXT_H
+
+/*
+ * Canvas text.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ * Copyright (C) 2008 Maximilian Albert
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-canvas-item.h"
+
+class SPItem;
+class SPDesktop;
+
+#define SP_TYPE_CANVASTEXT (sp_canvastext_get_type ())
+#define SP_CANVASTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CANVASTEXT, SPCanvasText))
+#define SP_IS_CANVASTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CANVASTEXT))
+
+enum CanvasTextAnchorPositionEnum {
+ TEXT_ANCHOR_CENTER,
+ TEXT_ANCHOR_TOP,
+ TEXT_ANCHOR_BOTTOM,
+ TEXT_ANCHOR_LEFT,
+ TEXT_ANCHOR_RIGHT,
+ TEXT_ANCHOR_ZERO,
+ TEXT_ANCHOR_MANUAL
+};
+
+struct SPCanvasText : public SPCanvasItem {
+ SPItem *item; // the item to which this line belongs in some sense; may be NULL for some users
+ guint32 rgba;
+ guint32 rgba_stroke;
+ guint32 rgba_background;
+ bool outline;
+ bool background;
+ double border;
+
+ SPDesktop *desktop; // the desktop to which this text is attached; needed for coordinate transforms (TODO: these should be eliminated)
+
+ gchar* text;
+ Geom::Point s; // Is the coordinate of the anchor, which is related to the rectangle filled with the background color
+ CanvasTextAnchorPositionEnum anchor_position; // The anchor position specifies where the anchor is with respect to the rectangle filled with the background color
+ double anchor_pos_x_manual;
+ double anchor_pos_y_manual;
+ Geom::Affine affine;
+ double fontsize;
+ double anchor_offset_x;
+ double anchor_offset_y;
+};
+struct SPCanvasTextClass : public SPCanvasItemClass{};
+
+GType sp_canvastext_get_type ();
+
+SPCanvasText *sp_canvastext_new(SPCanvasGroup *parent, SPDesktop *desktop, Geom::Point pos, gchar const *text);
+
+void sp_canvastext_set_rgba32 (SPCanvasText *ct, guint32 rgba, guint32 rgba_stroke);
+void sp_canvastext_set_coords (SPCanvasText *ct, gdouble x0, gdouble y0);
+void sp_canvastext_set_coords (SPCanvasText *ct, const Geom::Point start);
+void sp_canvastext_set_text (SPCanvasText *ct, gchar const* new_text);
+void sp_canvastext_set_number_as_text (SPCanvasText *ct, int num);
+void sp_canvastext_set_fontsize (SPCanvasText *ct, double size);
+void sp_canvastext_set_anchor_manually (SPCanvasText *ct, double anchor_x, double anchor_y);
+
+#endif // SEEN_SP_CANVASTEXT_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/display/curve.cpp b/src/display/curve.cpp
new file mode 100644
index 0000000..3c46fb7
--- /dev/null
+++ b/src/display/curve.cpp
@@ -0,0 +1,713 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Johan Engelen
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/curve.h"
+
+#include <glib.h>
+#include <2geom/pathvector.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/point.h>
+
+#include <utility>
+
+/**
+ * Routines for SPCurve and for its Geom::PathVector
+ */
+
+/* Constructors */
+
+/**
+ * The returned curve's state is as if SPCurve::reset has just been called on it.
+ */
+SPCurve::SPCurve()
+ : _refcount(1),
+ _pathv()
+{}
+
+SPCurve::SPCurve(Geom::PathVector pathv)
+ : _refcount(1),
+ _pathv(std::move(pathv))
+{}
+
+//concat constructor
+SPCurve::SPCurve(std::list<SPCurve *> const& l) : _refcount(1)
+{
+ for (auto c:l) {
+ _pathv.insert( _pathv.end(), c->get_pathvector().begin(), c->get_pathvector().end() );
+ }
+}
+
+
+SPCurve *
+SPCurve::new_from_rect(Geom::Rect const &rect, bool all_four_sides)
+{
+ SPCurve *c = new SPCurve();
+
+ Geom::Point p = rect.corner(0);
+ c->moveto(p);
+
+ for (int i=3; i>=1; --i) {
+ c->lineto(rect.corner(i));
+ }
+
+ if (all_four_sides) {
+ // When _constrained_ snapping to a path, the 2geom::SimpleCrosser will be invoked which doesn't consider the closing segment.
+ // of a path. Consequently, in case we want to snap to for example the page border, we must provide all four sides of the
+ // rectangle explicitly
+ c->lineto(rect.corner(0));
+ } else {
+ // ... instead of just three plus a closing segment
+ c->closepath();
+ }
+
+ return c;
+}
+
+SPCurve::~SPCurve()
+= default;
+
+/* Methods */
+
+void
+SPCurve::set_pathvector(Geom::PathVector const & new_pathv)
+{
+ _pathv = new_pathv;
+}
+
+Geom::PathVector const &
+SPCurve::get_pathvector() const
+{
+ return _pathv;
+}
+
+/*
+ * Returns the number of segments of all paths summed
+ * This count includes the closing line segment of a closed path.
+ */
+size_t
+SPCurve::get_segment_count() const
+{
+ return _pathv.curveCount();
+}
+
+/**
+ * Increase _refcount of curve.
+ *
+ * \todo should this be shared with other refcounting code?
+ */
+SPCurve *
+SPCurve::ref()
+{
+ _refcount += 1;
+
+ return this;
+}
+
+/**
+ * Decrease refcount of curve, with possible destruction.
+ *
+ * \todo should this be shared with other refcounting code?
+ */
+SPCurve *
+SPCurve::unref()
+{
+ _refcount -= 1;
+
+ if (_refcount < 1) {
+ delete this;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Create new curve from this curve's pathvector array.
+ */
+SPCurve *
+SPCurve::copy() const
+{
+ return new SPCurve(_pathv);
+}
+
+/**
+ * Return new curve that is the concatenation of all curves in list.
+ */
+SPCurve *
+SPCurve::concat(std::list<SPCurve *> l){
+ return new SPCurve(l);
+};
+
+/**
+ * Returns a list of new curves corresponding to the subpaths in \a curve.
+ * 2geomified
+ */
+std::list<SPCurve *>
+SPCurve::split() const
+{
+ std::list<SPCurve *> l;
+
+ for (const auto & path_it : _pathv) {
+ Geom::PathVector newpathv;
+ newpathv.push_back(path_it);
+ SPCurve * newcurve = new SPCurve(newpathv);
+ l.push_back(newcurve);
+ }
+
+ return l;
+}
+
+/**
+ * Transform all paths in curve using matrix.
+ */
+void
+SPCurve::transform(Geom::Affine const &m)
+{
+ _pathv *= m;
+}
+
+/**
+ * Set curve to empty curve.
+ * In more detail: this clears the internal pathvector from all its paths.
+ */
+void
+SPCurve::reset()
+{
+ _pathv.clear();
+}
+
+/** Several consecutive movetos are ALLOWED
+ * Ref: http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
+ * (first subitem of the item about zero-length path segments) */
+
+/**
+ * Calls SPCurve::moveto() with point made of given coordinates.
+ */
+void
+SPCurve::moveto(double x, double y)
+{
+ moveto(Geom::Point(x, y));
+}
+/**
+ * Perform a moveto to a point, thus starting a new subpath.
+ * Point p must be finite.
+ */
+void
+SPCurve::moveto(Geom::Point const &p)
+{
+ Geom::Path path(p);
+ path.setStitching(true);
+ _pathv.push_back(path);
+}
+
+/**
+ * Adds a line to the current subpath.
+ * Point p must be finite.
+ */
+void
+SPCurve::lineto(Geom::Point const &p)
+{
+ if (_pathv.empty()) g_message("SPCurve::lineto - path is empty!");
+ else _pathv.back().appendNew<Geom::LineSegment>( p );
+}
+/**
+ * Calls SPCurve::lineto( Geom::Point(x,y) )
+ */
+void
+SPCurve::lineto(double x, double y)
+{
+ lineto(Geom::Point(x,y));
+}
+
+/**
+ * Adds a quadratic bezier segment to the current subpath.
+ * All points must be finite.
+ */
+void
+SPCurve::quadto(Geom::Point const &p1, Geom::Point const &p2)
+{
+ if (_pathv.empty()) g_message("SPCurve::quadto - path is empty!");
+ else _pathv.back().appendNew<Geom::QuadraticBezier>( p1, p2);
+}
+/**
+ * Calls SPCurve::quadto( Geom::Point(x1,y1), Geom::Point(x2,y2) )
+ * All coordinates must be finite.
+ */
+void
+SPCurve::quadto(double x1, double y1, double x2, double y2)
+{
+ quadto( Geom::Point(x1,y1), Geom::Point(x2,y2) );
+}
+
+/**
+ * Adds a bezier segment to the current subpath.
+ * All points must be finite.
+ */
+void
+SPCurve::curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
+{
+ if (_pathv.empty()) g_message("SPCurve::curveto - path is empty!");
+ else _pathv.back().appendNew<Geom::CubicBezier>( p0, p1, p2 );
+}
+/**
+ * Calls SPCurve::curveto( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) )
+ * All coordinates must be finite.
+ */
+void
+SPCurve::curveto(double x0, double y0, double x1, double y1, double x2, double y2)
+{
+ curveto( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
+}
+
+/**
+ * Close current subpath by possibly adding a line between start and end.
+ */
+void
+SPCurve::closepath()
+{
+ _pathv.back().close(true);
+}
+
+/** Like SPCurve::closepath() but sets the end point of the last subpath
+ to the subpath start point instead of adding a new lineto.
+
+ Used for freehand drawing when the user draws back to the start point.
+**/
+void
+SPCurve::closepath_current()
+{
+ if (_pathv.back().size() > 0 && dynamic_cast<Geom::LineSegment const *>(&_pathv.back().back_open())) {
+ _pathv.back().erase_last();
+ } else {
+ _pathv.back().setFinal(_pathv.back().initialPoint());
+ }
+ _pathv.back().close(true);
+}
+
+/**
+ * True if no paths are in curve. If it only contains a path with only a moveto, the path is considered NON-empty
+ */
+bool
+SPCurve::is_empty() const
+{
+ return _pathv.empty();
+}
+
+/**
+ * True if paths are in curve. If it only contains a path with only a moveto, the path is considered as unset FALSE
+ */
+bool
+SPCurve::is_unset() const
+{
+ if (get_segment_count()) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * True iff all subpaths are closed.
+ * Returns false if the curve is empty.
+ */
+bool
+SPCurve::is_closed() const
+{
+ if (is_empty()) {
+ return false;
+ }
+
+ for (const auto & it : _pathv) {
+ if ( ! it.closed() ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * True if both curves are equal
+ */
+bool
+SPCurve::is_equal(SPCurve * other) const
+{
+ if(other == nullptr) {
+ return false;
+ }
+
+ if(_pathv == other->get_pathvector()){
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Return last pathsegment (possibly the closing path segment) of the last path in PathVector or NULL.
+ * If the last path is empty (contains only a moveto), the function returns NULL
+ */
+Geom::Curve const *
+SPCurve::last_segment() const
+{
+ if (is_empty()) {
+ return nullptr;
+ }
+ if (_pathv.back().empty()) {
+ return nullptr;
+ }
+
+ return &_pathv.back().back_default();
+}
+
+/**
+ * Return last path in PathVector or NULL.
+ */
+Geom::Path const *
+SPCurve::last_path() const
+{
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ return &_pathv.back();
+}
+
+/**
+ * Return first pathsegment in PathVector or NULL.
+ * equal in functionality to SPCurve::first_bpath()
+ */
+Geom::Curve const *
+SPCurve::first_segment() const
+{
+ if (is_empty()) {
+ return nullptr;
+ }
+ if (_pathv.front().empty()) {
+ return nullptr;
+ }
+
+ return &_pathv.front().front();
+}
+
+/**
+ * Return first path in PathVector or NULL.
+ */
+Geom::Path const *
+SPCurve::first_path() const
+{
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ return &_pathv.front();
+}
+
+/**
+ * Return first point of first subpath or nothing when the path is empty.
+ */
+boost::optional<Geom::Point>
+SPCurve::first_point() const
+{
+ boost::optional<Geom::Point> retval;
+
+ if (!is_empty()) {
+ retval = _pathv.front().initialPoint();
+ }
+
+ return retval;
+}
+
+/**
+ * Return the second point of first subpath or _movePos if curve too short.
+ * If the pathvector is empty, this returns nothing. If the first path is only a moveto, this method
+ * returns the first point of the second path, if it exists. If there is no 2nd path, it returns the
+ * first point of the first path.
+ */
+boost::optional<Geom::Point>
+SPCurve::second_point() const
+{
+ boost::optional<Geom::Point> retval;
+ if (!is_empty()) {
+ if (_pathv.front().empty()) {
+ // first path is only a moveto
+ // check if there is second path
+ if (_pathv.size() > 1) {
+ retval = _pathv[1].initialPoint();
+ } else {
+ retval = _pathv[0].initialPoint();
+ }
+ } else {
+ retval = _pathv.front()[0].finalPoint();
+ }
+ }
+
+ return retval;
+}
+
+/**
+ * Return the second-last point of last subpath or first point when that last subpath has only a moveto.
+ */
+boost::optional<Geom::Point>
+SPCurve::penultimate_point() const
+{
+ boost::optional<Geom::Point> retval;
+ if (!is_empty()) {
+ Geom::Path const &lastpath = _pathv.back();
+ if (!lastpath.empty()) {
+ Geom::Curve const &back = lastpath.back_default();
+ retval = back.initialPoint();
+ } else {
+ retval = lastpath.initialPoint();
+ }
+ }
+
+ return retval;
+}
+
+/**
+ * Return last point of last subpath or nothing when the curve is empty.
+ * If the last path is only a moveto, then return that point.
+ */
+boost::optional<Geom::Point>
+SPCurve::last_point() const
+{
+ boost::optional<Geom::Point> retval;
+
+ if (!is_empty()) {
+ retval = _pathv.back().finalPoint();
+ }
+
+ return retval;
+}
+
+/**
+ * Returns a *new* \a curve but drawn in the opposite direction.
+ * Should result in the same shape, but
+ * with all its markers drawn facing the other direction.
+ * Reverses the order of subpaths as well
+ **/
+SPCurve *
+SPCurve::create_reverse() const
+{
+ SPCurve *new_curve = new SPCurve(_pathv.reversed());
+
+ return new_curve;
+}
+
+/**
+ * Append \a curve2 to \a this.
+ * If \a use_lineto is false, simply add all paths in \a curve2 to \a this;
+ * if \a use_lineto is true, combine \a this's last path and \a curve2's first path and add the rest of the paths in \a curve2 to \a this.
+ */
+void
+SPCurve::append(SPCurve const *curve2,
+ bool use_lineto)
+{
+ if (curve2->is_empty())
+ return;
+
+ if (use_lineto) {
+ Geom::PathVector::const_iterator it = curve2->_pathv.begin();
+ if ( ! _pathv.empty() ) {
+ Geom::Path & lastpath = _pathv.back();
+ lastpath.appendNew<Geom::LineSegment>( (*it).initialPoint() );
+ lastpath.append( (*it) );
+ } else {
+ _pathv.push_back( (*it) );
+ }
+
+ for (++it; it != curve2->_pathv.end(); ++it) {
+ _pathv.push_back( (*it) );
+ }
+ } else {
+ for (const auto & it : curve2->_pathv) {
+ _pathv.push_back( it );
+ }
+ }
+}
+
+/**
+ * Append \a c1 to \a this with possible fusing of close endpoints. If the end of this curve and the start of c1 are within tolerance distance,
+ * then the startpoint of c1 is moved to the end of this curve and the first subpath of c1 is appended to the last subpath of this curve.
+ * When one of the curves (this curve or the argument curve) is closed, the returned value is NULL; otherwise the returned value is this curve.
+ * When one of the curves is empty, this curves path becomes the non-empty path.
+ */
+SPCurve *
+SPCurve::append_continuous(SPCurve const *c1, double tolerance)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ g_return_val_if_fail(c1 != nullptr, NULL);
+ if ( this->is_closed() || c1->is_closed() ) {
+ return nullptr;
+ }
+
+ if (c1->is_empty()) {
+ return this;
+ }
+
+ if (this->is_empty()) {
+ _pathv = c1->_pathv;
+ return this;
+ }
+
+ if ( (fabs((*this->last_point())[X] - (*c1->first_point())[X]) <= tolerance)
+ && (fabs((*this->last_point())[Y] - (*c1->first_point())[Y]) <= tolerance) )
+ {
+ // c1's first subpath can be appended to this curve's last subpath
+ Geom::PathVector::const_iterator path_it = c1->_pathv.begin();
+ Geom::Path & lastpath = _pathv.back();
+
+ Geom::Path newfirstpath(*path_it);
+ newfirstpath.setInitial(lastpath.finalPoint());
+ lastpath.append( newfirstpath );
+
+ for (++path_it; path_it != c1->_pathv.end(); ++path_it) {
+ _pathv.push_back( (*path_it) );
+ }
+
+ } else {
+ append(c1, true);
+ }
+
+ return this;
+}
+
+/**
+ * Remove last segment of curve.
+ * (Only used once in /src/pen-context.cpp)
+ */
+void
+SPCurve::backspace()
+{
+ if ( is_empty() )
+ return;
+
+ if ( !_pathv.back().empty() ) {
+ _pathv.back().erase_last();
+ _pathv.back().close(false);
+ }
+}
+
+/**
+ * TODO: add comments about what this method does and what assumptions are made and requirements are put on SPCurve
+ (2:08:18 AM) Johan: basically, i convert the path to pw<d2>
+(2:08:27 AM) Johan: then i calculate an offset path
+(2:08:29 AM) Johan: to move the knots
+(2:08:36 AM) Johan: then i add it
+(2:08:40 AM) Johan: then convert back to path
+If I remember correctly, this moves the firstpoint to new_p0, and the lastpoint to new_p1, and moves all nodes in between according to their arclength (interpolates the movement amount)
+ */
+void
+SPCurve::stretch_endpoints(Geom::Point const &new_p0, Geom::Point const &new_p1)
+{
+ if (is_empty()) {
+ return;
+ }
+
+ Geom::Point const offset0( new_p0 - *first_point() );
+ Geom::Point const offset1( new_p1 - *last_point() );
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = _pathv.front().toPwSb();
+ Geom::Piecewise<Geom::SBasis> arclength = Geom::arcLengthSb(pwd2);
+ if ( arclength.lastValue() <= 0 ) {
+ g_error("SPCurve::stretch_endpoints - arclength <= 0");
+ throw;
+ }
+ arclength *= 1./arclength.lastValue();
+ Geom::Point const A( offset0 );
+ Geom::Point const B( offset1 );
+ Geom::Piecewise<Geom::SBasis> offsetx = (arclength*-1.+1)*A[0] + arclength*B[0];
+ Geom::Piecewise<Geom::SBasis> offsety = (arclength*-1.+1)*A[1] + arclength*B[1];
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > offsetpath = Geom::sectionize( Geom::D2<Geom::Piecewise<Geom::SBasis> >(offsetx, offsety) );
+ pwd2 += offsetpath;
+ _pathv = Geom::path_from_piecewise( pwd2, 0.001 );
+}
+
+/**
+ * sets start of first path to new_p0, and end of first path to new_p1
+ */
+void
+SPCurve::move_endpoints(Geom::Point const &new_p0, Geom::Point const &new_p1)
+{
+ if (is_empty()) {
+ return;
+ }
+ _pathv.front().setInitial(new_p0);
+ _pathv.front().setFinal(new_p1);
+}
+
+/**
+ * returns the number of nodes in a path, used for statusbar text when selecting an spcurve.
+ * Sum of nodes in all the paths. When a path is closed, and its closing line segment is of zero-length,
+ * this function will not count the closing knot double (so basically ignores the closing line segment when it has zero length)
+ */
+size_t
+SPCurve::nodes_in_path() const
+{
+ size_t nr = 0;
+ for(const auto & it : _pathv) {
+ // if the path does not have any segments, it is a naked moveto,
+ // and therefore any path has at least one valid node
+ size_t psize = std::max<size_t>(1, it.size_closed());
+ nr += psize;
+ if (it.closed() && it.size_closed() > 0) {
+ const Geom::Curve &closingline = it.back_closed();
+ // the closing line segment is always of type
+ // Geom::LineSegment.
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ // closingline.isDegenerate() did not work, because it only checks for
+ // *exact* zero length, which goes wrong for relative coordinates and
+ // rounding errors...
+ // the closing line segment has zero-length. So stop before that one!
+ nr -= 1;
+ }
+ }
+ }
+
+ return nr;
+}
+
+/**
+ * Adds p to the last point (and last handle if present) of the last path
+ */
+void
+SPCurve::last_point_additive_move(Geom::Point const & p)
+{
+ if (is_empty()) {
+ return;
+ }
+
+ _pathv.back().setFinal( _pathv.back().finalPoint() + p );
+
+ // Move handle as well when the last segment is a cubic bezier segment:
+ // TODO: what to do for quadratic beziers?
+ if ( Geom::CubicBezier const *lastcube = dynamic_cast<Geom::CubicBezier const *>(&_pathv.back().back()) ) {
+ Geom::CubicBezier newcube( *lastcube );
+ newcube.setPoint(2, newcube[2] + p);
+ _pathv.back().replace( --_pathv.back().end(), newcube );
+ }
+}
+
+/*
+ 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/display/curve.h b/src/display/curve.h
new file mode 100644
index 0000000..c6ddb4c
--- /dev/null
+++ b/src/display/curve.h
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_DISPLAY_CURVE_H
+#define SEEN_DISPLAY_CURVE_H
+
+#include <2geom/pathvector.h>
+#include <cstddef>
+#include <boost/optional.hpp>
+#include <list>
+
+/**
+ * Wrapper around a Geom::PathVector object.
+ */
+class SPCurve {
+public:
+ /* Constructors */
+ explicit SPCurve();
+ explicit SPCurve(Geom::PathVector pathv);
+ explicit SPCurve(std::list<SPCurve *> const& pathv);
+ static SPCurve * new_from_rect(Geom::Rect const &rect, bool all_four_sides = false);
+
+ virtual ~SPCurve();
+
+ // Don't implement these:
+ SPCurve(const SPCurve&) = delete;
+ SPCurve& operator=(const SPCurve&) = delete;
+
+ void set_pathvector(Geom::PathVector const & new_pathv);
+ Geom::PathVector const & get_pathvector() const;
+
+ SPCurve * ref();
+ SPCurve * unref();
+
+ SPCurve * copy() const;
+
+ size_t get_segment_count() const;
+ size_t nodes_in_path() const;
+
+ bool is_empty() const;
+ bool is_unset() const;
+ bool is_closed() const;
+ bool is_equal(SPCurve * other) const;
+ Geom::Curve const * last_segment() const;
+ Geom::Path const * last_path() const;
+ Geom::Curve const * first_segment() const;
+ Geom::Path const * first_path() const;
+ boost::optional<Geom::Point> first_point() const;
+ boost::optional<Geom::Point> last_point() const;
+ boost::optional<Geom::Point> second_point() const;
+ boost::optional<Geom::Point> penultimate_point() const;
+
+ void reset();
+
+ void moveto(Geom::Point const &p);
+ void moveto(double x, double y);
+ void lineto(Geom::Point const &p);
+ void lineto(double x, double y);
+ void quadto(Geom::Point const &p1, Geom::Point const &p2);
+ void quadto(double x1, double y1, double x2, double y2);
+ void curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2);
+ void curveto(double x0, double y0, double x1, double y1, double x2, double y2);
+ void closepath();
+ void closepath_current();
+ void backspace();
+
+ void transform(Geom::Affine const &m);
+ void stretch_endpoints(Geom::Point const &, Geom::Point const &);
+ void move_endpoints(Geom::Point const &, Geom::Point const &);
+ void last_point_additive_move(Geom::Point const & p);
+
+ void append(SPCurve const *curve2, bool use_lineto);
+ SPCurve * append_continuous(SPCurve const *c1, double tolerance);
+ SPCurve * create_reverse() const;
+
+ std::list<SPCurve *> split() const;
+ static SPCurve * concat(std::list<SPCurve *> l);
+
+protected:
+ size_t _refcount;
+
+ Geom::PathVector _pathv;
+};
+
+#endif // !SEEN_DISPLAY_CURVE_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 :
diff --git a/src/display/drawing-context.cpp b/src/display/drawing-context.cpp
new file mode 100644
index 0000000..ac45df2
--- /dev/null
+++ b/src/display/drawing-context.cpp
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo drawing context with Inkscape extensions.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include "display/cairo-utils.h"
+
+namespace Inkscape {
+
+using Geom::X;
+using Geom::Y;
+
+/**
+ * @class DrawingContext::Save
+ * RAII idiom for saving the state of DrawingContext.
+ */
+
+DrawingContext::Save::Save()
+ : _dc(nullptr)
+{}
+DrawingContext::Save::Save(DrawingContext &dc)
+ : _dc(&dc)
+{
+ _dc->save();
+}
+DrawingContext::Save::~Save()
+{
+ if (_dc) {
+ _dc->restore();
+ }
+}
+void DrawingContext::Save::save(DrawingContext &dc)
+{
+ if (_dc) {
+ // TODO: it might be better to treat this occurrence as a bug
+ _dc->restore();
+ }
+ _dc = &dc;
+ _dc->save();
+}
+
+/**
+ * @class DrawingContext
+ * Minimal wrapper over Cairo.
+ *
+ * This is a wrapper over cairo_t, extended with operations that work
+ * with 2Geom geometrical primitives. Some of this is probably duplicated
+ * in cairo-render-context.cpp, which provides higher level operations
+ * for drawing entire SPObjects when exporting.
+ */
+
+// Uses existing Cairo surface
+DrawingContext::DrawingContext(cairo_t *ct, Geom::Point const &origin)
+ : _ct(ct)
+ , _surface(new DrawingSurface(cairo_get_group_target(ct), origin))
+ , _delete_surface(true)
+ , _restore_context(true)
+{
+ _surface->_has_context = true;
+ cairo_reference(_ct);
+ cairo_save(_ct);
+ cairo_translate(_ct, -origin[Geom::X], -origin[Geom::Y]);
+}
+
+// Uses existing Cairo surface
+DrawingContext::DrawingContext(cairo_surface_t *surface, Geom::Point const &origin)
+ : _ct(nullptr)
+ , _surface(new DrawingSurface(surface, origin))
+ , _delete_surface(true)
+ , _restore_context(false)
+{
+ _surface->_has_context = true;
+ _ct = _surface->createRawContext();
+}
+
+DrawingContext::DrawingContext(DrawingSurface &s)
+ : _ct(s.createRawContext())
+ , _surface(&s)
+ , _delete_surface(false)
+ , _restore_context(false)
+{}
+
+DrawingContext::~DrawingContext()
+{
+ if (_restore_context) {
+ cairo_restore(_ct);
+ }
+ cairo_destroy(_ct);
+ _surface->_has_context = false;
+ if (_delete_surface) {
+ delete _surface;
+ }
+}
+
+void DrawingContext::arc(Geom::Point const &center, double radius, Geom::AngleInterval const &angle)
+{
+ double from = angle.initialAngle();
+ double to = angle.finalAngle();
+ if (to > from) {
+ cairo_arc(_ct, center[X], center[Y], radius, from, to);
+ } else {
+ cairo_arc_negative(_ct, center[X], center[Y], radius, to, from);
+ }
+}
+
+// Applies transform to Cairo surface
+void DrawingContext::transform(Geom::Affine const &trans) {
+ ink_cairo_transform(_ct, trans);
+}
+
+void DrawingContext::path(Geom::PathVector const &pv) {
+ feed_pathvector_to_cairo(_ct, pv);
+}
+
+void DrawingContext::paint(double alpha) {
+ if (alpha == 1.0) cairo_paint(_ct);
+ else cairo_paint_with_alpha(_ct, alpha);
+}
+void DrawingContext::setSource(guint32 rgba) {
+ ink_cairo_set_source_rgba32(_ct, rgba);
+}
+void DrawingContext::setSource(DrawingSurface *s) {
+ Geom::Point origin = s->origin();
+ cairo_set_source_surface(_ct, s->raw(), origin[X], origin[Y]);
+}
+void DrawingContext::setSourceCheckerboard() {
+ cairo_pattern_t *check = ink_cairo_pattern_create_checkerboard();
+ cairo_set_source(_ct, check);
+ cairo_pattern_destroy(check);
+}
+
+Geom::Rect DrawingContext::targetLogicalBounds() const
+{
+ Geom::Rect ret(_surface->area());
+ return ret;
+}
+
+} // end 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/display/drawing-context.h b/src/display/drawing-context.h
new file mode 100644
index 0000000..aedb87e
--- /dev/null
+++ b/src/display/drawing-context.h
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo drawing context with Inkscape extensions.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_CONTEXT_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_CONTEXT_H
+
+#include <2geom/rect.h>
+#include <2geom/transforms.h>
+#include <boost/utility.hpp>
+#include <cairo.h>
+typedef unsigned int guint32;
+
+namespace Inkscape {
+
+class DrawingSurface;
+
+class DrawingContext
+ : boost::noncopyable
+{
+public:
+ class Save {
+ public:
+ Save();
+ Save(DrawingContext &dc);
+ ~Save();
+ void save(DrawingContext &dc);
+ private:
+ DrawingContext *_dc;
+ };
+
+ DrawingContext(cairo_t *ct, Geom::Point const &origin);
+ DrawingContext(cairo_surface_t *surface, Geom::Point const &origin);
+ DrawingContext(DrawingSurface &s);
+ ~DrawingContext();
+
+ void save() { cairo_save(_ct); }
+ void restore() { cairo_restore(_ct); }
+ void pushGroup() { cairo_push_group(_ct); }
+ void pushAlphaGroup() { cairo_push_group_with_content(_ct, CAIRO_CONTENT_ALPHA); }
+ void popGroupToSource() { cairo_pop_group_to_source(_ct); }
+
+ void transform(Geom::Affine const &trans);
+ void translate(Geom::Point const &t) { cairo_translate(_ct, t[Geom::X], t[Geom::Y]); } // todo: take Translate
+ void translate(double dx, double dy) { cairo_translate(_ct, dx, dy); }
+ void scale(Geom::Scale const &s) { cairo_scale(_ct, s[Geom::X], s[Geom::Y]); }
+ void scale(double sx, double sy) { cairo_scale(_ct, sx, sy); }
+ void device_to_user_distance(double &dx, double &dy) { cairo_device_to_user_distance(_ct, &dx, &dy); }
+ void user_to_device_distance(double &dx, double &dy) { cairo_user_to_device_distance(_ct, &dx, &dy); }
+
+ void moveTo(Geom::Point const &p) { cairo_move_to(_ct, p[Geom::X], p[Geom::Y]); }
+ void lineTo(Geom::Point const &p) { cairo_line_to(_ct, p[Geom::X], p[Geom::Y]); }
+ void curveTo(Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3) {
+ cairo_curve_to(_ct, p1[Geom::X], p1[Geom::Y], p2[Geom::X], p2[Geom::Y], p3[Geom::X], p3[Geom::Y]);
+ }
+ void arc(Geom::Point const &center, double radius, Geom::AngleInterval const &angle);
+ void closePath() { cairo_close_path(_ct); }
+ void rectangle(Geom::Rect const &r) {
+ cairo_rectangle(_ct, r.left(), r.top(), r.width(), r.height());
+ }
+ void rectangle(Geom::IntRect const &r) {
+ cairo_rectangle(_ct, r.left(), r.top(), r.width(), r.height());
+ }
+ // Used in drawing-text.cpp to overwrite glyphs, which have the opposite path rotation as a regular rect
+ void revrectangle(Geom::Rect const &r) {
+ cairo_move_to ( _ct, r.left(), r.top() );
+ cairo_rel_line_to (_ct, 0, r.height() );
+ cairo_rel_line_to (_ct, r.width(), 0 );
+ cairo_rel_line_to (_ct, 0, -r.height() );
+ cairo_close_path ( _ct);
+ }
+ void revrectangle(Geom::IntRect const &r) {
+ cairo_move_to ( _ct, r.left(), r.top() );
+ cairo_rel_line_to (_ct, 0, r.height() );
+ cairo_rel_line_to (_ct, r.width(), 0 );
+ cairo_rel_line_to (_ct, 0, -r.height() );
+ cairo_close_path ( _ct);
+ }
+ void newPath() { cairo_new_path(_ct); }
+ void newSubpath() { cairo_new_sub_path(_ct); }
+ void path(Geom::PathVector const &pv);
+
+ void paint(double alpha = 1.0);
+ void fill() { cairo_fill(_ct); }
+ void fillPreserve() { cairo_fill_preserve(_ct); }
+ void stroke() { cairo_stroke(_ct); }
+ void strokePreserve() { cairo_stroke_preserve(_ct); }
+ void clip() { cairo_clip(_ct); }
+
+ void setLineWidth(double w) { cairo_set_line_width(_ct, w); }
+ void setLineCap(cairo_line_cap_t cap) { cairo_set_line_cap(_ct, cap); }
+ void setLineJoin(cairo_line_join_t join) { cairo_set_line_join(_ct, join); }
+ void setMiterLimit(double miter) { cairo_set_miter_limit(_ct, miter); }
+ void setFillRule(cairo_fill_rule_t rule) { cairo_set_fill_rule(_ct, rule); }
+ void setOperator(cairo_operator_t op) { cairo_set_operator(_ct, op); }
+ cairo_operator_t getOperator() { return cairo_get_operator(_ct); }
+ void setTolerance(double tol) { cairo_set_tolerance(_ct, tol); }
+ void setSource(cairo_pattern_t *source) { cairo_set_source(_ct, source); }
+ void setSource(cairo_surface_t *surface, double x, double y) {
+ cairo_set_source_surface(_ct, surface, x, y);
+ }
+ void setSource(double r, double g, double b, double a = 1.0) {
+ cairo_set_source_rgba(_ct, r, g, b, a);
+ }
+ void setSource(guint32 rgba);
+ void setSource(DrawingSurface *s);
+ void setSourceCheckerboard();
+
+ void patternSetFilter(cairo_filter_t filter) {
+ cairo_pattern_set_filter(cairo_get_source(_ct), filter);
+ }
+ void patternSetExtend(cairo_extend_t extend) { cairo_pattern_set_extend(cairo_get_source(_ct), extend); }
+
+ Geom::Rect targetLogicalBounds() const;
+
+ cairo_t *raw() { return _ct; }
+ cairo_surface_t *rawTarget() { return cairo_get_group_target(_ct); }
+
+ DrawingSurface *surface() { return _surface; } // Needed to find scale in drawing-item.cpp
+
+private:
+ DrawingContext(cairo_t *ct, DrawingSurface *surface, bool destroy);
+
+ cairo_t *_ct;
+ DrawingSurface *_surface;
+ bool _delete_surface;
+ bool _restore_context;
+
+ friend class DrawingSurface;
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-group.cpp b/src/display/drawing-group.cpp
new file mode 100644
index 0000000..397b30c
--- /dev/null
+++ b/src/display/drawing-group.cpp
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Group belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/drawing-group.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-item.h"
+#include "display/drawing-surface.h"
+#include "display/drawing-text.h"
+#include "display/drawing.h"
+#include "style.h"
+
+namespace Inkscape {
+
+DrawingGroup::DrawingGroup(Drawing &drawing)
+ : DrawingItem(drawing)
+ , _child_transform(nullptr)
+{}
+
+DrawingGroup::~DrawingGroup()
+{
+ delete _child_transform; // delete NULL; is safe
+}
+
+/**
+ * Set whether the group returns children from pick calls.
+ * Previously this feature was called "transparent groups".
+ */
+void
+DrawingGroup::setPickChildren(bool p)
+{
+ _pick_children = p;
+}
+
+/**
+ * Set additional transform for the group.
+ * This is applied after the normal transform and mainly useful for
+ * markers, clipping paths, etc.
+ */
+void
+DrawingGroup::setChildTransform(Geom::Affine const &new_trans)
+{
+ Geom::Affine current;
+ if (_child_transform) {
+ current = *_child_transform;
+ }
+
+ if (!Geom::are_near(current, new_trans, 1e-18)) {
+ // mark the area where the object was for redraw.
+ _markForRendering();
+ if (new_trans.isIdentity()) {
+ delete _child_transform; // delete NULL; is safe
+ _child_transform = nullptr;
+ } else {
+ _child_transform = new Geom::Affine(new_trans);
+ }
+ _markForUpdate(STATE_ALL, true);
+ }
+}
+
+unsigned
+DrawingGroup::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ unsigned beststate = STATE_ALL;
+ bool outline = _drawing.outline();
+
+ UpdateContext child_ctx(ctx);
+ if (_child_transform) {
+ child_ctx.ctm = *_child_transform * ctx.ctm;
+ }
+ for (auto & i : _children) {
+ i.update(area, child_ctx, flags, reset);
+ }
+ if (beststate & STATE_BBOX) {
+ _bbox = Geom::OptIntRect();
+ for (auto & i : _children) {
+ if (i.visible()) {
+ _bbox.unionWith(outline ? i.geometricBounds() : i.visualBounds());
+ }
+ }
+ }
+ return beststate;
+}
+
+unsigned
+DrawingGroup::_renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
+{
+ if (stop_at == nullptr) {
+ // normal rendering
+ for (auto &i : _children) {
+ i.setAntialiasing(_antialias);
+ i.render(dc, area, flags, stop_at);
+ }
+ } else {
+ // background rendering
+ for (auto &i : _children) {
+ if (&i == stop_at)
+ return RENDER_OK; // do not render the stop_at item at all
+ if (i.isAncestorOf(stop_at)) {
+ // render its ancestors without masks, opacity or filters
+ i.setAntialiasing(_antialias);
+ i.render(dc, area, flags | RENDER_FILTER_BACKGROUND, stop_at);
+ return RENDER_OK;
+ } else {
+ i.setAntialiasing(_antialias);
+ i.render(dc, area, flags, stop_at);
+ }
+ }
+ }
+ return RENDER_OK;
+}
+
+void
+DrawingGroup::_clipItem(DrawingContext &dc, Geom::IntRect const &area)
+{
+ for (auto & i : _children) {
+ i.clip(dc, area);
+ }
+}
+
+DrawingItem *
+DrawingGroup::_pickItem(Geom::Point const &p, double delta, unsigned flags)
+{
+ for (auto & i : _children) {
+ DrawingItem *picked = i.pick(p, delta, flags);
+ if (picked) {
+ return _pick_children ? picked : this;
+ }
+ }
+ return nullptr;
+}
+
+bool
+DrawingGroup::_canClip()
+{
+ return true;
+}
+
+bool is_drawing_group(DrawingItem *item)
+{
+ return dynamic_cast<DrawingGroup *>(item) != nullptr;
+}
+
+} // end 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/display/drawing-group.h b/src/display/drawing-group.h
new file mode 100644
index 0000000..1f5f283
--- /dev/null
+++ b/src/display/drawing-group.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Group belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_GROUP_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_GROUP_H
+
+#include "display/drawing-item.h"
+
+namespace Inkscape {
+
+class DrawingGroup
+ : public DrawingItem
+{
+public:
+ DrawingGroup(Drawing &drawing);
+ ~DrawingGroup() override;
+
+ bool pickChildren() { return _pick_children; }
+ void setPickChildren(bool p);
+
+ void setChildTransform(Geom::Affine const &new_trans);
+
+protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+ unsigned _renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags,
+ DrawingItem *stop_at) override;
+ void _clipItem(DrawingContext &dc, Geom::IntRect const &area) override;
+ DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags) override;
+ bool _canClip() override;
+
+ Geom::Affine *_child_transform;
+};
+
+bool is_drawing_group(DrawingItem *item);
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-image.cpp b/src/display/drawing-image.cpp
new file mode 100644
index 0000000..0ac716d
--- /dev/null
+++ b/src/display/drawing-image.cpp
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Bitmap image belonging to an SVG drawing.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/bezier-curve.h>
+
+#include "display/drawing.h"
+#include "display/drawing-context.h"
+#include "display/drawing-image.h"
+#include "preferences.h"
+
+#include "display/cairo-utils.h"
+
+namespace Inkscape {
+
+DrawingImage::DrawingImage(Drawing &drawing)
+ : DrawingItem(drawing)
+ , _pixbuf(nullptr)
+{}
+
+DrawingImage::~DrawingImage()
+{
+ // _pixbuf is owned by SPImage - do not delete it
+}
+
+void
+DrawingImage::setPixbuf(Inkscape::Pixbuf *pb)
+{
+ _pixbuf = pb;
+
+ _markForUpdate(STATE_ALL, false);
+}
+
+void
+DrawingImage::setScale(double sx, double sy)
+{
+ _scale = Geom::Scale(sx, sy);
+ _markForUpdate(STATE_ALL, false);
+}
+
+void
+DrawingImage::setOrigin(Geom::Point const &o)
+{
+ _origin = o;
+ _markForUpdate(STATE_ALL, false);
+}
+
+void
+DrawingImage::setClipbox(Geom::Rect const &box)
+{
+ _clipbox = box;
+ _markForUpdate(STATE_ALL, false);
+}
+
+Geom::Rect
+DrawingImage::bounds() const
+{
+ if (!_pixbuf) return _clipbox;
+
+ double pw = _pixbuf->width();
+ double ph = _pixbuf->height();
+ double vw = pw * _scale[Geom::X];
+ double vh = ph * _scale[Geom::Y];
+ Geom::Point wh(vw, vh);
+ Geom::Rect view(_origin, _origin+wh);
+ Geom::OptRect res = _clipbox & view;
+ Geom::Rect ret = res ? *res : _clipbox;
+
+ return ret;
+}
+
+unsigned
+DrawingImage::_updateItem(Geom::IntRect const &, UpdateContext const &, unsigned, unsigned)
+{
+ _markForRendering();
+
+ // Calculate bbox
+ if (_pixbuf) {
+ Geom::Rect r = bounds() * _ctm;
+ _bbox = r.roundOutwards();
+ } else {
+ _bbox = Geom::OptIntRect();
+ }
+
+ return STATE_ALL;
+}
+
+unsigned DrawingImage::_renderItem(DrawingContext &dc, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/)
+{
+ bool outline = _drawing.outline();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool imgoutline = prefs->getBool("/options/rendering/imageinoutlinemode", false);
+
+ if (!outline || imgoutline) {
+ if (!_pixbuf) return RENDER_OK;
+
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ dc.newPath();
+ dc.rectangle(_clipbox);
+ dc.clip();
+
+ dc.translate(_origin);
+ dc.scale(_scale);
+ dc.setSource(_pixbuf->getSurfaceRaw(), 0, 0);
+ dc.patternSetExtend(CAIRO_EXTEND_PAD);
+
+ 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, cairo-render-context.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:
+ dc.patternSetFilter( CAIRO_FILTER_NEAREST );
+ break;
+ case SP_CSS_IMAGE_RENDERING_AUTO:
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZEQUALITY:
+ default:
+ // In recent Cairo, BEST used Lanczos3, which is prohibitively slow
+ dc.patternSetFilter( CAIRO_FILTER_GOOD );
+ break;
+ }
+ }
+
+ dc.paint(1);
+
+ } else { // outline; draw a rect instead
+
+ guint32 rgba = prefs->getInt("/options/wireframecolors/images", 0xff0000ff);
+
+ { Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ dc.newPath();
+
+ Geom::Rect r = bounds();
+ Geom::Point c00 = r.corner(0);
+ Geom::Point c01 = r.corner(3);
+ Geom::Point c11 = r.corner(2);
+ Geom::Point c10 = r.corner(1);
+
+ dc.moveTo(c00);
+ // the box
+ dc.lineTo(c10);
+ dc.lineTo(c11);
+ dc.lineTo(c01);
+ dc.lineTo(c00);
+ // the diagonals
+ dc.lineTo(c11);
+ dc.moveTo(c10);
+ dc.lineTo(c01);
+ }
+
+ dc.setLineWidth(0.5);
+ dc.setSource(rgba);
+ dc.stroke();
+ }
+ return RENDER_OK;
+}
+
+/** Calculates the closest distance from p to the segment a1-a2*/
+static double
+distance_to_segment (Geom::Point const &p, Geom::Point const &a1, Geom::Point const &a2)
+{
+ Geom::LineSegment l(a1, a2);
+ Geom::Point np = l.pointAt(l.nearestTime(p));
+ return Geom::distance(np, p);
+}
+
+DrawingItem *
+DrawingImage::_pickItem(Geom::Point const &p, double delta, unsigned /*sticky*/)
+{
+ if (!_pixbuf) return nullptr;
+
+ bool outline = _drawing.outline() || _drawing.getOutlineSensitive();
+
+ if (outline) {
+ Geom::Rect r = bounds();
+ Geom::Point pick = p * _ctm.inverse();
+
+ // find whether any side or diagonal is within delta
+ // to do so, iterate over all pairs of corners
+ for (unsigned i = 0; i < 3; ++i) { // for i=3, there is nothing to do
+ for (unsigned j = i+1; j < 4; ++j) {
+ if (distance_to_segment(pick, r.corner(i), r.corner(j)) < delta) {
+ return this;
+ }
+ }
+ }
+ return nullptr;
+
+ } else {
+ unsigned char *const pixels = _pixbuf->pixels();
+ int width = _pixbuf->width();
+ int height = _pixbuf->height();
+ size_t rowstride = _pixbuf->rowstride();
+
+ Geom::Point tp = p * _ctm.inverse();
+ Geom::Rect r = bounds();
+
+ if (!r.contains(tp))
+ return nullptr;
+
+ double vw = width * _scale[Geom::X];
+ double vh = height * _scale[Geom::Y];
+ int ix = floor((tp[Geom::X] - _origin[Geom::X]) / vw * width);
+ int iy = floor((tp[Geom::Y] - _origin[Geom::Y]) / vh * height);
+
+ if ((ix < 0) || (iy < 0) || (ix >= width) || (iy >= height))
+ return nullptr;
+
+ unsigned char *pix_ptr = pixels + iy * rowstride + ix * 4;
+ // pick if the image is less than 99% transparent
+ guint32 alpha = 0;
+ if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) {
+ guint32 px = *reinterpret_cast<guint32 const *>(pix_ptr);
+ alpha = (px & 0xff000000) >> 24;
+ } else if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_GDK) {
+ alpha = pix_ptr[3];
+ } else {
+ throw std::runtime_error("Unrecognized pixel format");
+ }
+ float alpha_f = (alpha / 255.0f) * _opacity;
+ return alpha_f > 0.01 ? this : nullptr;
+ }
+}
+
+} // end 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/display/drawing-image.h b/src/display/drawing-image.h
new file mode 100644
index 0000000..fd962db
--- /dev/null
+++ b/src/display/drawing-image.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Bitmap image belonging to an SVG drawing.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_IMAGE_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_IMAGE_H
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <2geom/transforms.h>
+
+#include "display/drawing-item.h"
+
+namespace Inkscape {
+class Pixbuf;
+
+class DrawingImage
+ : public DrawingItem
+{
+public:
+ DrawingImage(Drawing &drawing);
+ ~DrawingImage() override;
+
+ void setPixbuf(Inkscape::Pixbuf *pb);
+ void setScale(double sx, double sy);
+ void setOrigin(Geom::Point const &o);
+ void setClipbox(Geom::Rect const &box);
+ Geom::Rect bounds() const;
+
+protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+ unsigned _renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags,
+ DrawingItem *stop_at) override;
+ DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags) override;
+
+ Inkscape::Pixbuf *_pixbuf;
+
+ // TODO: the following three should probably be merged into a new Geom::Viewbox object
+ Geom::Rect _clipbox; ///< for preserveAspectRatio
+ Geom::Point _origin;
+ Geom::Scale _scale;
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-item.cpp b/src/display/drawing-item.cpp
new file mode 100644
index 0000000..29a23b2
--- /dev/null
+++ b/src/display/drawing-item.cpp
@@ -0,0 +1,1221 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Canvas item belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <climits>
+
+#include "display/drawing-context.h"
+#include "display/drawing-group.h"
+#include "display/drawing-item.h"
+#include "display/drawing-pattern.h"
+#include "display/drawing-surface.h"
+#include "display/drawing-text.h"
+#include "display/drawing.h"
+#include "nr-filter.h"
+#include "preferences.h"
+#include "style.h"
+
+#include "display/cairo-utils.h"
+#include "display/cairo-templates.h"
+
+#include "object/sp-item.h"
+
+namespace Inkscape {
+/**
+ * @class DrawingItem
+ * SVG drawing item for display.
+ *
+ * This was previously known as NRArenaItem. It represents the renderable
+ * portion of the SVG document. Typically this is created by the SP tree,
+ * in particular the show() virtual function.
+ *
+ * @section ObjectLifetime Object lifetime
+ * Deleting a DrawingItem will cause all of its children to be deleted as well.
+ * This can lead to nasty surprises if you hold references to things
+ * which are children of what is being deleted. Therefore, in the SP tree,
+ * you always need to delete the item views of children before deleting
+ * the view of the parent. Do not call delete on things returned from show()
+ * - this will cause dangling pointers inside the SPItem and lead to a crash.
+ * Use the corresponding hide() method.
+ *
+ * Outside of the SP tree, you should not use any references after the root node
+ * has been deleted.
+ */
+
+DrawingItem::DrawingItem(Drawing &drawing)
+ : _drawing(drawing)
+ , _parent(nullptr)
+ , _key(0)
+ , _style(nullptr)
+ , _context_style(nullptr)
+ , _opacity(1.0)
+ , _transform(nullptr)
+ , _clip(nullptr)
+ , _mask(nullptr)
+ , _fill_pattern(nullptr)
+ , _stroke_pattern(nullptr)
+ , _filter(nullptr)
+ , _item(nullptr)
+ , _cache(nullptr)
+ , _state(0)
+ , _child_type(CHILD_ORPHAN)
+ , _background_new(0)
+ , _background_accumulate(0)
+ , _visible(true)
+ , _sensitive(true)
+ , _cached(0)
+ , _cached_persistent(0)
+ , _has_cache_iterator(0)
+ , _propagate(0)
+ // , _renders_opacity(0)
+ , _pick_children(0)
+ , _antialias(2)
+ , _prev_nir(false)
+ , _isolation(SP_CSS_ISOLATION_AUTO)
+ , _mix_blend_mode(SP_CSS_BLEND_NORMAL)
+{}
+
+DrawingItem::~DrawingItem()
+{
+ _drawing.signal_item_deleted.emit(this);
+ //if (!_children.empty()) {
+ // g_warning("Removing item with children");
+ //}
+
+ // remove from the set of cached items and delete cache
+ setCached(false, true);
+ // remove this item from parent's children list
+ // due to the effect of clearChildren(), this only happens for the top-level deleted item
+ if (_parent) {
+ _markForRendering();
+ }
+ switch (_child_type) {
+ case CHILD_NORMAL: {
+ ChildrenList::iterator ithis = _parent->_children.iterator_to(*this);
+ _parent->_children.erase(ithis);
+ } break;
+ case CHILD_CLIP:
+ // we cannot call setClip(NULL) or setMask(NULL),
+ // because that would be an endless loop
+ _parent->_clip = nullptr;
+ break;
+ case CHILD_MASK:
+ _parent->_mask = nullptr;
+ break;
+ case CHILD_ROOT:
+ _drawing._root = nullptr;
+ break;
+ case CHILD_FILL_PATTERN:
+ _parent->_fill_pattern = nullptr;
+ break;
+ case CHILD_STROKE_PATTERN:
+ _parent->_stroke_pattern = nullptr;
+ break;
+ default: ;
+ }
+
+ if (_parent) {
+ _parent->_markForUpdate(STATE_ALL, false);
+ }
+ clearChildren();
+ delete _transform;
+ delete _stroke_pattern;
+ delete _fill_pattern;
+ delete _clip;
+ delete _mask;
+ delete _filter;
+ if(_style)
+ sp_style_unref(_style);
+}
+
+DrawingItem *
+DrawingItem::parent() const
+{
+ // initially I wanted to return NULL if we are a clip or mask child,
+ // but the previous behavior was just to return the parent regardless of child type
+ return _parent;
+}
+
+/// Returns true if item is among the descendants. Will return false if item == this.
+bool
+DrawingItem::isAncestorOf(DrawingItem *item) const
+{
+ for (DrawingItem *i = item->_parent; i; i = i->_parent) {
+ if (i == this) return true;
+ }
+ return false;
+}
+
+void
+DrawingItem::appendChild(DrawingItem *item)
+{
+ item->_parent = this;
+ assert(item->_child_type == CHILD_ORPHAN);
+ item->_child_type = CHILD_NORMAL;
+ _children.push_back(*item);
+
+ // This ensures that _markForUpdate() called on the child will recurse to this item
+ item->_state = STATE_ALL;
+ // Because _markForUpdate recurses through ancestors, we can simply call it
+ // on the just-added child. This has the additional benefit that we do not
+ // rely on the appended child being in the default non-updated state.
+ // We set propagate to true, because the child might have descendants of its own.
+ item->_markForUpdate(STATE_ALL, true);
+}
+
+void
+DrawingItem::prependChild(DrawingItem *item)
+{
+ item->_parent = this;
+ assert(item->_child_type == CHILD_ORPHAN);
+ item->_child_type = CHILD_NORMAL;
+ _children.push_front(*item);
+ // See appendChild for explanation
+ item->_state = STATE_ALL;
+ item->_markForUpdate(STATE_ALL, true);
+}
+
+/// Delete all regular children of this item (not mask or clip).
+void
+DrawingItem::clearChildren()
+{
+ if (_children.empty()) return;
+
+ _markForRendering();
+ // prevent children from referencing the parent during deletion
+ // this way, children won't try to remove themselves from a list
+ // from which they have already been removed by clear_and_dispose
+ for (auto & i : _children) {
+ i._parent = NULL;
+ i._child_type = CHILD_ORPHAN;
+ }
+ _children.clear_and_dispose(DeleteDisposer());
+ _markForUpdate(STATE_ALL, false);
+}
+
+/// Set the incremental transform for this item
+void
+DrawingItem::setTransform(Geom::Affine const &new_trans)
+{
+ Geom::Affine current;
+ if (_transform) {
+ current = *_transform;
+ }
+
+ if (!Geom::are_near(current, new_trans, 1e-18)) {
+ // mark the area where the object was for redraw.
+ _markForRendering();
+ delete _transform;
+ if (new_trans.isIdentity()) {
+ _transform = nullptr;
+ } else {
+ _transform = new Geom::Affine(new_trans);
+ }
+ _markForUpdate(STATE_ALL, true);
+ }
+}
+
+void
+DrawingItem::setOpacity(float opacity)
+{
+ if (_opacity != opacity) {
+ _opacity = opacity;
+ _markForRendering();
+ }
+}
+
+void
+DrawingItem::setAntialiasing(unsigned a)
+{
+ if (_antialias != a) {
+ _antialias = a;
+ _markForRendering();
+ }
+}
+
+void
+DrawingItem::setIsolation(bool isolation)
+{
+ _isolation = isolation;
+ //if( isolation != 0 ) std::cout << "isolation: " << isolation << std::endl;
+ _markForRendering();
+}
+
+void
+DrawingItem::setBlendMode(SPBlendMode mix_blend_mode)
+{
+ _mix_blend_mode = mix_blend_mode;
+ //if( mix_blend_mode != 0 ) std::cout << "setBlendMode: " << mix_blend_mode << std::endl;
+ _markForRendering();
+}
+
+void
+DrawingItem::setVisible(bool v)
+{
+ if (_visible != v) {
+ _visible = v;
+ _markForRendering();
+ }
+}
+
+/// This is currently unused
+void
+DrawingItem::setSensitive(bool s)
+{
+ _sensitive = s;
+}
+
+/**
+ * Enable / disable storing the rendering in memory.
+ * Calling setCached(false, true) will also remove the persistent status
+ */
+void
+DrawingItem::setCached(bool cached, bool persistent)
+{
+ static const char *cache_env = getenv("_INKSCAPE_DISABLE_CACHE");
+ if (cache_env) return;
+
+ if (_cached_persistent && !persistent)
+ return;
+
+ _cached = cached;
+ _cached_persistent = persistent ? cached : false;
+ if (cached) {
+ _drawing._cached_items.insert(this);
+ } else {
+ _drawing._cached_items.erase(this);
+ delete _cache;
+ _cache = nullptr;
+ if (_has_cache_iterator) {
+ _drawing._candidate_items.erase(_cache_iterator);
+ _has_cache_iterator = false;
+ }
+ }
+}
+
+/**
+ * Process information related to the new style.
+ *
+ * Note: _style is not used by DrawingGlyphs which uses its parent style.
+ */
+void
+DrawingItem::setStyle(SPStyle *style, SPStyle *context_style)
+{
+ // std::cout << "DrawingItem::setStyle: " << name() << " " << style
+ // << " " << context_style << std::endl;
+
+ if( style != _style ) {
+ if (style) sp_style_ref(style);
+ if (_style) sp_style_unref(_style);
+ _style = style;
+ }
+
+ if (style && style->filter.set && style->getFilter()) {
+ if (!_filter) {
+ int primitives = SP_FILTER(style->getFilter())->primitive_count();
+ _filter = new Inkscape::Filters::Filter(primitives);
+ }
+ SP_FILTER(style->getFilter())->build_renderer(_filter);
+ } else {
+ // no filter set for this group
+ delete _filter;
+ _filter = nullptr;
+ }
+
+ if (style && style->enable_background.set) {
+ bool _background_new_check = _background_new;
+ if (style->enable_background.value == SP_CSS_BACKGROUND_NEW) {
+ _background_new = true;
+ }
+ if (style->enable_background.value == SP_CSS_BACKGROUND_ACCUMULATE) {
+ _background_new = false;
+ }
+ if (_background_new_check != _background_new) {
+ _markForUpdate(STATE_BACKGROUND, true);
+ }
+ }
+
+ if (context_style != nullptr) {
+ _context_style = context_style;
+ } else if (_parent != nullptr) {
+ _context_style = _parent->_context_style;
+ }
+
+ _markForUpdate(STATE_ALL, false);
+}
+
+
+/**
+ * Recursively update children style.
+ * The purpose of this call is to update fill and stroke for markers that have elements with
+ * fill/stroke property values of 'context-fill' or 'context-stroke'. Marker styling is not
+ * updated like other 'clones' as marker instances are not included the SP object tree.
+ * Note: this is a virtual function.
+ */
+void
+DrawingItem::setChildrenStyle(SPStyle* context_style)
+{
+ _context_style = context_style;
+ for (auto & i : _children) {
+ i.setChildrenStyle( context_style );
+ }
+}
+
+
+void
+DrawingItem::setClip(DrawingItem *item)
+{
+ _markForRendering();
+ delete _clip;
+ _clip = item;
+ if (item) {
+ item->_parent = this;
+ assert(item->_child_type == CHILD_ORPHAN);
+ item->_child_type = CHILD_CLIP;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
+void
+DrawingItem::setMask(DrawingItem *item)
+{
+ _markForRendering();
+ delete _mask;
+ _mask = item;
+ if (item) {
+ item->_parent = this;
+ assert(item->_child_type == CHILD_ORPHAN);
+ item->_child_type = CHILD_MASK;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
+void
+DrawingItem::setFillPattern(DrawingPattern *pattern)
+{
+ _markForRendering();
+ delete _fill_pattern;
+ _fill_pattern = pattern;
+ if (pattern) {
+ pattern->_parent = this;
+ assert(pattern->_child_type == CHILD_ORPHAN);
+ pattern->_child_type = CHILD_FILL_PATTERN;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
+void
+DrawingItem::setStrokePattern(DrawingPattern *pattern)
+{
+ _markForRendering();
+ delete _stroke_pattern;
+ _stroke_pattern = pattern;
+ if (pattern) {
+ pattern->_parent = this;
+ assert(pattern->_child_type == CHILD_ORPHAN);
+ pattern->_child_type = CHILD_STROKE_PATTERN;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
+/// Move this item to the given place in the Z order of siblings.
+/// Does nothing if the item has no parent.
+void
+DrawingItem::setZOrder(unsigned z)
+{
+ if (!_parent) return;
+
+ ChildrenList::iterator it = _parent->_children.iterator_to(*this);
+ _parent->_children.erase(it);
+
+ ChildrenList::iterator i = _parent->_children.begin();
+ std::advance(i, std::min(z, unsigned(_parent->_children.size())));
+ _parent->_children.insert(i, *this);
+ _markForRendering();
+}
+
+void
+DrawingItem::setItemBounds(Geom::OptRect const &bounds)
+{
+ _item_bbox = bounds;
+}
+
+/**
+ * Update derived data before operations.
+ * The purpose of this call is to recompute internal data which depends
+ * on the attributes of the object, but is not directly settable by the user.
+ * Precomputing this data speeds up later rendering, because some items
+ * can be omitted.
+ *
+ * Currently this method handles updating the visual and geometric bounding boxes
+ * in pixels, storing the total transformation from item space to the screen
+ * and cache invalidation.
+ *
+ * @param area Area to which the update should be restricted. Only takes effect
+ * if the bounding box is known.
+ * @param ctx A structure to store cascading state.
+ * @param flags Which internal data should be recomputed. This can be any combination
+ * of StateFlags.
+ * @param reset State fields that should be reset before processing them. This is
+ * a means to force a recomputation of internal data even if the item
+ * considers it up to date. Mainly for internal use, such as
+ * propagating bounding box recomputation to children when the item's
+ * transform changes.
+ */
+void
+DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ bool render_filters = _drawing.renderFilters();
+ bool outline = _drawing.outline();
+
+ // Set reset flags according to propagation status
+ reset |= _propagate_state;
+ _propagate_state = 0;
+
+ _state &= ~reset; // reset state of this item
+
+ if ((~_state & flags) == 0) return; // nothing to do
+
+ // TODO this might be wrong
+ if (_state & STATE_BBOX) {
+ // we have up-to-date bbox
+ if (!area.intersects(outline ? _bbox : _drawbox)) return;
+ }
+
+ // compute which elements need an update
+ unsigned to_update = _state ^ flags;
+
+ // this needs to be called before we recurse into children
+ if (to_update & STATE_BACKGROUND) {
+ _background_accumulate = _background_new;
+ if (_child_type == CHILD_NORMAL && _parent->_background_accumulate)
+ _background_accumulate = true;
+ }
+
+ UpdateContext child_ctx(ctx);
+ if (_transform) {
+ child_ctx.ctm = *_transform * ctx.ctm;
+ }
+
+ // Vector effects
+ if (_style) {
+
+ if (_style->vector_effect.fixed) {
+ child_ctx.ctm.setTranslation(Geom::Point(0,0));
+ }
+
+ if (_style->vector_effect.size) {
+ double value = sqrt(child_ctx.ctm.det());
+ if (value > 0 ) {
+ child_ctx.ctm[0] = child_ctx.ctm[0]/value;
+ child_ctx.ctm[1] = child_ctx.ctm[1]/value;
+ child_ctx.ctm[2] = child_ctx.ctm[2]/value;
+ child_ctx.ctm[3] = child_ctx.ctm[3]/value;
+ }
+ }
+
+ if (_style->vector_effect.rotate) {
+ double value = sqrt(child_ctx.ctm.det());
+ child_ctx.ctm[0] = value;
+ child_ctx.ctm[1] = 0;
+ child_ctx.ctm[2] = 0;
+ child_ctx.ctm[3] = value;
+ }
+ }
+
+ /* Remember the transformation matrix */
+ Geom::Affine ctm_change = _ctm.inverse() * child_ctx.ctm;
+ _ctm = child_ctx.ctm;
+
+ // update _bbox and call this function for children
+ _state = _updateItem(area, child_ctx, flags, reset);
+
+ if (to_update & STATE_BBOX) {
+ // compute drawbox
+ if (_filter && render_filters) {
+ Geom::OptRect enlarged = _filter->filter_effect_area(_item_bbox);
+ if (enlarged) {
+ *enlarged *= ctm();
+ _drawbox = enlarged->roundOutwards();
+ } else {
+ _drawbox = Geom::OptIntRect();
+ }
+ } else {
+ _drawbox = _bbox;
+ }
+
+ // Clipping
+ if (_clip) {
+ _clip->update(area, child_ctx, flags, reset);
+ if (outline) {
+ _bbox.unionWith(_clip->_bbox);
+ } else {
+ _drawbox.intersectWith(_clip->_bbox);
+ }
+ }
+ // Masking
+ if (_mask) {
+ _mask->update(area, child_ctx, flags, reset);
+ if (outline) {
+ _bbox.unionWith(_mask->_bbox);
+ } else {
+ // for masking, we need full drawbox of mask
+ _drawbox.intersectWith(_mask->_drawbox);
+ }
+ }
+ }
+ if (to_update & STATE_CACHE) {
+ // Update cache score for this item
+ if (_has_cache_iterator) {
+ // remove old score information
+ _drawing._candidate_items.erase(_cache_iterator);
+ _has_cache_iterator = false;
+ }
+ double score = _cacheScore();
+ if (score >= _drawing._cache_score_threshold) {
+ CacheRecord cr;
+ cr.score = score;
+ // if _cacheRect() is empty, a negative score will be returned from _cacheScore(),
+ // so this will not execute (cache score threshold must be positive)
+ cr.cache_size = _cacheRect()->area() * 4;
+ cr.item = this;
+ _drawing._candidate_items.push_front(cr);
+ _cache_iterator = _drawing._candidate_items.begin();
+ _has_cache_iterator = true;
+ }
+
+ /* Update cache if enabled.
+ * General note: here we only tell the cache how it has to transform
+ * during the render phase. The transformation is deferred because
+ * after the update the item can have its caching turned off,
+ * e.g. because its filter was removed. This way we avoid tempoerarily
+ * using more memory than the cache budget */
+ if (_cache) {
+ Geom::OptIntRect cl = _cacheRect();
+ if (_visible && cl && _has_cache_iterator) { // never create cache for invisible items
+ // this takes care of invalidation on transform
+ _cache->scheduleTransform(*cl, ctm_change);
+ } else {
+ // Destroy cache for this item - outside of canvas or invisible.
+ // The opposite transition (invisible -> visible or object
+ // entering the canvas) is handled during the render phase
+ setCached(false, true);
+ }
+ }
+ }
+
+ if (to_update & STATE_RENDER) {
+ // now that we know drawbox, dirty the corresponding rect on canvas
+ // unless filtered, groups do not need to render by themselves, only their members
+ if (_fill_pattern) {
+ _fill_pattern->update(area, child_ctx, flags, reset);
+ }
+ if (_stroke_pattern) {
+ _stroke_pattern->update(area, child_ctx, flags, reset);
+ }
+ if (!is_drawing_group(this) || (_filter && render_filters)) {
+ _markForRendering();
+ }
+ }
+}
+
+struct MaskLuminanceToAlpha {
+ guint32 operator()(guint32 in) {
+ guint r = 0, g = 0, b = 0;
+ Display::ExtractRGB32(in, r, g, b);
+ // the operation of unpremul -> luminance-to-alpha -> multiply by alpha
+ // is equivalent to luminance-to-alpha on premultiplied color values
+ // original computation in double: r*0.2125 + g*0.7154 + b*0.0721
+ guint32 ao = r*109 + g*366 + b*37; // coeffs add up to 512
+ return ((ao + 256) << 15) & 0xff000000; // equivalent to ((ao + 256) / 512) << 24
+ }
+};
+
+/**
+ * Rasterize items.
+ * This method submits the drawing operations required to draw this item
+ * to the supplied DrawingContext, restricting drawing the specified area.
+ *
+ * This method does some common tasks and calls the item-specific rendering
+ * function, _renderItem(), to render e.g. paths or bitmaps.
+ *
+ * @param flags Rendering options. This deals mainly with cache control.
+ */
+unsigned
+DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
+{
+ bool outline = _drawing.outline();
+ bool render_filters = _drawing.renderFilters();
+ // stop_at is handled in DrawingGroup, but this check is required to handle the case
+ // where a filtered item with background-accessing filter has enable-background: new
+ if (this == stop_at) {
+ return RENDER_STOP;
+ }
+
+ // If we are invisible, return immediately
+ if (!_visible) {
+ return RENDER_OK;
+ }
+
+ if (_ctm.isSingular(1e-18)) {
+ return RENDER_OK;
+ }
+
+ // TODO convert outline rendering to a separate virtual function
+ if (outline) {
+ _renderOutline(dc, area, flags);
+ return RENDER_OK;
+ }
+
+ // carea is the area to paint
+ Geom::OptIntRect carea = Geom::intersect(area, _drawbox);
+
+ if (!carea) {
+ return RENDER_OK;
+ }
+ // iarea is the bounding box for intermediate rendering
+ // Note 1: Pixels inside iarea but outside carea are invalid
+ // (incomplete filter dependence region).
+ // Note 2: We only need to render carea of clip and mask, but
+ // iarea of the object.
+
+ Geom::OptIntRect iarea = carea;
+ // expand carea to contain the dependent area of filters.
+ if (_filter && render_filters) {
+ iarea = _cacheRect();
+ if (!iarea) {
+ iarea = carea;
+ _filter->area_enlarge(*iarea, this);
+ iarea.intersectWith(_drawbox);
+ setCached(false, true);
+ } else {
+ setCached(true, true);
+ }
+ }
+
+ if (!iarea) {
+ return RENDER_OK;
+ }
+
+ // Device scale for HiDPI screens (typically 1 or 2)
+ int device_scale = dc.surface()->device_scale();
+
+ switch(_antialias){
+ case 0:
+ cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_NONE);
+ break;
+ case 1:
+ cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_FAST);
+ break;
+ case 2:
+ cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_GOOD);
+ break;
+ case 3:
+ cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_BEST);
+ break;
+ default: // should not happen
+ g_assert_not_reached();
+ }
+
+ // Render from cache if possible
+ // Bypass in case of pattern, see below.
+ if (_cached && !(flags & RENDER_BYPASS_CACHE)) {
+ if (_cache) {
+ _cache->prepare();
+ dc.setOperator(ink_css_blend_to_cairo_operator(_mix_blend_mode));
+ _cache->paintFromCache(dc, carea, _filter && render_filters);
+ if (!carea) {
+ dc.setSource(0, 0, 0, 0);
+ return RENDER_OK;
+ }
+ } else {
+ // There is no cache. This could be because caching of this item
+ // was just turned on after the last update phase, or because
+ // we were previously outside of the canvas.
+ if (iarea) {
+ _cache = new DrawingCache(*iarea, device_scale);
+ }
+ }
+ } else {
+ // if our caching was turned off after the last update, it was already
+ // deleted in setCached()
+ }
+
+ // determine whether this shape needs intermediate rendering.
+ bool needs_intermediate_rendering = false;
+ bool &nir = needs_intermediate_rendering;
+ bool needs_opacity = (_opacity < 0.995);
+
+ // this item needs an intermediate rendering if:
+ nir |= (_clip != nullptr); // 1. it has a clipping path
+ nir |= (_mask != nullptr); // 2. it has a mask
+ nir |= (_filter != nullptr && render_filters); // 3. it has a filter
+ nir |= needs_opacity; // 4. it is non-opaque
+ nir |= (_mix_blend_mode != SP_CSS_BLEND_NORMAL); // 5. it has blend mode
+ nir |= (_isolation == SP_CSS_ISOLATION_ISOLATE); // 6. it is isolated
+ nir |= !parent(); // 7. is root, need isolation from background
+ if (_prev_nir && !needs_intermediate_rendering) {
+ setCached(false, true);
+ }
+ _prev_nir = needs_intermediate_rendering;
+ nir |= (_cache != nullptr); // 5. it is to be cached
+
+ /* How the rendering is done.
+ *
+ * Clipping, masking and opacity are done by rendering them to a surface
+ * and then compositing the object's rendering onto it with the IN operator.
+ * The object itself is rendered to a group.
+ *
+ * Opacity is done by rendering the clipping path with an alpha
+ * value corresponding to the opacity. If there is no clipping path,
+ * the entire intermediate surface is painted with alpha corresponding
+ * to the opacity value.
+ *
+ */
+ // Short-circuit the simple case.
+ // We also use this path for filter background rendering, because masking, clipping,
+ // filters and opacity do not apply when rendering the ancestors of the filtered
+ // element
+
+ if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) {
+ dc.setOperator(ink_css_blend_to_cairo_operator(SP_CSS_BLEND_NORMAL));
+ return _renderItem(dc, *iarea, flags & ~RENDER_FILTER_BACKGROUND, stop_at);
+ }
+
+
+ DrawingSurface intermediate(*iarea, device_scale);
+ DrawingContext ict(intermediate);
+
+ // This path fails for patterns/hatches when stepping the pattern to handle overflows.
+ // The offsets are applied to drawing context (dc) but they are not copied to the
+ // intermediate context. Something like this is needed:
+ // Copy cairo matrix from dc to intermediate, needed for patterns/hatches
+ // cairo_matrix_t cairo_matrix;
+ // cairo_get_matrix(dc.raw(), &cairo_matrix);
+ // cairo_set_matrix(ict.raw(), &cairo_matrix);
+ // For the moment we disable caching for patterns,
+ // see https://gitlab.com/inkscape/inkscape/-/issues/309
+
+ unsigned render_result = RENDER_OK;
+
+ // 1. Render clipping path with alpha = opacity.
+ ict.setSource(0,0,0,_opacity);
+ // Since clip can be combined with opacity, the result could be incorrect
+ // for overlapping clip children. To fix this we use the SOURCE operator
+ // instead of the default OVER.
+ ict.setOperator(CAIRO_OPERATOR_SOURCE);
+ ict.paint();
+ if (_clip) {
+ ict.pushGroup();
+ _clip->clip(ict, *carea); // fixme: carea or area?
+ ict.popGroupToSource();
+ ict.setOperator(CAIRO_OPERATOR_IN);
+ ict.paint();
+ }
+ ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default
+
+ // 2. Render the mask if present and compose it with the clipping path + opacity.
+ if (_mask) {
+ ict.pushGroup();
+ _mask->render(ict, *carea, flags);
+
+ cairo_surface_t *mask_s = ict.rawTarget();
+ // Convert mask's luminance to alpha
+ ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha());
+ ict.popGroupToSource();
+ ict.setOperator(CAIRO_OPERATOR_IN);
+ ict.paint();
+ ict.setOperator(CAIRO_OPERATOR_OVER);
+ }
+
+ // 3. Render object itself
+ ict.pushGroup();
+ render_result = _renderItem(ict, *iarea, flags, stop_at);
+
+ // 4. Apply filter.
+ if (_filter && render_filters) {
+ bool rendered = false;
+ if (_filter->uses_background() && _background_accumulate) {
+ DrawingItem *bg_root = this;
+ for (; bg_root; bg_root = bg_root->_parent) {
+ if (bg_root->_background_new) break;
+ }
+ if (bg_root) {
+ DrawingSurface bg(*iarea, device_scale);
+ DrawingContext bgdc(bg);
+ bg_root->render(bgdc, *iarea, flags | RENDER_FILTER_BACKGROUND, this);
+ _filter->render(this, ict, &bgdc);
+ rendered = true;
+ }
+ }
+ if (!rendered) {
+ _filter->render(this, ict, nullptr);
+ }
+ // Note that because the object was rendered to a group,
+ // the internals of the filter need to use cairo_get_group_target()
+ // instead of cairo_get_target().
+ }
+
+ // 5. Render object inside the composited mask + clip
+ ict.popGroupToSource();
+ ict.setOperator(CAIRO_OPERATOR_IN);
+ ict.paint();
+
+ // 6. Paint the completed rendering onto the base context (or into cache)
+ if (_cached && _cache) {
+ DrawingContext cachect(*_cache);
+ cachect.rectangle(*iarea);
+ cachect.setOperator(CAIRO_OPERATOR_SOURCE);
+ cachect.setSource(&intermediate);
+ cachect.fill();
+ Geom::OptIntRect cl = _cacheRect();
+ if (_filter && render_filters && cl) {
+ _cache->markClean(*cl);
+ } else {
+ _cache->markClean(*iarea);
+ }
+ }
+
+ dc.rectangle(*carea);
+ dc.setSource(&intermediate);
+ // 7. Render blend mode
+ dc.setOperator(ink_css_blend_to_cairo_operator(_mix_blend_mode));
+ dc.fill();
+ dc.setSource(0,0,0,0);
+ // Web isolation only works if parent doesnt have transform
+
+
+ // the call above is to clear a ref on the intermediate surface held by dc
+
+ return render_result;
+}
+
+void
+DrawingItem::_renderOutline(DrawingContext &dc, Geom::IntRect const &area, unsigned flags)
+{
+ // intersect with bbox rather than drawbox, as we want to render things outside
+ // of the clipping path as well
+ Geom::OptIntRect carea = Geom::intersect(area, _bbox);
+ if (!carea) return;
+
+ // just render everything: item, clip, mask
+ // First, render the object itself
+ _renderItem(dc, *carea, flags, nullptr);
+
+ // render clip and mask, if any
+ guint32 saved_rgba = _drawing.outlinecolor; // save current outline color
+ // render clippath as an object, using a different color
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (_clip) {
+ _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips
+ _clip->render(dc, *carea, flags);
+ }
+ // render mask as an object, using a different color
+ if (_mask) {
+ _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks
+ _mask->render(dc, *carea, flags);
+ }
+ _drawing.outlinecolor = saved_rgba; // restore outline color
+}
+
+/**
+ * Rasterize the clipping path.
+ * This method submits drawing operations required to draw a basic filled shape
+ * of the item to the supplied drawing context. Rendering is limited to the
+ * given area. The rendering of the clipped object is composited into
+ * the result of this call using the IN operator. See the implementation
+ * of render() for details.
+ */
+void
+DrawingItem::clip(Inkscape::DrawingContext &dc, Geom::IntRect const &area)
+{
+ // don't bother if the object does not implement clipping (e.g. DrawingImage)
+ if (!_canClip()) return;
+ if (!_visible) return;
+ if (!area.intersects(_bbox)) return;
+
+ dc.setSource(0,0,0,1);
+ dc.pushGroup();
+ // rasterize the clipping path
+ _clipItem(dc, area);
+ if (_clip) {
+ // The item used as the clipping path itself has a clipping path.
+ // Render this item's clipping path onto a temporary surface, then composite it
+ // with the item using the IN operator
+ dc.pushGroup();
+ _clip->clip(dc, area);
+ dc.popGroupToSource();
+ dc.setOperator(CAIRO_OPERATOR_IN);
+ dc.paint();
+ }
+ dc.popGroupToSource();
+ dc.setOperator(CAIRO_OPERATOR_OVER);
+ dc.paint();
+ dc.setSource(0,0,0,0);
+}
+
+/**
+ * Get the item under the specified point.
+ * Searches the tree for the first item in the Z-order which is closer than
+ * @a delta to the given point. The pick should be visual - for example
+ * an object with a thick stroke should pick on the entire area of the stroke.
+ * @param p Search point
+ * @param delta Maximum allowed distance from the point
+ * @param sticky Whether the pick should ignore visibility and sensitivity.
+ * When false, only visible and sensitive objects are considered.
+ * When true, invisible and insensitive objects can also be picked.
+ */
+DrawingItem *
+DrawingItem::pick(Geom::Point const &p, double delta, unsigned flags)
+{
+ // Sometimes there's no BBOX in state, reason unknown (bug 992817)
+ // I made this not an assert to remove the warning
+ if (!(_state & STATE_BBOX) || !(_state & STATE_PICK)) {
+ g_warning("Invalid state when picking: STATE_BBOX = %d, STATE_PICK = %d",
+ _state & STATE_BBOX, _state & STATE_PICK);
+ return nullptr;
+ }
+ // ignore invisible and insensitive items unless sticky
+ if (!(flags & PICK_STICKY) && !(_visible && _sensitive))
+ return nullptr;
+
+ bool outline = _drawing.outline() || _drawing.getOutlineSensitive();
+
+ if (!_drawing.outline() && !_drawing.getOutlineSensitive()) {
+ // pick inside clipping path; if NULL, it means the object is clipped away there
+ if (_clip) {
+ DrawingItem *cpick = _clip->pick(p, delta, flags | PICK_AS_CLIP);
+ if (!cpick) return nullptr;
+ }
+ // same for mask
+ if (_mask) {
+ DrawingItem *mpick = _mask->pick(p, delta, flags);
+ if (!mpick) return nullptr;
+ }
+ }
+
+ Geom::OptIntRect box = (outline || (flags & PICK_AS_CLIP)) ? _bbox : _drawbox;
+ if (!box) {
+ return nullptr;
+ }
+
+ Geom::Rect expanded = *box;
+ expanded.expandBy(delta);
+ DrawingGlyphs *dglyps = dynamic_cast<DrawingGlyphs *>(this);
+ if (dglyps && !(flags & PICK_AS_CLIP)) {
+ expanded = (Geom::Rect)dglyps->getPickBox();
+ }
+
+ if (expanded.contains(p)) {
+ return _pickItem(p, delta, flags);
+ }
+ return nullptr;
+}
+
+// For debugging
+Glib::ustring
+DrawingItem::name()
+{
+ if (_item) {
+ if (_item->getId())
+ return _item->getId();
+ else
+ return "No object id";
+ } else {
+ return "No associated object";
+ }
+}
+
+// For debugging: Print drawing tree structure.
+void
+DrawingItem::recursivePrintTree( unsigned level )
+{
+ if (level == 0) {
+ std::cout << "Display Item Tree" << std::endl;
+ }
+ std::cout << "DI: ";
+ for (unsigned i = 0; i < level; ++i) {
+ std::cout << " ";
+ }
+ std::cout << name() << std::endl;
+ for (auto & i : _children) {
+ i.recursivePrintTree( level+1 );
+ }
+}
+
+
+/**
+ * Marks the current visual bounding box of the item for redrawing.
+ * This is called whenever the object changes its visible appearance.
+ * For some cases (such as setting opacity) this is enough, but for others
+ * _markForUpdate() also needs to be called.
+ */
+void
+DrawingItem::_markForRendering()
+{
+ // TODO: this function does too much work when a large subtree
+ // is invalidated - fix
+
+ bool outline = _drawing.outline();
+ Geom::OptIntRect dirty = outline ? _bbox : _drawbox;
+ if (!dirty) return;
+
+ // dirty the caches of all parents
+ DrawingItem *bkg_root = nullptr;
+
+ for (DrawingItem *i = this; i; i = i->_parent) {
+ if (i != this && i->_filter) {
+ i->_filter->area_enlarge(*dirty, i);
+ }
+ if (i->_cache) {
+ i->_cache->markDirty(*dirty);
+ }
+ if (i->_background_accumulate) {
+ bkg_root = i;
+ }
+ }
+
+ if (bkg_root && bkg_root->_parent && bkg_root->_parent->_parent) {
+ bkg_root->_invalidateFilterBackground(*dirty);
+ }
+ _drawing.signal_request_render.emit(*dirty);
+}
+
+void
+DrawingItem::_invalidateFilterBackground(Geom::IntRect const &area)
+{
+ if (!_drawbox.intersects(area)) return;
+
+ if (_cache && _filter && _filter->uses_background()) {
+ _cache->markDirty(area);
+ }
+
+ for (auto & i : _children) {
+ i._invalidateFilterBackground(area);
+ }
+}
+
+/**
+ * Marks the item as needing a recomputation of internal data.
+ *
+ * This mechanism avoids traversing the entire rendering tree (which could be vast)
+ * on every trivial state changed in any item. Only items marked as needing
+ * an update (having some bits in their _state unset) will be traversed
+ * during the update call.
+ *
+ * The _propagate variable is another optimization. We use it to specify that
+ * all children should also have the corresponding flags unset before checking
+ * whether they need to be traversed. This way there is one less traversal
+ * of the tree. Without this we would need to unset state bits in all children.
+ * With _propagate we do this during the update call, when we have to recurse
+ * into children anyway.
+ */
+void
+DrawingItem::_markForUpdate(unsigned flags, bool propagate)
+{
+ if (propagate) {
+ _propagate_state |= flags;
+ }
+
+ if (_state & flags) {
+ unsigned oldstate = _state;
+ _state &= ~flags;
+ if (oldstate != _state && _parent) {
+ // If we actually reset anything in state, recurse on the parent.
+ _parent->_markForUpdate(flags, false);
+ } else {
+ // If nothing changed, it means our ancestors are already invalidated
+ // up to the root. Do not bother recursing, because it won't change anything.
+ // Also do this if we are the root item, because we have no more ancestors
+ // to invalidate.
+ _drawing.signal_request_update.emit(this);
+ }
+ }
+}
+
+/**
+ * Compute the caching score.
+ *
+ * Higher scores mean the item is more aggressively prioritized for automatic
+ * caching by Inkscape::Drawing.
+ */
+double
+DrawingItem::_cacheScore()
+{
+ Geom::OptIntRect cache_rect = _cacheRect();
+ if (!cache_rect) return -1.0;
+ // a crude first approximation:
+ // the basic score is the number of pixels in the drawbox
+ double score = cache_rect->area();
+ // this is multiplied by the filter complexity and its expansion
+ if (_filter &&_drawing.renderFilters()) {
+ score *= _filter->complexity(_ctm);
+ Geom::IntRect ref_area = Geom::IntRect::from_xywh(0, 0, 16, 16);
+ Geom::IntRect test_area = ref_area;
+ Geom::IntRect limit_area(0, INT_MIN, 16, INT_MAX);
+ _filter->area_enlarge(test_area, this);
+ // area_enlarge never shrinks the rect, so the result of intersection below
+ // must be non-empty
+ score *= double((test_area & limit_area)->area()) / ref_area.area();
+ }
+ // if the object is clipped, add 1/2 of its bbox pixels
+ if (_clip && _clip->_bbox) {
+ score += _clip->_bbox->area() * 0.5;
+ }
+ // if masked, add mask score
+ if (_mask) {
+ score += _mask->_cacheScore();
+ }
+ //g_message("caching score: %f", score);
+ return score;
+}
+
+inline void expandByScale(Geom::IntRect &rect, double scale)
+{
+ double fraction = (scale - 1) / 2;
+ rect.expandBy(rect.width() * fraction, rect.height() * fraction);
+}
+
+
+Geom::OptIntRect DrawingItem::_cacheRect()
+{
+ Geom::OptIntRect r = _drawbox & _drawing.cacheLimit();
+ if (_filter && _drawing.cacheLimit() && _drawing.renderFilters() && r && r != _drawbox) {
+ // we check unfiltered item is emought inside the cache area to render properly
+ Geom::OptIntRect canvas = r;
+ expandByScale(*canvas, 0.5);
+ Geom::OptIntRect valid = Geom::intersect(canvas, _bbox);
+ if (!valid) {
+ valid = _bbox;
+ // contract the item _bbox to get reduced size to render. $ seems good enought
+ expandByScale(*valid, 0.5);
+ // now we get the nearest point to cache area
+ Geom::IntPoint center = (*_drawing.cacheLimit()).midpoint();
+ Geom::IntPoint nearest = (*valid).nearestEdgePoint(center);
+ r.expandTo(nearest);
+ }
+ return _drawbox & r;
+ }
+ return r;
+}
+
+} // end 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/display/drawing-item.h b/src/display/drawing-item.h
new file mode 100644
index 0000000..1cd677b
--- /dev/null
+++ b/src/display/drawing-item.h
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Canvas item belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_H
+
+#include <2geom/rect.h>
+#include <2geom/affine.h>
+#include <boost/operators.hpp>
+#include <boost/utility.hpp>
+#include <boost/intrusive/list.hpp>
+#include <exception>
+#include <list>
+
+#include "style-enums.h"
+
+namespace Glib {
+class ustring;
+}
+
+class SPStyle;
+class SPItem;
+
+namespace Inkscape {
+
+class Drawing;
+class DrawingCache;
+class DrawingContext;
+class DrawingItem;
+class DrawingPattern;
+
+namespace Filters {
+
+class Filter;
+
+} // namespace Filters
+
+
+
+struct UpdateContext {
+ Geom::Affine ctm;
+};
+
+struct CacheRecord
+ : boost::totally_ordered<CacheRecord>
+{
+ bool operator<(CacheRecord const &other) const { return score < other.score; }
+ bool operator==(CacheRecord const &other) const { return score == other.score; }
+ operator DrawingItem *() const { return item; }
+ double score;
+ size_t cache_size;
+ DrawingItem *item;
+};
+typedef std::list<CacheRecord> CacheList;
+
+class InvalidItemException : public std::exception {
+ const char *what() const noexcept override {
+ return "Invalid item in drawing";
+ }
+};
+
+class DrawingItem
+ : boost::noncopyable
+{
+public:
+ enum RenderFlags {
+ RENDER_DEFAULT = 0,
+ RENDER_CACHE_ONLY = 1,
+ RENDER_BYPASS_CACHE = 2,
+ RENDER_FILTER_BACKGROUND = 4
+ };
+ enum StateFlags {
+ STATE_NONE = 0,
+ STATE_BBOX = (1<<0), // bounding boxes are up-to-date
+ STATE_CACHE = (1<<1), // cache extents and clean area are up-to-date
+ STATE_PICK = (1<<2), // can process pick requests
+ STATE_RENDER = (1<<3), // can be rendered
+ STATE_BACKGROUND = (1<<4), // filter background data is up to date
+ STATE_ALL = (1<<5)-1
+ };
+ enum PickFlags {
+ PICK_NORMAL = 0, // normal pick
+ PICK_STICKY = (1<<0), // sticky pick - ignore visibility and sensitivity
+ PICK_AS_CLIP = (1<<2) // pick with no stroke and opaque fill regardless of item style
+ };
+
+ DrawingItem(Drawing &drawing);
+ virtual ~DrawingItem();
+
+ Geom::OptIntRect geometricBounds() const { return _bbox; }
+ Geom::OptIntRect visualBounds() const { return _drawbox; }
+ Geom::OptRect itemBounds() const { return _item_bbox; }
+ Geom::Affine ctm() const { return _ctm; }
+ Geom::Affine transform() const { return _transform ? *_transform : Geom::identity(); }
+ Drawing &drawing() const { return _drawing; }
+ DrawingItem *parent() const;
+ bool isAncestorOf(DrawingItem *item) const;
+
+ void appendChild(DrawingItem *item);
+ void prependChild(DrawingItem *item);
+ void clearChildren();
+
+ bool visible() const { return _visible; }
+ void setVisible(bool v);
+ bool sensitive() const { return _sensitive; }
+ void setSensitive(bool v);
+ bool cached() const { return _cached; }
+ void setCached(bool c, bool persistent = false);
+
+ virtual void setStyle(SPStyle *style, SPStyle *context_style = nullptr);
+ virtual void setChildrenStyle(SPStyle *context_style);
+ void setOpacity(float opacity);
+ void setAntialiasing(unsigned a);
+ void setIsolation(bool isolation); // CSS Compositing and Blending
+ void setBlendMode(SPBlendMode blend_mode);
+ void setTransform(Geom::Affine const &trans);
+ void setClip(DrawingItem *item);
+ void setMask(DrawingItem *item);
+ void setFillPattern(DrawingPattern *pattern);
+ void setStrokePattern(DrawingPattern *pattern);
+ void setZOrder(unsigned z);
+ void setItemBounds(Geom::OptRect const &bounds);
+ void setFilterBounds(Geom::OptRect const &bounds);
+
+ void setKey(unsigned key) { _key = key; }
+ unsigned key() const { return _key; }
+ void setItem(SPItem *item) { _item = item; }
+ SPItem* getItem() const { return _item; } // SPItem
+
+
+ void update(Geom::IntRect const &area = Geom::IntRect::infinite(), UpdateContext const &ctx = UpdateContext(), unsigned flags = STATE_ALL, unsigned reset = 0);
+ unsigned render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags = 0, DrawingItem *stop_at = nullptr);
+ void clip(DrawingContext &dc, Geom::IntRect const &area);
+ DrawingItem *pick(Geom::Point const &p, double delta, unsigned flags = 0);
+
+ virtual Glib::ustring name(); // For debugging
+ void recursivePrintTree(unsigned level = 0); // For debugging
+
+protected:
+ enum ChildType {
+ CHILD_ORPHAN = 0, // no parent - implies _parent == NULL
+ CHILD_NORMAL = 1, // contained in _children of parent
+ CHILD_CLIP = 2, // referenced by _clip member of parent
+ CHILD_MASK = 3, // referenced by _mask member of parent
+ CHILD_ROOT = 4, // root item of _drawing
+ CHILD_FILL_PATTERN = 5, // referenced by fill pattern of parent
+ CHILD_STROKE_PATTERN = 6 // referenced by stroke pattern of parent
+ };
+ enum RenderResult {
+ RENDER_OK = 0,
+ RENDER_STOP = 1
+ };
+ void _renderOutline(DrawingContext &dc, Geom::IntRect const &area, unsigned flags);
+ void _markForUpdate(unsigned state, bool propagate);
+ void _markForRendering();
+ void _invalidateFilterBackground(Geom::IntRect const &area);
+ double _cacheScore();
+ Geom::OptIntRect _cacheRect();
+ virtual unsigned _updateItem(Geom::IntRect const &/*area*/, UpdateContext const &/*ctx*/,
+ unsigned /*flags*/, unsigned /*reset*/) { return 0; }
+ virtual unsigned _renderItem(DrawingContext &/*dc*/, Geom::IntRect const &/*area*/, unsigned /*flags*/,
+ DrawingItem * /*stop_at*/) { return RENDER_OK; }
+ virtual void _clipItem(DrawingContext &/*dc*/, Geom::IntRect const &/*area*/) {}
+ virtual DrawingItem *_pickItem(Geom::Point const &/*p*/, double /*delta*/, unsigned /*flags*/) { return nullptr; }
+ virtual bool _canClip() { return false; }
+
+ // member variables start here
+
+ Drawing &_drawing;
+ DrawingItem *_parent;
+
+ typedef boost::intrusive::list_member_hook<> ListHook;
+ ListHook _child_hook;
+
+ typedef boost::intrusive::list<
+ DrawingItem,
+ boost::intrusive::member_hook<DrawingItem, ListHook, &DrawingItem::_child_hook>
+ > ChildrenList;
+ ChildrenList _children;
+
+ unsigned _key; ///< Some SPItems can have more than one DrawingItem;
+ /// this value is a hack used to distinguish between them
+ SPStyle *_style; // Not used by DrawingGlyphs
+ SPStyle *_context_style; // Used for 'context-fill', 'context-stroke'
+
+ float _opacity;
+
+ Geom::Affine *_transform; ///< Incremental transform from parent to this item's coords
+ Geom::Affine _ctm; ///< Total transform from item coords to display coords
+ Geom::OptIntRect _bbox; ///< Bounding box in display (pixel) coords including stroke
+ Geom::OptIntRect _drawbox; ///< Full visual bounding box - enlarged by filters, shrunk by clips and masks
+ Geom::OptRect _item_bbox; ///< Geometric bounding box in item's user space.
+ /// This is used to compute the filter effect region and render in
+ /// objectBoundingBox units.
+
+ DrawingItem *_clip;
+ DrawingItem *_mask;
+ DrawingPattern *_fill_pattern;
+ DrawingPattern *_stroke_pattern;
+ Inkscape::Filters::Filter *_filter;
+ SPItem *_item; ///< Used to associate DrawingItems with SPItems that created them
+ DrawingCache *_cache;
+ bool _prev_nir;
+
+ CacheList::iterator _cache_iterator;
+
+ unsigned _state : 8;
+ unsigned _propagate_state : 8;
+ unsigned _child_type : 3; // see ChildType enum
+ unsigned _background_new : 1; ///< Whether enable-background: new is set for this element
+ unsigned _background_accumulate : 1; ///< Whether this element accumulates background
+ /// (has any ancestor with enable-background: new)
+ unsigned _visible : 1;
+ unsigned _sensitive : 1; ///< Whether this item responds to events
+ unsigned _cached : 1; ///< Whether the rendering is stored for reuse
+ unsigned _cached_persistent : 1; ///< If set, will always be cached regardless of score
+ unsigned _has_cache_iterator : 1; ///< If set, _cache_iterator is valid
+ unsigned _propagate : 1; ///< Whether to call update for all children on next update
+ //unsigned _renders_opacity : 1; ///< Whether object needs temporary surface for opacity
+ unsigned _pick_children : 1; ///< For groups: if true, children are returned from pick(),
+ /// otherwise the group is returned
+ unsigned _antialias : 2; ///< antialiasing level (NONE/FAST/GOOD(DEFAULT)/BEST)
+
+ bool _isolation : 1;
+ SPBlendMode _mix_blend_mode;
+
+ friend class Drawing;
+};
+
+struct DeleteDisposer {
+ void operator()(DrawingItem *item) { delete item; }
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-pattern.cpp b/src/display/drawing-pattern.cpp
new file mode 100644
index 0000000..90bad03
--- /dev/null
+++ b/src/display/drawing-pattern.cpp
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Canvas belonging to SVG pattern.
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-pattern.h"
+#include "display/drawing-surface.h"
+
+namespace Inkscape {
+
+DrawingPattern::DrawingPattern(Drawing &drawing, bool debug)
+ : DrawingGroup(drawing)
+ , _pattern_to_user(nullptr)
+ , _overflow_steps(1)
+ , _debug(debug)
+{
+}
+
+DrawingPattern::~DrawingPattern()
+{
+ delete _pattern_to_user; // delete NULL; is safe
+}
+
+void
+DrawingPattern::setPatternToUserTransform(Geom::Affine const &new_trans) {
+ Geom::Affine current;
+ if (_pattern_to_user) {
+ current = *_pattern_to_user;
+ }
+
+ if (!Geom::are_near(current, new_trans, 1e-18)) {
+ // mark the area where the object was for redraw.
+ _markForRendering();
+ if (new_trans.isIdentity()) {
+ delete _pattern_to_user; // delete NULL; is safe
+ _pattern_to_user = nullptr;
+ } else {
+ _pattern_to_user = new Geom::Affine(new_trans);
+ }
+ _markForUpdate(STATE_ALL, true);
+ }
+}
+
+void
+DrawingPattern::setTileRect(Geom::Rect const &tile_rect) {
+ _tile_rect = tile_rect;
+}
+
+void
+DrawingPattern::setOverflow(Geom::Affine initial_transform, int steps, Geom::Affine step_transform) {
+ _overflow_initial_transform = initial_transform;
+ _overflow_steps = steps;
+ _overflow_step_transform = step_transform;
+}
+
+cairo_pattern_t *
+DrawingPattern::renderPattern(float opacity) {
+ bool needs_opacity = (1.0 - opacity) >= 1e-3;
+ bool visible = opacity >= 1e-3;
+
+ if (!visible) {
+ return nullptr;
+ }
+
+ if (!_tile_rect || (_tile_rect->area() == 0)) {
+ return nullptr;
+ }
+ Geom::Rect pattern_tile = *_tile_rect;
+
+ //TODO: If pattern_to_user set it to identity transform
+
+ // The DrawingSurface class handles the mapping from "logical space"
+ // (coordinates in the rendering) to "physical space" (surface pixels).
+ // An oversampling is done as the pattern may not pixel align with the final surface.
+ // The cairo surface is created when the DrawingContext is declared.
+ // Create drawing surface with size of pattern tile (in pattern space) but with number of pixels
+ // based on required resolution (c).
+ Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);
+ Inkscape::DrawingContext dc(pattern_surface);
+ dc.transform( pattern_surface.drawingTransform().inverse() );
+
+ pattern_tile *= pattern_surface.drawingTransform();
+ Geom::IntRect one_tile = pattern_tile.roundOutwards();
+
+ // Render pattern.
+ if (needs_opacity) {
+ dc.pushGroup(); // this group is for pattern + opacity
+ }
+
+ if (_debug) {
+ dc.setSource(0.8, 0.0, 0.8);
+ dc.paint();
+ }
+
+ //FIXME: What flags to choose?
+ if (_overflow_steps == 1) {
+ render(dc, one_tile, RENDER_DEFAULT);
+ } else {
+ //Overflow transforms need to be transformed to the new coordinate system
+ //introduced by dc.transform( pattern_surface.drawingTransform().inverse() );
+ Geom::Affine dt = pattern_surface.drawingTransform();
+ Geom::Affine idt = pattern_surface.drawingTransform().inverse();
+ Geom::Affine initial_transform = idt * _overflow_initial_transform * dt;
+ Geom::Affine step_transform = idt * _overflow_step_transform * dt;
+ dc.transform(initial_transform);
+ for (int i = 0; i < _overflow_steps; i++) {
+ // render() fails to handle transforms applied here when using cache.
+ render(dc, one_tile, RENDER_BYPASS_CACHE);
+ dc.transform(step_transform);
+ // cairo_surface_t* raw = pattern_surface.raw();
+ // std::string filename = "drawing-pattern" + std::to_string(i) + ".png";
+ // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );
+ }
+ }
+
+ // Uncomment to debug
+ // cairo_surface_t* raw = pattern_surface.raw();
+ // std::cout << " cairo_surface (sp-pattern): "
+ // << " width: " << cairo_image_surface_get_width( raw )
+ // << " height: " << cairo_image_surface_get_height( raw )
+ // << std::endl;
+ // std::string filename = "drawing-pattern.png";
+ // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );
+
+ if (needs_opacity) {
+ dc.popGroupToSource(); // pop raw pattern
+ dc.paint(opacity); // apply opacity
+ }
+
+ cairo_pattern_t *cp = cairo_pattern_create_for_surface(pattern_surface.raw());
+ // Apply transformation to user space. Also compensate for oversampling.
+ if (_pattern_to_user) {
+ ink_cairo_pattern_set_matrix(cp, _pattern_to_user->inverse() * pattern_surface.drawingTransform());
+ } else {
+ ink_cairo_pattern_set_matrix(cp, pattern_surface.drawingTransform());
+ }
+
+ if (_debug) {
+ cairo_pattern_set_extend(cp, CAIRO_EXTEND_NONE);
+ } else {
+ cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
+ }
+
+ return cp;
+}
+
+// TODO investigate if area should be used.
+unsigned DrawingPattern::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ UpdateContext pattern_ctx;
+
+ if (!_tile_rect || (_tile_rect->area() == 0)) {
+ return STATE_NONE;
+ }
+
+ Geom::Rect pattern_tile = *_tile_rect;
+ Geom::Coord det_ctm = ctx.ctm.descrim();
+ Geom::Coord det_ps2user = _pattern_to_user ? _pattern_to_user->descrim() : 1.0;
+ Geom::Coord det_child_transform = _child_transform ? _child_transform->descrim() : 1.0;
+ const double oversampling = 2.0;
+ double scale = det_ctm*det_ps2user*det_child_transform * oversampling;
+ //FIXME: When scale is too big (zooming in a hatch), cairo doesn't render the pattern
+ //More precisely it fails when setting pattern matrix in DrawingPattern::renderPattern
+ //Fully correct solution should make use of visible area bbox and change hatch tile rect
+ //accordingly
+ if (scale > 25) {
+ scale = 25;
+ }
+ Geom::Point c(pattern_tile.dimensions()*scale*oversampling);
+ _pattern_resolution = c.ceil();
+
+ Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);
+
+ pattern_ctx.ctm = pattern_surface.drawingTransform();
+ return DrawingGroup::_updateItem(Geom::IntRect::infinite(), pattern_ctx, flags, reset);
+}
+
+} // end 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/display/drawing-pattern.h b/src/display/drawing-pattern.h
new file mode 100644
index 0000000..86b881a
--- /dev/null
+++ b/src/display/drawing-pattern.h
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Canvas belonging to SVG pattern.
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_H
+
+#include "display/drawing-group.h"
+
+typedef struct _cairo_pattern cairo_pattern_t;
+
+namespace Inkscape {
+
+/**
+ * @brief Drawing tree node used for rendering paints.
+ *
+ * DrawingPattern is used for rendering patterns and hatches.
+ *
+ * It renders it's children to a cairo_pattern_t structure that can be
+ * applied as source for fill or stroke operations.
+ */
+class DrawingPattern
+ : public DrawingGroup
+{
+public:
+ DrawingPattern(Drawing &drawing, bool debug = false);
+ ~DrawingPattern() override;
+
+ /**
+ * Set the transformation from pattern to user coordinate systems.
+ * @see SPPattern description for explanation of coordinate systems.
+ */
+ void setPatternToUserTransform(Geom::Affine const &new_trans);
+ /**
+ * Set the tile rect position and dimensions in content coordinate system
+ */
+ void setTileRect(Geom::Rect const &tile_rect);
+ /**
+ * Turn on overflow rendering.
+ *
+ * Overflow is implemented as repeated rendering of pattern contents. In every step
+ * a translation transform is applied.
+ */
+ void setOverflow(Geom::Affine initial_transform, int steps, Geom::Affine step_transform);
+ /**
+ * Render the pattern.
+ *
+ * Returns caito_pattern_t structure that can be set as source surface.
+ */
+ cairo_pattern_t *renderPattern(float opacity);
+protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+
+ Geom::Affine *_pattern_to_user;
+ Geom::Affine _overflow_initial_transform;
+ Geom::Affine _overflow_step_transform;
+ int _overflow_steps;
+ Geom::OptRect _tile_rect;
+ bool _debug;
+ Geom::IntPoint _pattern_resolution;
+};
+
+bool is_drawing_group(DrawingItem *item);
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_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/display/drawing-shape.cpp b/src/display/drawing-shape.cpp
new file mode 100644
index 0000000..aa46a70
--- /dev/null
+++ b/src/display/drawing-shape.cpp
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Shape (styled path) belonging to an SVG drawing.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm.h>
+#include <2geom/curves.h>
+#include <2geom/pathvector.h>
+#include <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+
+#include "display/cairo-utils.h"
+#include "display/canvas-arena.h"
+#include "display/canvas-bpath.h"
+#include "display/curve.h"
+#include "display/drawing.h"
+#include "display/drawing-context.h"
+#include "display/drawing-group.h"
+#include "display/drawing-shape.h"
+#include "helper/geom-curves.h"
+#include "helper/geom.h"
+#include "preferences.h"
+#include "style.h"
+#include "svg/svg.h"
+
+namespace Inkscape {
+
+DrawingShape::DrawingShape(Drawing &drawing)
+ : DrawingItem(drawing)
+ , _curve(nullptr)
+ , _last_pick(nullptr)
+ , _repick_after(0)
+{}
+
+DrawingShape::~DrawingShape()
+{
+ if (_curve)
+ _curve->unref();
+}
+
+void
+DrawingShape::setPath(SPCurve *curve)
+{
+ _markForRendering();
+
+ if (_curve) {
+ _curve->unref();
+ _curve = nullptr;
+ }
+ if (curve) {
+ _curve = curve;
+ curve->ref();
+ }
+
+ _markForUpdate(STATE_ALL, false);
+}
+
+void
+DrawingShape::setStyle(SPStyle *style, SPStyle *context_style)
+{
+ DrawingItem::setStyle(style, context_style); // Must be first
+ _nrstyle.set(_style, _context_style);
+}
+
+void
+DrawingShape::setChildrenStyle(SPStyle* context_style)
+{
+ DrawingItem::setChildrenStyle( context_style );
+ _nrstyle.set(_style, _context_style);
+}
+
+unsigned
+DrawingShape::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ Geom::OptRect boundingbox;
+
+ unsigned beststate = STATE_ALL;
+
+ // update markers
+ for (auto & i : _children) {
+ i.update(area, ctx, flags, reset);
+ }
+
+ if (!(flags & STATE_RENDER)) {
+ /* We do not have to create rendering structures */
+ if (flags & STATE_BBOX) {
+ if (_curve) {
+ boundingbox = bounds_exact_transformed(_curve->get_pathvector(), ctx.ctm);
+ if (boundingbox) {
+ _bbox = boundingbox->roundOutwards();
+ } else {
+ _bbox = Geom::OptIntRect();
+ }
+ }
+ if (beststate & STATE_BBOX) {
+ for (auto & i : _children) {
+ _bbox.unionWith(i.geometricBounds());
+ }
+ }
+ }
+ return (flags | _state);
+ }
+
+ boundingbox = Geom::OptRect();
+ bool outline = _drawing.outline();
+
+ // clear Cairo data to force update
+ _nrstyle.update();
+
+ if (_curve) {
+ boundingbox = bounds_exact_transformed(_curve->get_pathvector(), ctx.ctm);
+
+ if (boundingbox && (_nrstyle.stroke.type != NRStyle::PAINT_NONE || outline)) {
+ float width, scale;
+ scale = ctx.ctm.descrim();
+ width = std::max(0.125f, _nrstyle.stroke_width * scale);
+ if ( fabs(_nrstyle.stroke_width * scale) > 0.01 ) { // FIXME: this is always true
+ boundingbox->expandBy(width);
+ }
+ // those pesky miters, now
+ float miterMax = width * _nrstyle.miter_limit;
+ if ( miterMax > 0.01 ) {
+ // grunt mode. we should compute the various miters instead
+ // (one for each point on the curve)
+ boundingbox->expandBy(miterMax);
+ }
+ }
+ }
+
+ _bbox = boundingbox ? boundingbox->roundOutwards() : Geom::OptIntRect();
+
+ if (!_curve ||
+ !_style ||
+ _curve->is_empty())
+ {
+ return STATE_ALL;
+ }
+
+ if (beststate & STATE_BBOX) {
+ for (auto & i : _children) {
+ _bbox.unionWith(i.geometricBounds());
+ }
+ }
+ return STATE_ALL;
+}
+
+void
+DrawingShape::_renderFill(DrawingContext &dc)
+{
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+
+ bool has_fill = _nrstyle.prepareFill(dc, _item_bbox, _fill_pattern);
+
+ if( has_fill ) {
+ dc.path(_curve->get_pathvector());
+ _nrstyle.applyFill(dc);
+ dc.fillPreserve();
+ dc.newPath(); // clear path
+ }
+}
+
+void
+DrawingShape::_renderStroke(DrawingContext &dc)
+{
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+
+ bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox, _stroke_pattern);
+ has_stroke &= (_nrstyle.stroke_width != 0);
+
+ if( has_stroke ) {
+ // TODO: remove segments outside of bbox when no dashes present
+ dc.path(_curve->get_pathvector());
+ if (_style && _style->vector_effect.stroke) {
+ dc.restore();
+ dc.save();
+ }
+ _nrstyle.applyStroke(dc);
+
+ // If the draw mode is set to visible hairlines, don't let them get smaller than half a
+ // pixel.
+ if (_drawing.visibleHairlines()) {
+ double half_pixel_size = 0.5, trash = 0.5;
+ dc.device_to_user_distance(half_pixel_size, trash);
+ if (_nrstyle.stroke_width < half_pixel_size) {
+ dc.setLineWidth(half_pixel_size);
+ }
+ }
+
+ dc.strokePreserve();
+ dc.newPath(); // clear path
+ }
+}
+
+void
+DrawingShape::_renderMarkers(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
+{
+ // marker rendering
+ for (auto & i : _children) {
+ i.render(dc, area, flags, stop_at);
+ }
+}
+
+unsigned
+DrawingShape::_renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
+{
+ if (!_curve || !_style) return RENDER_OK;
+ if (!area.intersects(_bbox)) return RENDER_OK; // skip if not within bounding box
+
+ bool outline = _drawing.outline();
+
+ if (outline) {
+ guint32 rgba = _drawing.outlinecolor;
+
+ // paint-order doesn't matter
+ { Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ dc.path(_curve->get_pathvector());
+ }
+ { Inkscape::DrawingContext::Save save(dc);
+ dc.setSource(rgba);
+ dc.setLineWidth(0.5);
+ dc.setTolerance(0.5);
+ dc.stroke();
+ }
+
+ _renderMarkers(dc, area, flags, stop_at);
+ return RENDER_OK;
+
+ }
+
+ if( _nrstyle.paint_order_layer[0] == NRStyle::PAINT_ORDER_NORMAL ) {
+ // This is the most common case, special case so we don't call get_pathvector(), etc. twice
+
+ {
+ // we assume the context has no path
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+
+
+ // update fill and stroke paints.
+ // this cannot be done during nr_arena_shape_update, because we need a Cairo context
+ // to render svg:pattern
+ bool has_fill = _nrstyle.prepareFill(dc, _item_bbox, _fill_pattern);
+ bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox, _stroke_pattern);
+ has_stroke &= (_nrstyle.stroke_width != 0);
+ if (has_fill || has_stroke) {
+ dc.path(_curve->get_pathvector());
+ // TODO: remove segments outside of bbox when no dashes present
+ if (has_fill) {
+ _nrstyle.applyFill(dc);
+ dc.fillPreserve();
+ }
+ if (_style && _style->vector_effect.stroke) {
+ dc.restore();
+ dc.save();
+ }
+ if (has_stroke) {
+ _nrstyle.applyStroke(dc);
+
+ // If the draw mode is set to visible hairlines, don't let anything get smaller
+ // than half a pixel.
+ if (_drawing.visibleHairlines()) {
+ double half_pixel_size = 0.5, trash = 0.5;
+ dc.device_to_user_distance(half_pixel_size, trash);
+ if (_nrstyle.stroke_width < half_pixel_size) {
+ dc.setLineWidth(half_pixel_size);
+ }
+ }
+
+ dc.strokePreserve();
+ }
+ dc.newPath(); // clear path
+ } // has fill or stroke pattern
+ }
+ _renderMarkers(dc, area, flags, stop_at);
+ return RENDER_OK;
+
+ }
+
+ // Handle different paint orders
+ for (auto & i : _nrstyle.paint_order_layer) {
+ switch (i) {
+ case NRStyle::PAINT_ORDER_FILL:
+ _renderFill(dc);
+ break;
+ case NRStyle::PAINT_ORDER_STROKE:
+ _renderStroke(dc);
+ break;
+ case NRStyle::PAINT_ORDER_MARKER:
+ _renderMarkers(dc, area, flags, stop_at);
+ break;
+ default:
+ // PAINT_ORDER_AUTO Should not happen
+ break;
+ }
+ }
+ return RENDER_OK;
+}
+
+void DrawingShape::_clipItem(DrawingContext &dc, Geom::IntRect const & /*area*/)
+{
+ if (!_curve) return;
+
+ Inkscape::DrawingContext::Save save(dc);
+ // handle clip-rule
+ if (_style) {
+ if (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) {
+ dc.setFillRule(CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ dc.setFillRule(CAIRO_FILL_RULE_WINDING);
+ }
+ }
+ dc.transform(_ctm);
+ dc.path(_curve->get_pathvector());
+ dc.fill();
+}
+
+DrawingItem *
+DrawingShape::_pickItem(Geom::Point const &p, double delta, unsigned flags)
+{
+ if (_repick_after > 0)
+ --_repick_after;
+
+ if (_repick_after > 0) // we are a slow, huge path
+ return _last_pick; // skip this pick, returning what was returned last time
+
+ if (!_curve) return nullptr;
+ if (!_style) return nullptr;
+
+ bool outline = _drawing.outline() || _drawing.getOutlineSensitive();
+ bool pick_as_clip = flags & PICK_AS_CLIP;
+
+ if (SP_SCALE24_TO_FLOAT(_style->opacity.value) == 0 && !outline && !pick_as_clip)
+ // fully transparent, no pick unless outline mode
+ return nullptr;
+
+
+ gint64 tstart = g_get_monotonic_time();
+
+ double width;
+ if (pick_as_clip) {
+ width = 0; // no width should be applied to clip picking
+ // this overrides display mode and stroke style considerations
+ } else if (outline) {
+ width = 0.5; // in outline mode, everything is stroked with the same 0.5px line width
+ } else if (_nrstyle.stroke.type != NRStyle::PAINT_NONE && _nrstyle.stroke.opacity > 1e-3) {
+ // for normal picking calculate the distance corresponding top the stroke width
+ // FIXME BUG: this is incorrect for transformed strokes
+ float const scale = _ctm.descrim();
+ width = std::max(0.125f, _nrstyle.stroke_width * scale) / 2;
+ } else {
+ width = 0;
+ }
+
+ double dist = Geom::infinity();
+ int wind = 0;
+ bool needfill = pick_as_clip || (_nrstyle.fill.type != NRStyle::PAINT_NONE &&
+ _nrstyle.fill.opacity > 1e-3 && !outline);
+ bool wind_evenodd = pick_as_clip ? (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) :
+ (_style->fill_rule.computed == SP_WIND_RULE_EVENODD);
+
+ // actual shape picking
+ if (_drawing.arena()) {
+ Geom::Rect viewbox = _drawing.arena()->item.canvas->getViewbox();
+ viewbox.expandBy (width);
+ pathv_matrix_point_bbox_wind_distance(_curve->get_pathvector(), _ctm, p, nullptr, needfill? &wind : nullptr, &dist, 0.5, &viewbox);
+ } else {
+ pathv_matrix_point_bbox_wind_distance(_curve->get_pathvector(), _ctm, p, nullptr, needfill? &wind : nullptr, &dist, 0.5, nullptr);
+ }
+
+ gint64 tfinish = g_get_monotonic_time();
+ gint64 this_pick = tfinish - tstart;
+ //g_print ("pick time %lu\n", this_pick);
+ if (this_pick > 10000) { // slow picking, remember to skip several new picks
+ _repick_after = this_pick / 5000;
+ }
+
+ // covered by fill?
+ if (needfill) {
+ if (wind_evenodd) {
+ if (wind & 0x1) {
+ _last_pick = this;
+ return this;
+ }
+ } else {
+ if (wind != 0) {
+ _last_pick = this;
+ return this;
+ }
+ }
+ }
+
+ // close to the edge, as defined by strokewidth and delta?
+ // this ignores dashing (as if the stroke is solid) and always works as if caps are round
+ if (needfill || width > 0) { // if either fill or stroke visible,
+ if ((dist - width) < delta) {
+ _last_pick = this;
+ return this;
+ }
+ }
+
+ // if not picked on the shape itself, try its markers
+ for (auto & i : _children) {
+ DrawingItem *ret = i.pick(p, delta, flags & ~PICK_STICKY);
+ if (ret) {
+ _last_pick = this;
+ return this;
+ }
+ }
+
+ _last_pick = nullptr;
+ return nullptr;
+}
+
+bool
+DrawingShape::_canClip()
+{
+ return true;
+}
+
+} // end 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/display/drawing-shape.h b/src/display/drawing-shape.h
new file mode 100644
index 0000000..f8a3d23
--- /dev/null
+++ b/src/display/drawing-shape.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Group belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_SHAPE_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_SHAPE_H
+
+#include "display/drawing-item.h"
+#include "display/nr-style.h"
+
+class SPStyle;
+class SPCurve;
+
+namespace Inkscape {
+
+class DrawingShape
+ : public DrawingItem
+{
+public:
+ DrawingShape(Drawing &drawing);
+ ~DrawingShape() override;
+
+ void setPath(SPCurve *curve);
+ void setStyle(SPStyle *style, SPStyle *context_style = nullptr) override;
+ void setChildrenStyle(SPStyle *context_style) override;
+
+protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+ unsigned _renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags,
+ DrawingItem *stop_at) override;
+ void _clipItem(DrawingContext &dc, Geom::IntRect const &area) override;
+ DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags) override;
+ bool _canClip() override;
+
+ void _renderFill(DrawingContext &dc);
+ void _renderStroke(DrawingContext &dc);
+ void _renderMarkers(DrawingContext &dc, Geom::IntRect const &area, unsigned flags,
+ DrawingItem *stop_at);
+
+ SPCurve *_curve;
+ NRStyle _nrstyle;
+
+ DrawingItem *_last_pick;
+ unsigned _repick_after;
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-surface.cpp b/src/display/drawing-surface.cpp
new file mode 100644
index 0000000..c84e358
--- /dev/null
+++ b/src/display/drawing-surface.cpp
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo surface that remembers its origin.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+//#include <iostream>
+#include "display/drawing-surface.h"
+#include "display/drawing-context.h"
+#include "display/cairo-utils.h"
+
+namespace Inkscape {
+
+using Geom::X;
+using Geom::Y;
+
+
+/**
+ * @class DrawingSurface
+ * Drawing surface that remembers its origin.
+ *
+ * This is a very minimalistic wrapper over cairo_surface_t. The main
+ * extra functionality provided by this class is that it automates
+ * the mapping from "logical space" (coordinates in the rendering)
+ * and the "physical space" (surface pixels). For example, patterns
+ * have to be rendered on tiles which have possibly non-integer
+ * widths and heights.
+ *
+ * This class has delayed allocation functionality - it creates
+ * the Cairo surface it wraps on the first call to createRawContext()
+ * of when a DrawingContext is constructed.
+ */
+
+/**
+ * Creates a surface with the given physical extents.
+ * When a drawing context is created for this surface, its pixels
+ * will cover the area under the given rectangle.
+ */
+DrawingSurface::DrawingSurface(Geom::IntRect const &area, int device_scale)
+ : _surface(nullptr)
+ , _origin(area.min())
+ , _scale(1, 1)
+ , _pixels(area.dimensions())
+ , _device_scale(device_scale)
+{
+ assert(_device_scale > 0);
+}
+
+/**
+ * Creates a surface with the given logical and physical extents.
+ * When a drawing context is created for this surface, its pixels
+ * will cover the area under the given rectangle. IT will contain
+ * the number of pixels specified by the second argument.
+ * @param logbox Logical extents of the surface
+ * @param pixdims Pixel dimensions of the surface.
+ */
+DrawingSurface::DrawingSurface(Geom::Rect const &logbox, Geom::IntPoint const &pixdims, int device_scale)
+ : _surface(nullptr)
+ , _origin(logbox.min())
+ , _scale(pixdims[X] / logbox.width(), pixdims[Y] / logbox.height())
+ , _pixels(pixdims)
+ , _device_scale(device_scale)
+{
+ assert(_device_scale > 0);
+}
+
+/**
+ * Wrap a cairo_surface_t.
+ * This constructor will take an extra reference on @a surface, which will
+ * be released on destruction.
+ */
+DrawingSurface::DrawingSurface(cairo_surface_t *surface, Geom::Point const &origin)
+ : _surface(surface)
+ , _origin(origin)
+ , _scale(1, 1)
+{
+ cairo_surface_reference(surface);
+
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale( surface, &x_scale, &y_scale);
+ if (x_scale != y_scale) {
+ std::cerr << "DrawingSurface::DrawingSurface: non-uniform device scale!" << std::endl;
+ }
+ _device_scale = x_scale;
+ assert(_device_scale > 0);
+
+ _pixels[X] = cairo_image_surface_get_width(surface)/_device_scale;
+ _pixels[Y] = cairo_image_surface_get_height(surface)/_device_scale;
+}
+
+DrawingSurface::~DrawingSurface()
+{
+ if (_surface)
+ cairo_surface_destroy(_surface);
+}
+
+/// Get the logical extents of the surface.
+Geom::Rect
+DrawingSurface::area() const
+{
+ Geom::Rect r = Geom::Rect::from_xywh(_origin, dimensions());
+ return r;
+}
+
+/// Get the pixel dimensions of the surface
+Geom::IntPoint
+DrawingSurface::pixels() const
+{
+ return _pixels;
+}
+
+/// Get the logical width and weight of the surface as a point.
+Geom::Point
+DrawingSurface::dimensions() const
+{
+ Geom::Point logical_dims(_pixels[X] / _scale[X], _pixels[Y] / _scale[Y]);
+ return logical_dims;
+}
+
+Geom::Point
+DrawingSurface::origin() const
+{
+ return _origin;
+}
+
+Geom::Scale
+DrawingSurface::scale() const
+{
+ return _scale;
+}
+
+/// Device scale for HiDPI screens
+int
+DrawingSurface::device_scale() const
+{
+ return _device_scale;
+}
+
+/// Get the transformation applied to the drawing context on construction.
+Geom::Affine
+DrawingSurface::drawingTransform() const
+{
+ Geom::Affine ret = Geom::Translate(-_origin) * _scale;
+ return ret;
+}
+
+cairo_surface_type_t
+DrawingSurface::type() const
+{
+ // currently hardcoded
+ return CAIRO_SURFACE_TYPE_IMAGE;
+}
+
+/// Drop contents of the surface and release the underlying Cairo object.
+void
+DrawingSurface::dropContents()
+{
+ if (_surface) {
+ cairo_surface_destroy(_surface);
+ _surface = nullptr;
+ }
+}
+
+/**
+ * Create a drawing context for this surface.
+ * It's better to use the surface constructor of DrawingContext.
+ */
+cairo_t *
+DrawingSurface::createRawContext()
+{
+ // deferred allocation
+ if (!_surface) {
+ _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ _pixels[X] * _device_scale,
+ _pixels[Y] * _device_scale);
+ cairo_surface_set_device_scale(_surface, _device_scale, _device_scale);
+ }
+ cairo_t *ct = cairo_create(_surface);
+ if (_scale != Geom::Scale::identity()) {
+ cairo_scale(ct, _scale[X], _scale[Y]);
+ }
+ cairo_translate(ct, -_origin[X], -_origin[Y]);
+ return ct;
+}
+
+Geom::IntRect
+DrawingSurface::pixelArea() const
+{
+ Geom::IntRect ret = Geom::IntRect::from_xywh(_origin.round(), _pixels);
+ return ret;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+DrawingCache::DrawingCache(Geom::IntRect const &area, int device_scale)
+ : DrawingSurface(area, device_scale)
+ , _clean_region(cairo_region_create())
+ , _pending_area(area)
+{}
+
+DrawingCache::~DrawingCache()
+{
+ cairo_region_destroy(_clean_region);
+}
+
+void
+DrawingCache::markDirty(Geom::IntRect const &area)
+{
+ cairo_rectangle_int_t dirty = _convertRect(area);
+ cairo_region_subtract_rectangle(_clean_region, &dirty);
+}
+void
+DrawingCache::markClean(Geom::IntRect const &area)
+{
+ Geom::OptIntRect r = Geom::intersect(area, pixelArea());
+ if (!r) return;
+ cairo_rectangle_int_t clean = _convertRect(*r);
+ cairo_region_union_rectangle(_clean_region, &clean);
+}
+
+/// Call this during the update phase to schedule a transformation of the cache.
+void
+DrawingCache::scheduleTransform(Geom::IntRect const &new_area, Geom::Affine const &trans)
+{
+ _pending_area = new_area;
+ _pending_transform *= trans;
+}
+
+/// Transforms the cache according to the transform specified during the update phase.
+/// Call this during render phase, before painting.
+void
+DrawingCache::prepare()
+{
+ Geom::IntRect old_area = pixelArea();
+ bool is_identity = _pending_transform.isIdentity();
+ if (is_identity && _pending_area == old_area) return; // no change
+
+ bool is_integer_translation = is_identity;
+ if (!is_identity && _pending_transform.isTranslation()) {
+ Geom::IntPoint t = _pending_transform.translation().round();
+ if (Geom::are_near(Geom::Point(t), _pending_transform.translation())) {
+ is_integer_translation = true;
+ cairo_region_translate(_clean_region, t[X], t[Y]);
+ if (old_area + t == _pending_area) {
+ // if the areas match, the only thing to do
+ // is to ensure that the clean area is not too large
+ // we can exit early
+ cairo_rectangle_int_t limit = _convertRect(_pending_area);
+ cairo_region_intersect_rectangle(_clean_region, &limit);
+ _origin += t;
+ _pending_transform.setIdentity();
+ return;
+ }
+ }
+ }
+
+ // the area has changed, so the cache content needs to be copied
+ Geom::IntPoint old_origin = old_area.min();
+ cairo_surface_t *old_surface = _surface;
+ _surface = nullptr;
+ _pixels = _pending_area.dimensions();
+ _origin = _pending_area.min();
+
+ if (is_integer_translation) {
+ // transform the cache only for integer translations and identities
+ cairo_t *ct = createRawContext();
+ if (!is_identity) {
+ ink_cairo_transform(ct, _pending_transform);
+ }
+ cairo_set_source_surface(ct, old_surface, old_origin[X], old_origin[Y]);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_pattern_set_filter(cairo_get_source(ct), CAIRO_FILTER_NEAREST);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ cairo_rectangle_int_t limit = _convertRect(_pending_area);
+ cairo_region_intersect_rectangle(_clean_region, &limit);
+ } else {
+ // dirty everything
+ cairo_region_destroy(_clean_region);
+ _clean_region = cairo_region_create();
+ }
+
+ //std::cout << _pending_transform << old_area << _pending_area << std::endl;
+ cairo_surface_destroy(old_surface);
+ _pending_transform.setIdentity();
+}
+
+/**
+ * Paints the clean area from cache and modifies the @a area
+ * parameter to the bounds of the region that must be repainted.
+ */
+void DrawingCache::paintFromCache(DrawingContext &dc, Geom::OptIntRect &area, bool is_filter)
+{
+ if (!area) return;
+
+ // We subtract the clean region from the area, then get the bounds
+ // of the resulting region. This is the area that needs to be repainted
+ // by the item.
+ // Then we subtract the area that needs to be repainted from the
+ // original area and paint the resulting region from cache.
+ cairo_rectangle_int_t area_c = _convertRect(*area);
+ cairo_region_t *dirty_region = cairo_region_create_rectangle(&area_c);
+ cairo_region_t *cache_region = cairo_region_copy(dirty_region);
+ cairo_region_subtract(dirty_region, _clean_region);
+
+ if (is_filter && !cairo_region_is_empty(dirty_region)) { // To allow fast panning on high zoom on filters
+ return;
+ }
+ if (cairo_region_is_empty(dirty_region)) {
+ area = Geom::OptIntRect();
+ } else {
+ cairo_rectangle_int_t to_repaint;
+ cairo_region_get_extents(dirty_region, &to_repaint);
+ area = _convertRect(to_repaint);
+ markDirty(*area);
+ cairo_region_subtract_rectangle(cache_region, &to_repaint);
+ }
+ cairo_region_destroy(dirty_region);
+
+ if (!cairo_region_is_empty(cache_region)) {
+ int nr = cairo_region_num_rectangles(cache_region);
+ cairo_rectangle_int_t tmp;
+ for (int i = 0; i < nr; ++i) {
+ cairo_region_get_rectangle(cache_region, i, &tmp);
+ dc.rectangle(_convertRect(tmp));
+ }
+ dc.setSource(this);
+ dc.fill();
+ }
+ cairo_region_destroy(cache_region);
+}
+
+// debugging utility
+void
+DrawingCache::_dumpCache(Geom::OptIntRect const &area)
+{
+ static int dumpnr = 0;
+ cairo_surface_t *surface = ink_cairo_surface_copy(_surface);
+ DrawingContext dc(surface, _origin);
+ if (!cairo_region_is_empty(_clean_region)) {
+ Inkscape::DrawingContext::Save save(dc);
+ int nr = cairo_region_num_rectangles(_clean_region);
+ cairo_rectangle_int_t tmp;
+ for (int i = 0; i < nr; ++i) {
+ cairo_region_get_rectangle(_clean_region, i, &tmp);
+ dc.rectangle(_convertRect(tmp));
+ }
+ dc.setSource(0,1,0,0.1);
+ dc.fill();
+ }
+ dc.rectangle(*area);
+ dc.setSource(1,0,0,0.1);
+ dc.fill();
+ char *fn = g_strdup_printf("dump%d.png", dumpnr++);
+ cairo_surface_write_to_png(surface, fn);
+ cairo_surface_destroy(surface);
+ g_free(fn);
+}
+
+cairo_rectangle_int_t
+DrawingCache::_convertRect(Geom::IntRect const &area)
+{
+ cairo_rectangle_int_t ret;
+ ret.x = area.left();
+ ret.y = area.top();
+ ret.width = area.width();
+ ret.height = area.height();
+ return ret;
+}
+
+Geom::IntRect
+DrawingCache::_convertRect(cairo_rectangle_int_t const &r)
+{
+ Geom::IntRect ret = Geom::IntRect::from_xywh(
+ r.x, r.y,
+ r.width, r.height);
+ return ret;
+}
+
+} // end 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/display/drawing-surface.h b/src/display/drawing-surface.h
new file mode 100644
index 0000000..0522b53
--- /dev/null
+++ b/src/display/drawing-surface.h
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Cairo surface that remembers its origin.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_SURFACE_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_SURFACE_H
+
+#include <cairo.h>
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+#include <2geom/transforms.h>
+
+extern "C" {
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+typedef struct _cairo_region cairo_region_t;
+}
+
+namespace Inkscape {
+class DrawingContext;
+
+class DrawingSurface
+{
+public:
+ explicit DrawingSurface(Geom::IntRect const &area, int device_scale = 1);
+ DrawingSurface(Geom::Rect const &logbox, Geom::IntPoint const &pixdims, int device_scale = 1);
+ DrawingSurface(cairo_surface_t *surface, Geom::Point const &origin);
+ virtual ~DrawingSurface();
+
+ Geom::Rect area() const;
+ Geom::IntPoint pixels() const;
+ Geom::Point dimensions() const;
+ Geom::Point origin() const;
+ Geom::Scale scale() const;
+ int device_scale() const;
+ Geom::Affine drawingTransform() const;
+ cairo_surface_type_t type() const;
+ void dropContents();
+
+ cairo_surface_t *raw() { return _surface; }
+ cairo_t *createRawContext();
+
+protected:
+ Geom::IntRect pixelArea() const;
+
+ cairo_surface_t *_surface;
+ Geom::Point _origin;
+ Geom::Scale _scale;
+ Geom::IntPoint _pixels;
+ int _device_scale; // To support HiDPI screens
+ bool _has_context;
+
+ friend class DrawingContext;
+};
+
+class DrawingCache
+ : public DrawingSurface
+{
+public:
+ explicit DrawingCache(Geom::IntRect const &area, int device_scale = 1);
+ ~DrawingCache() override;
+
+ void markDirty(Geom::IntRect const &area = Geom::IntRect::infinite());
+ void markClean(Geom::IntRect const &area = Geom::IntRect::infinite());
+ void scheduleTransform(Geom::IntRect const &new_area, Geom::Affine const &trans);
+ void prepare();
+ void paintFromCache(DrawingContext &dc, Geom::OptIntRect &area, bool is_filter);
+
+ protected:
+ cairo_region_t *_clean_region;
+ Geom::IntRect _pending_area;
+ Geom::Affine _pending_transform;
+private:
+ void _dumpCache(Geom::OptIntRect const &area);
+ static cairo_rectangle_int_t _convertRect(Geom::IntRect const &r);
+ static Geom::IntRect _convertRect(cairo_rectangle_int_t const &r);
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing-text.cpp b/src/display/drawing-text.cpp
new file mode 100644
index 0000000..4eb4224
--- /dev/null
+++ b/src/display/drawing-text.cpp
@@ -0,0 +1,758 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Group belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+//#include "display/cairo-utils.h"
+//#include "display/canvas-bpath.h" // for SPWindRule (WTF!)
+#include "display/drawing.h"
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include "display/drawing-text.h"
+#include "helper/geom.h"
+#include "libnrtype/font-instance.h"
+#include "style.h"
+#include "2geom/pathvector.h"
+
+#include "display/cairo-utils.h"
+#include "display/canvas-bpath.h"
+
+namespace Inkscape {
+
+
+DrawingGlyphs::DrawingGlyphs(Drawing &drawing)
+ : DrawingItem(drawing)
+ , _font(nullptr)
+ , _glyph(0)
+{}
+
+DrawingGlyphs::~DrawingGlyphs()
+{
+ if (_font) {
+ _font->Unref();
+ _font = nullptr;
+ }
+}
+
+void
+DrawingGlyphs::setGlyph(font_instance *font, int glyph, Geom::Affine const &trans)
+{
+ _markForRendering();
+
+ setTransform(trans);
+
+ if (font) font->Ref();
+ if (_font) _font->Unref();
+ _font = font;
+ _glyph = glyph;
+
+ _markForUpdate(STATE_ALL, false);
+}
+
+void
+DrawingGlyphs::setStyle(SPStyle * /*style*/, SPStyle * /*context_style*/)
+{
+ std::cerr << "DrawingGlyphs: Use parent style" << std::endl;
+}
+
+
+unsigned DrawingGlyphs::_updateItem(Geom::IntRect const &/*area*/, UpdateContext const &ctx, unsigned /*flags*/, unsigned /*reset*/)
+{
+ DrawingText *ggroup = dynamic_cast<DrawingText *>(_parent);
+ if (!ggroup) {
+ throw InvalidItemException();
+ }
+
+ if (!_font || !ggroup->_style) {
+ return STATE_ALL;
+ }
+
+
+ _pick_bbox = Geom::IntRect();
+ _bbox = Geom::IntRect();
+
+ /*
+ Make a bounding box for drawing that is a little taller and lower (currently 10% extra) than
+ the font's drawing box. Extra space is to hold overline or underline, if present. All
+ characters in a font use the same ascent and descent, but different widths. This lets leading
+ and trailing spaces have text decorations. If it is not done the bounding box is limited to
+ the box surrounding the drawn parts of visible glyphs only, and draws outside are ignored.
+ The box is also a hair wider than the text, since the glyphs do not always start or end at
+ the left and right edges of the box defined in the font.
+ */
+
+ float scale_bigbox = 1.0;
+ if (_transform) {
+ scale_bigbox /= _transform->descrim();
+ }
+
+ /* Because there can be text decorations the bounding box must correspond in Y to a little above the glyph's ascend
+ and a little below its descend. This leaves room for overline and underline. The left and right sides
+ come from the glyph's bounding box. Note that the initial direction of ascender is positive down in Y, and
+ this flips after the transform is applied. So change the sign on descender. 1.1 provides a little extra space
+ above and below the max/min y positions of the letters to place the text decorations.*/
+
+ Geom::Rect b;
+ if (_drawable) {
+ Geom::OptRect tiltb = bounds_exact(*_font->PathVector(_glyph));
+ if (tiltb) {
+ Geom::Rect bigbox(Geom::Point(tiltb->left(),-_dsc*scale_bigbox*1.1),Geom::Point(tiltb->right(),_asc*scale_bigbox*1.1));
+ b = bigbox * ctx.ctm;
+ }
+ }
+ if (b.hasZeroArea()) { // Fallback, spaces mostly
+ Geom::Rect bigbox(Geom::Point(0.0, -_dsc*scale_bigbox*1.1),Geom::Point(_width*scale_bigbox, _asc*scale_bigbox*1.1));
+ b = bigbox * ctx.ctm;
+ }
+
+ /*
+ The pick box matches the characters as best as it can, leaving no extra space above or below
+ for decorations. The pathvector may include spaces, and spaces have no drawable glyph.
+ Catch those and do not pass them to bounds_exact_transformed(), which crashes Inkscape if it
+ sees a nondrawable glyph. Instead mock up a pickbox for them using font characteristics.
+ There may also be some other similar white space characters in some other unforeseen context
+ which should be handled by this code as well..
+ */
+
+ Geom::OptRect pb;
+ if (_drawable) {
+ Geom::PathVector *glyphv = _font->PathVector(_glyph);
+ if (glyphv && !glyphv->empty()) {
+ pb = bounds_exact_transformed(*glyphv, ctx.ctm);
+ }
+ glyphv = _font->PathVector(42);
+ if (glyphv && !glyphv->empty()) {
+ if (pb) {
+ pb.unionWith(bounds_exact_transformed(*glyphv, ctx.ctm));
+ } else {
+ pb = bounds_exact_transformed(*glyphv, ctx.ctm);
+ }
+ pb.expandTo(Geom::Point((*pb).right() + (_width * ctx.ctm.descrim()), (*pb).bottom()));
+ }
+ }
+ if (!pb) { // Fallback
+ Geom::Rect pbigbox(Geom::Point(0.0, _asc*scale_bigbox*0.66),Geom::Point(_width*scale_bigbox, 0.0));
+ pb = pbigbox * ctx.ctm;
+ }
+
+#if 0
+ /* FIXME if this is commented out then not even an approximation of pick on decorations */
+ /* adjust the pick box up or down to include the decorations.
+ This is only approximate since at this point we don't know how wide that line is, if it has
+ an unusual offset, and so forth. The selection point is set at what is roughly the center of
+ the decoration (vertically) for the wide ones, like wavy and double line.
+ The text decorations are not actually selectable.
+ */
+ if (_decorations.overline || _decorations.underline) {
+ double top = _asc*scale_bigbox*0.66;
+ double bot = 0;
+ if (_decorations.overline) { top = _asc * scale_bigbox * 1.025; }
+ if (_decorations.underline) { bot = -_dsc * scale_bigbox * 0.2; }
+ Geom::Rect padjbox(Geom::Point(0.0, top),Geom::Point(_width*scale_bigbox, bot));
+ pb.unionWith(padjbox * ctx.ctm);
+ }
+#endif
+
+ if (ggroup->_nrstyle.stroke.type != NRStyle::PAINT_NONE) {
+ // this expands the selection box for cases where the stroke is "thick"
+ float scale = ctx.ctm.descrim();
+ if (_transform) {
+ scale /= _transform->descrim(); // FIXME temporary hack
+ }
+ float width = MAX(0.125, ggroup->_nrstyle.stroke_width * scale);
+ if ( fabs(ggroup->_nrstyle.stroke_width * scale) > 0.01 ) { // FIXME: this is always true
+ b.expandBy(0.5 * width);
+ pb->expandBy(0.5 * width);
+ }
+
+ // save bbox without miters for picking
+ _pick_bbox = pb->roundOutwards();
+
+ float miterMax = width * ggroup->_nrstyle.miter_limit;
+ if ( miterMax > 0.01 ) {
+ // grunt mode. we should compute the various miters instead
+ // (one for each point on the curve)
+ b.expandBy(miterMax);
+ }
+ _bbox = b.roundOutwards();
+ } else {
+ _bbox = b.roundOutwards();
+ _pick_bbox = pb->roundOutwards();
+ }
+ return STATE_ALL;
+}
+
+DrawingItem *DrawingGlyphs::_pickItem(Geom::Point const &p, double /*delta*/, unsigned /*flags*/)
+{
+ DrawingText *ggroup = dynamic_cast<DrawingText *>(_parent);
+ if (!ggroup) {
+ throw InvalidItemException();
+ }
+ DrawingItem *result = nullptr;
+ bool invisible = (ggroup->_nrstyle.fill.type == NRStyle::PAINT_NONE) &&
+ (ggroup->_nrstyle.stroke.type == NRStyle::PAINT_NONE);
+
+ if (_font && _bbox && (_drawing.outline() || _drawing.getOutlineSensitive() || !invisible)) {
+ // With text we take a simple approach: pick if the point is in a character bbox
+ Geom::Rect expanded(_pick_bbox);
+ // FIXME, why expand by delta? When is the next line needed?
+ // expanded.expandBy(delta);
+ if (expanded.contains(p)) {
+ result = this;
+ }
+ }
+ return result;
+}
+
+
+
+DrawingText::DrawingText(Drawing &drawing)
+ : DrawingGroup(drawing)
+{}
+
+DrawingText::~DrawingText()
+= default;
+
+void
+DrawingText::clear()
+{
+ _markForRendering();
+ _children.clear_and_dispose(DeleteDisposer());
+}
+
+bool
+DrawingText::addComponent(font_instance *font, int glyph, Geom::Affine const &trans,
+ float width, float ascent, float descent, float phase_length)
+{
+/* original, did not save a glyph for white space characters, causes problems for text-decoration
+ if (!font || !font->PathVector(glyph)) {
+ return(false);
+ }
+*/
+ if (!font)return(false);
+
+ _markForRendering();
+ DrawingGlyphs *ng = new DrawingGlyphs(_drawing);
+ ng->setGlyph(font, glyph, trans);
+ if(font->PathVector(glyph)){ ng->_drawable = true; }
+ else { ng->_drawable = false; }
+ ng->_width = width; // used especially when _drawable = false, otherwise, it is the advance of the font
+ ng->_asc = ascent; // of font, not of this one character
+ ng->_dsc = descent; // of font, not of this one character
+ ng->_pl = phase_length; // used for phase of dots, dashes, and wavy
+ appendChild(ng);
+ return(true);
+}
+
+void
+DrawingText::setStyle(SPStyle *style, SPStyle *context_style)
+{
+ DrawingGroup::setStyle(style, context_style); // Must be first
+ _nrstyle.set(_style, _context_style);
+}
+
+void
+DrawingText::setChildrenStyle(SPStyle* context_style)
+{
+ DrawingGroup::setChildrenStyle( context_style );
+ _nrstyle.set(_style, _context_style);
+}
+
+unsigned
+DrawingText::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ _nrstyle.update();
+ return DrawingGroup::_updateItem(area, ctx, flags, reset);
+}
+
+void DrawingText::decorateStyle(DrawingContext &dc, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2, double thickness)
+{
+ double wave[16]={
+ 0.000000, 0.382499, 0.706825, 0.923651, 1.000000, 0.923651, 0.706825, 0.382499,
+ 0.000000, -0.382499, -0.706825, -0.923651, -1.000000, -0.923651, -0.706825, -0.382499,
+ };
+ int dashes[16]={
+ 8, 7, 6, 5,
+ 4, 3, 2, 1,
+ -8, -7, -6, -5,
+ -4, -3, -2, -1
+ };
+ int dots[16]={
+ 4, 3, 2, 1,
+ -4, -3, -2, -1,
+ 4, 3, 2, 1,
+ -4, -3, -2, -1
+ };
+ double step = vextent/32.0;
+ unsigned i = 15 & (unsigned) round(xphase/step); // xphase is >= 0.0
+
+ /* For most spans draw the last little bit right to p2 or even a little beyond.
+ This allows decoration continuity within the line, and does not step outside the clip box off the end
+ For the first/last section on the line though, stay well clear of the edge, or when the
+ text is dragged it may "spray" pixels.
+ */
+ /* snap to nearest step in X */
+ Geom::Point ps = Geom::Point(step * round(p1[Geom::X]/step),p1[Geom::Y]);
+ Geom::Point pf = Geom::Point(step * round(p2[Geom::X]/step),p2[Geom::Y]);
+ Geom::Point poff = Geom::Point(0,thickness/2.0);
+
+ if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_ISDOUBLE){
+ ps -= Geom::Point(0, vextent/12.0);
+ pf -= Geom::Point(0, vextent/12.0);
+ dc.rectangle( Geom::Rect(ps + poff, pf - poff));
+ ps += Geom::Point(0, vextent/6.0);
+ pf += Geom::Point(0, vextent/6.0);
+ dc.rectangle( Geom::Rect(ps + poff, pf - poff));
+ }
+ /* The next three have a problem in that they are phase dependent. The bits of a line are not
+ necessarily passing through this routine in order, so we have to use the xphase information
+ to figure where in each of their cycles to start. Only accurate to 1 part in 16.
+ Huge positive offset should keep the phase calculation from ever being negative.
+ */
+ else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_DOTTED){
+ // FIXME: Per spec, this should produce round dots.
+ Geom::Point pv = ps;
+ while(true){
+ Geom::Point pvlast = pv;
+ if(dots[i]>0){
+ if(pv[Geom::X] > pf[Geom::X]) break;
+
+ pv += Geom::Point(step * (double)dots[i], 0.0);
+
+ if(pv[Geom::X]>= pf[Geom::X]){
+ // Last dot
+ dc.rectangle( Geom::Rect(pvlast + poff, pf - poff));
+ break;
+ } else {
+ dc.rectangle( Geom::Rect(pvlast + poff, pv - poff));
+ }
+
+ pv += Geom::Point(step * 4.0, 0.0);
+
+ } else {
+ pv += Geom::Point(step * -(double)dots[i], 0.0);
+ }
+ i = 0; // once in phase, it stays in phase
+ }
+ }
+ else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_DASHED){
+ Geom::Point pv = ps;
+ while(true){
+ Geom::Point pvlast = pv;
+ if(dashes[i]>0){
+ if(pv[Geom::X]> pf[Geom::X]) break;
+
+ pv += Geom::Point(step * (double)dashes[i], 0.0);
+
+ if(pv[Geom::X]>= pf[Geom::X]){
+ // Last dash
+ dc.rectangle( Geom::Rect(pvlast + poff, pf - poff));
+ break;
+ } else {
+ dc.rectangle( Geom::Rect(pvlast + poff, pv - poff));
+ }
+
+ pv += Geom::Point(step * 8.0, 0.0);
+
+ } else {
+ pv += Geom::Point(step * -(double)dashes[i], 0.0);
+ }
+ i = 0; // once in phase, it stays in phase
+ }
+ }
+ else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_WAVY){
+ double amp = vextent/10.0;
+ double x = ps[Geom::X];
+ double y = ps[Geom::Y] + poff[Geom::Y];
+ dc.moveTo(Geom::Point(x, y + amp * wave[i]));
+ while(true){
+ i = ((i + 1) & 15);
+ x += step;
+ dc.lineTo(Geom::Point(x, y + amp * wave[i]));
+ if(x >= pf[Geom::X])break;
+ }
+ y = ps[Geom::Y] - poff[Geom::Y];
+ dc.lineTo(Geom::Point(x, y + amp * wave[i]));
+ while(true){
+ i = ((i - 1) & 15);
+ x -= step;
+ dc.lineTo(Geom::Point(x, y + amp * wave[i]));
+ if(x <= ps[Geom::X])break;
+ }
+ dc.closePath();
+ }
+ else { // TEXT_DECORATION_STYLE_SOLID, also default in case it was not set for some reason
+ dc.rectangle( Geom::Rect(ps + poff, pf - poff));
+ }
+}
+
+/* returns scaled line thickness */
+void DrawingText::decorateItem(DrawingContext &dc, double phase_length, bool under)
+{
+ if ( _nrstyle.font_size <= 1.0e-32 )return; // might cause a divide by zero or overflow and nothing would be visible anyway
+ double tsp_width_adj = _nrstyle.tspan_width / _nrstyle.font_size;
+ double tsp_asc_adj = _nrstyle.ascender / _nrstyle.font_size;
+ double tsp_size_adj = (_nrstyle.ascender + _nrstyle.descender) / _nrstyle.font_size;
+
+ double final_underline_thickness = CLAMP(_nrstyle.underline_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0);
+ double final_line_through_thickness = CLAMP(_nrstyle.line_through_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0);
+
+ double xphase = phase_length/ _nrstyle.font_size; // used to figure out phase of patterns
+
+ Geom::Point p1;
+ Geom::Point p2;
+ // All lines must be the same thickness, in combinations, line_through trumps underline
+ double thickness = final_underline_thickness;
+ if ( thickness <= 1.0e-32 )return; // might cause a divide by zero or overflow and nothing would be visible anyway
+ dc.setTolerance(0.5); // Is this really necessary... could effect dots.
+
+ if( under ) {
+
+ if(_nrstyle.text_decoration_line & NRStyle::TEXT_DECORATION_LINE_UNDERLINE){
+ p1 = Geom::Point(0.0, -_nrstyle.underline_position);
+ p2 = Geom::Point(tsp_width_adj,-_nrstyle.underline_position);
+ decorateStyle(dc, tsp_size_adj, xphase, p1, p2, thickness);
+ }
+
+ if(_nrstyle.text_decoration_line & NRStyle::TEXT_DECORATION_LINE_OVERLINE){
+ p1 = Geom::Point(0.0, tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness);
+ p2 = Geom::Point(tsp_width_adj,tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness);
+ decorateStyle(dc, tsp_size_adj, xphase, p1, p2, thickness);
+ }
+
+ } else {
+ // Over
+
+ if(_nrstyle.text_decoration_line & NRStyle::TEXT_DECORATION_LINE_LINETHROUGH){
+ thickness = final_line_through_thickness;
+ p1 = Geom::Point(0.0, _nrstyle.line_through_position);
+ p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position);
+ decorateStyle(dc, tsp_size_adj, xphase, p1, p2, thickness);
+ }
+
+ // Obviously this does not blink, but it does indicate which text has been set with that attribute
+ if(_nrstyle.text_decoration_line & NRStyle::TEXT_DECORATION_LINE_BLINK){
+ thickness = final_line_through_thickness;
+ p1 = Geom::Point(0.0, _nrstyle.line_through_position - 2*final_line_through_thickness);
+ p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position - 2*final_line_through_thickness);
+ decorateStyle(dc, tsp_size_adj, xphase, p1, p2, thickness);
+ p1 = Geom::Point(0.0, _nrstyle.line_through_position + 2*final_line_through_thickness);
+ p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position + 2*final_line_through_thickness);
+ decorateStyle(dc, tsp_size_adj, xphase, p1, p2, thickness);
+ }
+ }
+}
+
+unsigned DrawingText::_renderItem(DrawingContext &dc, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/)
+{
+ if (_drawing.outline()) {
+ guint32 rgba = _drawing.outlinecolor;
+ Inkscape::DrawingContext::Save save(dc);
+ dc.setSource(rgba);
+ dc.setTolerance(0.5); // low quality, but good enough for outline mode
+
+ for (auto & i : _children) {
+ DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i);
+ if (!g) throw InvalidItemException();
+
+ Inkscape::DrawingContext::Save save(dc);
+ // skip glyphs with singular transforms
+ if (g->_ctm.isSingular()) continue;
+ dc.transform(g->_ctm);
+ if(g->_drawable){
+ dc.path(*g->_font->PathVector(g->_glyph));
+ dc.fill();
+ }
+ }
+ return RENDER_OK;
+ }
+
+ // NOTE: This is very similar to drawing-shape.cpp; the only differences are in path feeding
+ // and in applying text decorations.
+
+
+ // Do we have text decorations?
+ bool decorate = (_nrstyle.text_decoration_line != NRStyle::TEXT_DECORATION_LINE_CLEAR );
+
+ // prepareFill / prepareStroke need to be called with _ctm in effect.
+ // However, we might need to apply a different ctm for glyphs.
+ // Therefore, only apply this ctm temporarily.
+ bool has_stroke = false;
+ bool has_fill = false;
+ bool has_td_fill = false;
+ bool has_td_stroke = false;
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+
+ has_fill = _nrstyle.prepareFill( dc, _item_bbox, _fill_pattern);
+ has_stroke = _nrstyle.prepareStroke( dc, _item_bbox, _stroke_pattern);
+
+ // Avoid creating patterns if not needed
+ if( decorate ) {
+ has_td_fill = _nrstyle.prepareTextDecorationFill( dc, _item_bbox, _fill_pattern);
+ has_td_stroke = _nrstyle.prepareTextDecorationStroke(dc, _item_bbox, _stroke_pattern);
+ }
+ }
+
+ if (has_fill || has_stroke || has_td_fill || has_td_stroke) {
+
+ // Determine order for fill and stroke.
+ // Text doesn't have markers, we can do paint-order quick and dirty.
+ bool fill_first = false;
+ if( _nrstyle.paint_order_layer[0] == NRStyle::PAINT_ORDER_NORMAL ||
+ _nrstyle.paint_order_layer[0] == NRStyle::PAINT_ORDER_FILL ||
+ _nrstyle.paint_order_layer[2] == NRStyle::PAINT_ORDER_STROKE ) {
+ fill_first = true;
+ } // Won't get "stroke fill stroke" but that isn't 'valid'
+
+
+ // Determine geometry of text decoration
+ double phase_length = 0.0;
+ Geom::Affine aff;
+ if( decorate ) {
+
+ Geom::Affine rotinv;
+ bool invset = false;
+ double leftmost = DBL_MAX;
+ bool first_y = true;
+ double start_y = 0.0;
+ for (auto & i : _children) {
+
+ DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i);
+ if (!g) throw InvalidItemException();
+
+ if (!invset) {
+ rotinv = g->_ctm.withoutTranslation().inverse();
+ invset = true;
+ }
+
+ Geom::Point pt = g->_ctm.translation() * rotinv;
+ if (pt[Geom::X] < leftmost) {
+ leftmost = pt[Geom::X];
+ aff = g->_ctm;
+ phase_length = g->_pl;
+ }
+
+ // Check for text on a path. FIXME: This needs better test (and probably not here).
+ if (first_y) {
+ first_y = false;
+ start_y = pt[Geom::Y];
+ }
+ else if (fabs(pt[Geom::Y] - start_y) > 1.0e-6) {
+ // If the text has been mapped onto a path, which causes y to vary, drop the
+ // text decorations. To handle that properly would need a conformal map.
+ decorate = false;
+ }
+ }
+ }
+
+ // Draw text decorations that go UNDER the text (underline, over-line)
+ if( decorate ) {
+
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(aff); // must be leftmost affine in span
+ decorateItem(dc, phase_length, true);
+ }
+
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm); // Needed so that fill pattern rotates with text
+
+ if (has_td_fill && fill_first) {
+ _nrstyle.applyTextDecorationFill(dc);
+ dc.fillPreserve();
+ }
+
+ if (has_td_stroke) {
+ _nrstyle.applyTextDecorationStroke(dc);
+ dc.strokePreserve();
+ }
+
+ if (has_td_fill && !fill_first) {
+ _nrstyle.applyTextDecorationFill(dc);
+ dc.fillPreserve();
+ }
+
+ }
+
+ dc.newPath(); // Clear text-decoration path
+ }
+
+ // Accumulate the path that represents the glyphs and/or draw SVG glyphs.
+ for (auto & i : _children) {
+ DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i);
+ if (!g) throw InvalidItemException();
+
+ Inkscape::DrawingContext::Save save(dc);
+ if (g->_ctm.isSingular()) continue;
+ dc.transform(g->_ctm);
+ if (g->_drawable) {
+ if (g->_font->FontHasSVG()) {
+ Inkscape::Pixbuf* pixbuf = g->_font->PixBuf(g->_glyph);
+ if (pixbuf) {
+ // Geom::OptRect box = bounds_exact(*g->_font->PathVector(g->_glyph));
+ // if (box) {
+ // Inkscape::DrawingContext::Save save(dc);
+ // dc.newPath();
+ // dc.rectangle(*box);
+ // dc.setLineWidth(0.01);
+ // dc.setSource(0x8080ffff);
+ // dc.stroke();
+ // }
+ {
+ // pixbuf is in font design units, scale to embox.
+ double scale = g->_font->GetDesignUnits();
+ if (scale <= 0) scale = 1000;
+ Inkscape::DrawingContext::Save save(dc);
+ dc.translate(0, 1);
+ dc.scale(1.0/scale, -1.0/scale);
+ dc.setSource(pixbuf->getSurfaceRaw(), 0, 0);
+ dc.paint(1);
+ }
+ } else {
+ dc.path(*g->_font->PathVector(g->_glyph));
+ }
+ } else {
+ dc.path(*g->_font->PathVector(g->_glyph));
+ }
+ }
+ }
+
+ // Draw the glyphs (non-SVG glyphs).
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ if (has_fill && fill_first) {
+ _nrstyle.applyFill(dc);
+ dc.fillPreserve();
+ }
+ }
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ if (!_style || !(_style->vector_effect.stroke)) {
+ dc.transform(_ctm);
+ }
+ if (has_stroke) {
+ _nrstyle.applyStroke(dc);
+
+ // If the draw mode is set to visible hairlines, don't let anything get smaller
+ // than half a pixel.
+ if (_drawing.visibleHairlines()) {
+ double half_pixel_size = 0.5, trash = 0.5;
+ dc.device_to_user_distance(half_pixel_size, trash);
+ if (_nrstyle.stroke_width < half_pixel_size) {
+ dc.setLineWidth(half_pixel_size);
+ }
+ }
+
+ dc.strokePreserve();
+ }
+ }
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ if (has_fill && !fill_first) {
+ _nrstyle.applyFill(dc);
+ dc.fillPreserve();
+ }
+ }
+ dc.newPath(); // Clear glyphs path
+
+ // Draw text decorations that go OVER the text (line through, blink)
+ if (decorate) {
+
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(aff); // must be leftmost affine in span
+ decorateItem(dc, phase_length, false);
+ }
+
+ {
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm); // Needed so that fill pattern rotates with text
+
+ if (has_td_fill && fill_first) {
+ _nrstyle.applyTextDecorationFill(dc);
+ dc.fillPreserve();
+ }
+
+ if (has_td_stroke) {
+ _nrstyle.applyTextDecorationStroke(dc);
+ dc.strokePreserve();
+ }
+
+ if (has_td_fill && !fill_first) {
+ _nrstyle.applyTextDecorationFill(dc);
+ dc.fillPreserve();
+ }
+
+ }
+
+ dc.newPath(); // Clear text-decoration path
+ }
+
+ }
+ return RENDER_OK;
+}
+
+void DrawingText::_clipItem(DrawingContext &dc, Geom::IntRect const &/*area*/)
+{
+ Inkscape::DrawingContext::Save save(dc);
+
+ // handle clip-rule
+ if (_style) {
+ if (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) {
+ dc.setFillRule(CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ dc.setFillRule(CAIRO_FILL_RULE_WINDING);
+ }
+ }
+
+ for (auto & i : _children) {
+ DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i);
+ if (!g) {
+ throw InvalidItemException();
+ }
+
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(g->_ctm);
+ if(g->_drawable){
+ dc.path(*g->_font->PathVector(g->_glyph));
+ }
+ }
+ dc.fill();
+}
+
+DrawingItem *
+DrawingText::_pickItem(Geom::Point const &p, double delta, unsigned flags)
+{
+ return DrawingGroup::_pickItem(p, delta, flags) ? this : nullptr;
+}
+
+bool
+DrawingText::_canClip()
+{
+ return true;
+}
+
+} // end 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/display/drawing-text.h b/src/display/drawing-text.h
new file mode 100644
index 0000000..084c793
--- /dev/null
+++ b/src/display/drawing-text.h
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Group belonging to an SVG drawing element.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_TEXT_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_TEXT_H
+
+#include "display/drawing-group.h"
+#include "display/nr-style.h"
+
+class SPStyle;
+class font_instance;
+
+namespace Inkscape {
+
+class DrawingGlyphs
+ : public DrawingItem
+{
+public:
+ DrawingGlyphs(Drawing &drawing);
+ ~DrawingGlyphs() override;
+
+ void setGlyph(font_instance *font, int glyph, Geom::Affine const &trans);
+ void setStyle(SPStyle *style, SPStyle *context_style = nullptr) override; // Not to be used
+ Geom::IntRect getPickBox() const { return _pick_bbox; };
+
+ protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+ DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags) override;
+
+ font_instance *_font;
+ int _glyph;
+ bool _drawable;
+ float _width; // These three are used to set up bounding box
+ float _asc; //
+ float _dsc; //
+ float _pl; // phase length
+ Geom::IntRect _pick_bbox;
+
+ friend class DrawingText;
+};
+
+class DrawingText
+ : public DrawingGroup
+{
+public:
+ DrawingText(Drawing &drawing);
+ ~DrawingText() override;
+
+ void clear();
+ bool addComponent(font_instance *font, int glyph, Geom::Affine const &trans,
+ float width, float ascent, float descent, float phase_length);
+ void setStyle(SPStyle *style, SPStyle *context_style = nullptr) override;
+ void setChildrenStyle(SPStyle *context_style) override;
+
+protected:
+ unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset) override;
+ unsigned _renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags,
+ DrawingItem *stop_at) override;
+ void _clipItem(DrawingContext &dc, Geom::IntRect const &area) override;
+ DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags) override;
+ bool _canClip() override;
+
+ void decorateItem(DrawingContext &dc, double phase_length, bool under);
+ void decorateStyle(DrawingContext &dc, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2, double thickness);
+ NRStyle _nrstyle;
+
+ friend class DrawingGlyphs;
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_ITEM_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/display/drawing.cpp b/src/display/drawing.cpp
new file mode 100644
index 0000000..8b9de15
--- /dev/null
+++ b/src/display/drawing.cpp
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * SVG drawing for display.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright (C) 2011-2012 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <algorithm>
+#include "display/drawing.h"
+#include "nr-filter-gaussian.h"
+#include "nr-filter-types.h"
+
+//grayscale colormode:
+#include "cairo-templates.h"
+#include "drawing-context.h"
+#include "canvas-arena.h"
+
+
+namespace Inkscape {
+
+// hardcoded grayscale color matrix values as default
+static const gdouble grayscale_value_matrix[20] = {
+ 0.21, 0.72, 0.072, 0, 0,
+ 0.21, 0.72, 0.072, 0, 0,
+ 0.21, 0.72, 0.072, 0, 0,
+ 0 , 0 , 0 , 1, 0
+};
+
+Drawing::Drawing(SPCanvasArena *arena)
+ : _root(nullptr)
+ , outlinecolor(0x000000ff)
+ , delta(0)
+ , _exact(false)
+ , _outline_sensitive(true)
+ , _rendermode(RENDERMODE_NORMAL)
+ , _colormode(COLORMODE_NORMAL)
+ , _blur_quality(BLUR_QUALITY_BEST)
+ , _filter_quality(Filters::FILTER_QUALITY_BEST)
+ , _cache_score_threshold(50000.0)
+ , _cache_budget(0)
+ , _grayscale_colormatrix(std::vector<gdouble>(grayscale_value_matrix, grayscale_value_matrix + 20))
+ , _canvasarena(arena)
+{
+
+}
+
+Drawing::~Drawing()
+{
+ delete _root;
+}
+
+void
+Drawing::setRoot(DrawingItem *item)
+{
+ delete _root;
+ _root = item;
+ if (item) {
+ assert(item->_child_type == DrawingItem::CHILD_ORPHAN);
+ item->_child_type = DrawingItem::CHILD_ROOT;
+ }
+}
+
+RenderMode
+Drawing::renderMode() const
+{
+ return _exact ? RENDERMODE_NORMAL : _rendermode;
+}
+ColorMode
+Drawing::colorMode() const
+{
+ return (outline() || _exact) ? COLORMODE_NORMAL : _colormode;
+}
+bool
+Drawing::outline() const
+{
+ return renderMode() == RENDERMODE_OUTLINE;
+}
+bool
+Drawing::visibleHairlines() const
+{
+ return renderMode() == RENDERMODE_VISIBLE_HAIRLINES;
+}
+bool
+Drawing::renderFilters() const
+{
+ return renderMode() == RENDERMODE_NORMAL || renderMode() == RENDERMODE_VISIBLE_HAIRLINES;
+}
+int
+Drawing::blurQuality() const
+{
+ if (renderMode() == RENDERMODE_NORMAL) {
+ return _exact ? BLUR_QUALITY_BEST : _blur_quality;
+ } else {
+ return BLUR_QUALITY_WORST;
+ }
+}
+int
+Drawing::filterQuality() const
+{
+ if (renderMode() == RENDERMODE_NORMAL) {
+ return _exact ? Filters::FILTER_QUALITY_BEST : _filter_quality;
+ } else {
+ return Filters::FILTER_QUALITY_WORST;
+ }
+}
+
+void
+Drawing::setRenderMode(RenderMode mode)
+{
+ _rendermode = mode;
+}
+void
+Drawing::setColorMode(ColorMode mode)
+{
+ _colormode = mode;
+}
+void
+Drawing::setBlurQuality(int q)
+{
+ _blur_quality = q;
+}
+void
+Drawing::setFilterQuality(int q)
+{
+ _filter_quality = q;
+}
+void
+Drawing::setExact(bool e)
+{
+ _exact = e;
+}
+
+void Drawing::setOutlineSensitive(bool e) { _outline_sensitive = e; };
+
+Geom::OptIntRect const &
+Drawing::cacheLimit() const
+{
+ return _cache_limit;
+}
+void
+Drawing::setCacheLimit(Geom::OptIntRect const &r, bool update_cache)
+{
+ _cache_limit = r;
+ if (update_cache) {
+ for (auto _cached_item : _cached_items)
+ {
+ _cached_item->_markForUpdate(DrawingItem::STATE_CACHE, false);
+ }
+ }
+}
+void
+Drawing::setCacheBudget(size_t bytes)
+{
+ _cache_budget = bytes;
+ _pickItemsForCaching();
+}
+
+void
+Drawing::setGrayscaleMatrix(gdouble value_matrix[20]) {
+ _grayscale_colormatrix = Filters::FilterColorMatrix::ColorMatrixMatrix(
+ std::vector<gdouble> (value_matrix, value_matrix + 20) );
+}
+
+void
+Drawing::update(Geom::IntRect const &area, unsigned flags, unsigned reset)
+{
+ if (_root) {
+ auto ctx = _canvasarena ? _canvasarena->ctx : UpdateContext();
+ _root->update(area, ctx, flags, reset);
+ }
+ if ((flags & DrawingItem::STATE_CACHE) || (flags & DrawingItem::STATE_ALL)) {
+ // process the updated cache scores
+ _pickItemsForCaching();
+ }
+}
+
+void
+Drawing::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, int antialiasing)
+{
+ if (_root) {
+ int prev_a = _root->_antialias;
+ if(antialiasing >= 0)
+ _root->setAntialiasing(antialiasing);
+ _root->render(dc, area, flags);
+ _root->setAntialiasing(prev_a);
+ }
+
+ if (colorMode() == COLORMODE_GRAYSCALE) {
+ // apply grayscale filter on top of everything
+ cairo_surface_t *input = dc.rawTarget();
+ cairo_surface_t *out = ink_cairo_surface_create_identical(input);
+ ink_cairo_surface_filter(input, out, _grayscale_colormatrix);
+ Geom::Point origin = dc.targetLogicalBounds().min();
+ dc.setSource(out, origin[Geom::X], origin[Geom::Y]);
+ dc.setOperator(CAIRO_OPERATOR_SOURCE);
+ dc.paint();
+ dc.setOperator(CAIRO_OPERATOR_OVER);
+
+ cairo_surface_destroy(out);
+ }
+}
+
+DrawingItem *
+Drawing::pick(Geom::Point const &p, double delta, unsigned flags)
+{
+ if (_root) {
+ return _root->pick(p, delta, flags);
+ }
+ return nullptr;
+}
+
+void
+Drawing::_pickItemsForCaching()
+{
+ // we cache the objects with the highest score until the budget is exhausted
+ _candidate_items.sort(std::greater<CacheRecord>());
+ size_t used = 0;
+ CandidateList::iterator i;
+ for (i = _candidate_items.begin(); i != _candidate_items.end(); ++i) {
+ if (used + i->cache_size > _cache_budget) break;
+ used += i->cache_size;
+ }
+
+ std::set<DrawingItem*> to_cache;
+ for (CandidateList::iterator j = _candidate_items.begin(); j != i; ++j) {
+ j->item->setCached(true);
+ to_cache.insert(j->item);
+ }
+ // Everything which is now in _cached_items but not in to_cache must be uncached
+ // Note that calling setCached on an item modifies _cached_items
+ // TODO: find a way to avoid the set copy
+ std::set<DrawingItem*> to_uncache;
+ std::set_difference(_cached_items.begin(), _cached_items.end(),
+ to_cache.begin(), to_cache.end(),
+ std::inserter(to_uncache, to_uncache.end()));
+ for (auto j : to_uncache) {
+ j->setCached(false);
+ }
+}
+
+} // end 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/display/drawing.h b/src/display/drawing.h
new file mode 100644
index 0000000..b33c897
--- /dev/null
+++ b/src/display/drawing.h
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * SVG drawing for display.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_H
+
+#include <2geom/rect.h>
+#include <boost/operators.hpp>
+#include <boost/utility.hpp>
+#include <set>
+#include <sigc++/sigc++.h>
+
+#include "display/drawing-item.h"
+#include "display/rendermode.h"
+#include "nr-filter-colormatrix.h"
+
+typedef struct _SPCanvasArena SPCanvasArena;
+typedef unsigned int guint32;
+
+namespace Inkscape {
+
+class DrawingItem;
+
+class Drawing
+ : boost::noncopyable
+{
+public:
+ struct OutlineColors {
+ guint32 paths;
+ guint32 clippaths;
+ guint32 masks;
+ guint32 images;
+ };
+
+ Drawing(SPCanvasArena *arena = nullptr);
+ ~Drawing();
+
+ DrawingItem *root() { return _root; }
+ SPCanvasArena *arena() { return _canvasarena; }
+ void setRoot(DrawingItem *item);
+
+ RenderMode renderMode() const;
+ ColorMode colorMode() const;
+ bool outline() const;
+ bool visibleHairlines() const;
+ bool renderFilters() const;
+ int blurQuality() const;
+ int filterQuality() const;
+ void setRenderMode(RenderMode mode);
+ void setColorMode(ColorMode mode);
+ void setBlurQuality(int q);
+ void setFilterQuality(int q);
+ void setExact(bool e);
+ bool getExact() const { return _exact; };
+ void setOutlineSensitive(bool e);
+ bool getOutlineSensitive() const { return _outline_sensitive; };
+
+ Geom::OptIntRect const &cacheLimit() const;
+ void setCacheLimit(Geom::OptIntRect const &r, bool update_cache = true);
+ void setCacheBudget(size_t bytes);
+
+ OutlineColors const &colors() const { return _colors; }
+
+ void setGrayscaleMatrix(double value_matrix[20]);
+
+ void update(Geom::IntRect const &area = Geom::IntRect::infinite(), unsigned flags = DrawingItem::STATE_ALL, unsigned reset = 0);
+ void render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags = 0, int antialiasing = -1);
+ DrawingItem *pick(Geom::Point const &p, double delta, unsigned flags);
+
+ sigc::signal<void, DrawingItem *> signal_request_update;
+ sigc::signal<void, Geom::IntRect const &> signal_request_render;
+ sigc::signal<void, DrawingItem *> signal_item_deleted;
+
+private:
+ void _pickItemsForCaching();
+
+ typedef std::list<CacheRecord> CandidateList;
+ bool _outline_sensitive;
+ DrawingItem *_root;
+ std::set<DrawingItem *> _cached_items; // modified by DrawingItem::setCached()
+ CacheList _candidate_items;
+public:
+ // TODO: remove these temporarily public members
+ guint32 outlinecolor;
+ double delta;
+private:
+ bool _exact; // if true then rendering must be exact
+ RenderMode _rendermode;
+ ColorMode _colormode;
+ int _blur_quality;
+ int _filter_quality;
+ Geom::OptIntRect _cache_limit;
+
+ double _cache_score_threshold; ///< do not consider objects for caching below this score
+ size_t _cache_budget; ///< maximum allowed size of cache
+
+ OutlineColors _colors;
+ Filters::FilterColorMatrix::ColorMatrixMatrix _grayscale_colormatrix;
+ SPCanvasArena *_canvasarena; // may be NULL if this arena is not the screen
+ // but used for export etc.
+
+ friend class DrawingItem;
+};
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DRAWING_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/display/gnome-canvas-acetate.cpp b/src/display/gnome-canvas-acetate.cpp
new file mode 100644
index 0000000..5157ab8
--- /dev/null
+++ b/src/display/gnome-canvas-acetate.cpp
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Infinite invisible canvas item
+ *
+ * Author:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@acm.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 1998-1999 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gnome-canvas-acetate.h"
+#include "ui/event-debug.h"
+
+static void sp_canvas_acetate_destroy(SPCanvasItem *object);
+
+static void sp_canvas_acetate_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static double sp_canvas_acetate_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+static int sp_canvas_acetate_event( SPCanvasItem *item, GdkEvent *event);
+
+G_DEFINE_TYPE(SPCanvasAcetate, sp_canvas_acetate, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass)
+{
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ item_class->destroy = sp_canvas_acetate_destroy;
+ item_class->update = sp_canvas_acetate_update;
+ item_class->point = sp_canvas_acetate_point;
+ // item_class->event = sp_canvas_acetate_event;
+}
+
+static void sp_canvas_acetate_init (SPCanvasAcetate */*acetate*/)
+{
+ /* Nothing here */
+}
+
+static void sp_canvas_acetate_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (GNOME_IS_CANVAS_ACETATE (object));
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_acetate_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_canvas_acetate_parent_class)->destroy(object);
+}
+
+static void sp_canvas_acetate_update( SPCanvasItem *item, Geom::Affine const &/*affine*/, unsigned int /*flags*/ )
+{
+ item->x1 = -G_MAXINT;
+ item->y1 = -G_MAXINT;
+ item->x2 = G_MAXINT;
+ item->y2 = G_MAXINT;
+}
+
+static double sp_canvas_acetate_point( SPCanvasItem *item, Geom::Point /*p*/, SPCanvasItem **actual_item )
+{
+ *actual_item = item;
+
+ return 0.0;
+}
+
+// static int sp_canvas_acetate_event( SPCanvasItem *item, GdkEvent *event)
+// {
+// ui_dump_event (event, "sp_canvas_acetate_event");
+// return 0; // ?
+// }
diff --git a/src/display/gnome-canvas-acetate.h b/src/display/gnome-canvas-acetate.h
new file mode 100644
index 0000000..d08667d
--- /dev/null
+++ b/src/display/gnome-canvas-acetate.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_ACETATE_H
+#define SEEN_SP_CANVAS_ACETATE_H
+
+/*
+ * Infinite invisible canvas item
+ *
+ * Author:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@acm.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1998-1999 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/sp-canvas-item.h"
+
+#define GNOME_TYPE_CANVAS_ACETATE (sp_canvas_acetate_get_type ())
+#define SP_CANVAS_ACETATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetate))
+#define SP_CANVAS_ACETATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetateClass))
+#define GNOME_IS_CANVAS_ACETATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_ACETATE))
+#define GNOME_IS_CANVAS_ACETATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ACETATE))
+
+
+struct SPCanvasAcetate {
+ SPCanvasItem item;
+};
+
+struct SPCanvasAcetateClass {
+ SPCanvasItemClass parent_class;
+};
+
+GType sp_canvas_acetate_get_type ();
+
+#endif // SEEN_SP_CANVAS_ACETATE_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/display/grayscale.cpp b/src/display/grayscale.cpp
new file mode 100644
index 0000000..fb77dfa
--- /dev/null
+++ b/src/display/grayscale.cpp
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Provide methods to calculate grayscale values (e.g. convert rgba value to grayscale rgba value)
+ */
+
+/*
+ * Author:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2011 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/grayscale.h"
+#include "color.h"
+
+// for activeDesktopIsGrayscale:
+#include "display/rendermode.h"
+#include "inkscape.h"
+#include "desktop.h"
+
+namespace Grayscale {
+
+/* Original values from Johan:
+const float red_factor = 0.3;
+const float green_factor = 0.59;
+const float blue_factor = 0.11;
+*/
+
+// Values below are from the SVG specification
+const float red_factor = 0.2125;
+const float green_factor = 0.7154;
+const float blue_factor = 0.0721;
+
+guint32 process(guint32 rgba) {
+ return process(SP_RGBA32_R_U(rgba), SP_RGBA32_G_U(rgba), SP_RGBA32_B_U(rgba), SP_RGBA32_A_U(rgba));
+}
+
+guint32 process(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
+
+ /** To reduce banding in gradients, this calculation is tweaked a bit
+ * by outputting blue+1 or red+1 or both. The luminance is calculated
+ * times 4. Then last two bits are used to determine if red and/or blue
+ * can be increased by one. Then these two bits are discarded.
+ * So the output color it still looks gray, but has more than 256 steps.
+ * The assumption is that the eye is most sensitive to green, then red, then blue.
+ * (hope this trick works :-) Johan)
+ */
+
+ guint32 luminance = ( red_factor * (r << 3)
+ + green_factor * (g << 3)
+ + blue_factor * (b << 3) );
+ unsigned blue_plus_one = (luminance & 0x01) ? 1 : 0;
+ unsigned red_plus_one = (luminance & 0x02) ? 1 : 0;
+ unsigned green_plus_one = (luminance & 0x04) ? 1 : 0;
+ luminance = luminance >> 3;
+
+ if (luminance >= 0xff) {
+ return SP_RGBA32_U_COMPOSE(0xff, 0xff, 0xff, a);
+ } else {
+ return SP_RGBA32_U_COMPOSE(luminance + red_plus_one, luminance + green_plus_one, luminance + blue_plus_one, a);
+ }
+}
+
+unsigned char luminance(unsigned char r, unsigned char g, unsigned char b) {
+ guint32 luminance = ( red_factor * r
+ + green_factor * g
+ + blue_factor * b );
+ if (luminance > 0xff) {
+ luminance = 0xff;
+ }
+
+ return luminance & 0xff;
+}
+
+/**
+ * Use this method if there is no other way to find out if grayscale view or not.
+ *
+ * In some cases, the choice between normal or grayscale is so deep in the code hierarchy,
+ * that it is not possible to determine whether grayscale is desired or not, without using
+ * the global SP_ACTIVE_DESKTOP macro. Then use this method, so we know where the abuse is
+ * happening...
+ */
+bool activeDesktopIsGrayscale() {
+ if (SP_ACTIVE_DESKTOP) {
+ return (SP_ACTIVE_DESKTOP->getColorMode() == Inkscape::COLORMODE_GRAYSCALE);
+ }
+
+ return false;
+}
+
+
+};
+
+/*
+ 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/display/grayscale.h b/src/display/grayscale.h
new file mode 100644
index 0000000..fad1205
--- /dev/null
+++ b/src/display/grayscale.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_DISPLAY_GRAYSCALE_H
+#define SEEN_DISPLAY_GRAYSCALE_H
+
+/*
+ * Author:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2011 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+typedef unsigned int guint32;
+
+/**
+ * Provide methods to calculate grayscale values (e.g. convert rgba value to grayscale rgba value).
+ */
+namespace Grayscale {
+ guint32 process(guint32 rgba);
+ guint32 process(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+ unsigned char luminance(unsigned char r, unsigned char g, unsigned char b);
+
+ bool activeDesktopIsGrayscale();
+};
+
+#endif // !SEEN_DISPLAY_GRAYSCALE_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/display/guideline.cpp b/src/display/guideline.cpp
new file mode 100644
index 0000000..d2816f1
--- /dev/null
+++ b/src/display/guideline.cpp
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Horizontal/vertical but can also be angled line
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Johan Engelen
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2009 Maximilian Albert
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/coord.h>
+#include <2geom/transforms.h>
+#include <2geom/line.h>
+
+#include "sp-canvas-util.h"
+#include "inkscape.h"
+#include "guideline.h"
+#include "display/cairo-utils.h"
+#include "display/sp-canvas.h"
+#include "display/sodipodi-ctrl.h"
+#include "object/sp-namedview.h"
+
+static void sp_guideline_destroy(SPCanvasItem *object);
+
+static void sp_guideline_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+static double sp_guideline_point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+
+G_DEFINE_TYPE(SPGuideLine, sp_guideline, SP_TYPE_CANVAS_ITEM);
+
+static void sp_guideline_class_init(SPGuideLineClass *c)
+{
+ SPCanvasItemClass *item_class = SP_CANVAS_ITEM_CLASS(c);
+ item_class->destroy = sp_guideline_destroy;
+ item_class->update = sp_guideline_update;
+ item_class->render = sp_guideline_render;
+ item_class->point = sp_guideline_point;
+}
+
+static void sp_guideline_init(SPGuideLine *gl)
+{
+ gl->rgba = 0x0000ff7f;
+
+ gl->locked = false;
+ gl->normal_to_line = Geom::Point(0,1);
+ gl->angle = M_PI_2;
+ gl->point_on_line = Geom::Point(0,0);
+ gl->sensitive = 0;
+
+ gl->origin = nullptr;
+ gl->label = nullptr;
+}
+
+static void sp_guideline_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_GUIDELINE (object));
+
+ SPGuideLine *gl = SP_GUIDELINE(object);
+
+ if (gl->origin) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(gl->origin));
+ }
+
+ if (gl->label) {
+ g_free(gl->label);
+ }
+
+ SP_CANVAS_ITEM_CLASS(sp_guideline_parent_class)->destroy(object);
+}
+
+static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPGuideLine const *gl = SP_GUIDELINE (item);
+
+ cairo_save(buf->ct);
+ cairo_translate(buf->ct, -buf->rect.left(), -buf->rect.top());
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool xrayactive = prefs->getBool("/desktop/xrayactive", false);
+ SPDesktop * desktop = SP_ACTIVE_DESKTOP;
+ if (xrayactive && desktop) {
+ guint32 bg = desktop->getNamedView()->pagecolor;
+ guint32 _color = SP_RGBA32_F_COMPOSE(
+ CLAMP(((1 - SP_RGBA32_A_F(gl->rgba)) * SP_RGBA32_R_F(bg)) + (SP_RGBA32_A_F(gl->rgba) * SP_RGBA32_R_F(gl->rgba)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(gl->rgba)) * SP_RGBA32_G_F(bg)) + (SP_RGBA32_A_F(gl->rgba) * SP_RGBA32_G_F(gl->rgba)), 0.0, 1.0),
+ CLAMP(((1 - SP_RGBA32_A_F(gl->rgba)) * SP_RGBA32_B_F(bg)) + (SP_RGBA32_A_F(gl->rgba) * SP_RGBA32_B_F(gl->rgba)), 0.0, 1.0),
+ 1.0);
+ ink_cairo_set_source_rgba32(buf->ct, _color);
+ } else {
+ ink_cairo_set_source_rgba32(buf->ct, gl->rgba);
+ }
+ cairo_set_line_width(buf->ct, 1);
+ cairo_set_line_cap(buf->ct, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_font_size(buf->ct, 10);
+
+ // normal_dt does not have unit length
+ Geom::Point normal_dt = gl->normal_to_line * gl->affine.withoutTranslation();
+ Geom::Point point_on_line_dt = gl->point_on_line * gl->affine;
+
+ if (gl->label) {
+ int px = round(point_on_line_dt[Geom::X]);
+ int py = round(point_on_line_dt[Geom::Y]);
+ cairo_save(buf->ct);
+ cairo_translate(buf->ct, px, py);
+ cairo_rotate(buf->ct, atan2(normal_dt.cw()) + M_PI * (desktop && desktop->is_yaxisdown() ? 1 : 0));
+ cairo_translate(buf->ct, 0, -5);
+ cairo_move_to(buf->ct, 0, 0);
+ cairo_show_text(buf->ct, gl->label);
+ cairo_restore(buf->ct);
+ }
+
+ // Draw guide.
+ // Special case horizontal and vertical lines so they accurately align to the to pixels.
+
+ // Need to use floor()+0.5 such that Cairo will draw us lines with a width of a single pixel, without any aliasing.
+ // For this we need to position the lines at exactly half pixels, see https://www.cairographics.org/FAQ/#sharp_lines
+ // Must be consistent with the pixel alignment of the grid lines, see CanvasXYGrid::Render(), and the drawing of the rulers
+
+ // Don't use isHorizontal()/isVertical() as they test only exact matches.
+ if ( Geom::are_near(normal_dt[Geom::Y], 0.0) ) { // Vertical?
+
+ double position = floor(point_on_line_dt[Geom::X]) + 0.5;
+ cairo_move_to(buf->ct, position, buf->rect.top() + 0.5);
+ cairo_line_to(buf->ct, position, buf->rect.bottom() - 0.5);
+ cairo_stroke(buf->ct);
+
+ } else if ( Geom::are_near(normal_dt[Geom::X], 0.0) ) { // Horizontal?
+
+ double position = floor(point_on_line_dt[Geom::Y]) + 0.5;
+ cairo_move_to(buf->ct, buf->rect.left() + 0.5, position);
+ cairo_line_to(buf->ct, buf->rect.right() - 0.5, position);
+ cairo_stroke(buf->ct);
+
+ } else {
+
+ Geom::Line guide =
+ Geom::Line::from_origin_and_vector( point_on_line_dt, Geom::rot90(normal_dt) );
+
+ // Find intersections of guide with buf rectangle. There should be zero or two.
+ std::vector<Geom::Point> intersections;
+ for (unsigned i = 0; i < 4; ++i) {
+ Geom::LineSegment side( buf->rect.corner(i), buf->rect.corner((i+1)%4) );
+ try {
+ Geom::OptCrossing oc = Geom::intersection(guide, side);
+ if (oc) {
+ intersections.push_back( guide.pointAt((*oc).ta));
+ }
+ } catch (Geom::InfiniteSolutions) {
+ // Shouldn't happen as we have already taken care of horizontal/vertical guides.
+ std::cerr << "sp_guideline_render: Error: Infinite intersections." << std::endl;
+ }
+ }
+
+ if (intersections.size() == 2) {
+ double x0 = intersections[0][Geom::X];
+ double x1 = intersections[1][Geom::X];
+ double y0 = intersections[0][Geom::Y];
+ double y1 = intersections[1][Geom::Y];
+ cairo_move_to(buf->ct, x0, y0);
+ cairo_line_to(buf->ct, x1, y1);
+ cairo_stroke(buf->ct);
+ }
+ }
+
+ cairo_restore(buf->ct);
+}
+
+static void sp_guideline_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPGuideLine *gl = SP_GUIDELINE(item);
+
+ if ((SP_CANVAS_ITEM_CLASS(sp_guideline_parent_class))->update) {
+ (SP_CANVAS_ITEM_CLASS(sp_guideline_parent_class))->update(item, affine, flags);
+ }
+
+ if (item->visible) {
+ if (gl->locked) {
+ g_object_set(G_OBJECT(gl->origin), "stroke_color", 0x0000ff88,
+ "shape", SP_CTRL_SHAPE_CROSS,
+ "size", 7, NULL);
+ } else {
+ g_object_set(G_OBJECT(gl->origin), "stroke_color", 0xff000088,
+ "shape", SP_CTRL_SHAPE_CIRCLE,
+ "size", 5, NULL);
+ }
+ gl->origin->moveto(gl->point_on_line);
+ sp_canvas_item_request_update(SP_CANVAS_ITEM(gl->origin));
+ }
+
+ gl->affine = affine;
+
+ // Rotated canvas requires large bbox for all guides.
+ sp_canvas_update_bbox (item, -1000000, -1000000, 1000000, 1000000);
+
+}
+
+// Returns 0.0 if point is on the guideline
+static double sp_guideline_point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPGuideLine *gl = SP_GUIDELINE (item);
+
+ if (!gl->sensitive) {
+ return Geom::infinity();
+ }
+
+ *actual_item = item;
+
+ Geom::Point vec = gl->normal_to_line * gl->affine.withoutTranslation();
+ double distance = Geom::dot((p - gl->point_on_line * gl->affine), unit_vector(vec));
+ return MAX(fabs(distance)-1, 0);
+}
+
+SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, char* label, Geom::Point point_on_line, Geom::Point normal)
+{
+ SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_GUIDELINE, nullptr);
+ SPGuideLine *gl = SP_GUIDELINE(item);
+
+ normal.normalize();
+ gl->label = label;
+ gl->locked = false;
+ gl->normal_to_line = normal;
+ gl->angle = tan( -gl->normal_to_line[Geom::X] / gl->normal_to_line[Geom::Y]);
+ sp_guideline_set_position(gl, point_on_line);
+
+ gl->origin = (SPCtrl *) sp_canvas_item_new(parent, SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "mode", SP_CTRL_MODE_COLOR,
+ "filled", FALSE,
+ "stroked", TRUE,
+ "stroke_color", 0x01000000, NULL);
+ gl->origin->pickable = false;
+
+ return item;
+}
+
+void sp_guideline_set_label(SPGuideLine *gl, const char* label)
+{
+ if (gl->label) {
+ g_free(gl->label);
+ }
+ gl->label = g_strdup(label);
+
+ sp_canvas_item_request_update(SP_CANVAS_ITEM (gl));
+}
+
+void sp_guideline_set_locked(SPGuideLine *gl, const bool locked)
+{
+ gl->locked = locked;
+ sp_canvas_item_request_update(SP_CANVAS_ITEM (gl));
+}
+
+void sp_guideline_set_position(SPGuideLine *gl, Geom::Point point_on_line)
+{
+ gl->point_on_line = point_on_line;
+ sp_canvas_item_request_update(SP_CANVAS_ITEM (gl));
+}
+
+void sp_guideline_set_normal(SPGuideLine *gl, Geom::Point normal_to_line)
+{
+ gl->normal_to_line = normal_to_line;
+ gl->angle = tan( -normal_to_line[Geom::X] / normal_to_line[Geom::Y]);
+
+ sp_canvas_item_request_update(SP_CANVAS_ITEM (gl));
+}
+
+void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba)
+{
+ gl->rgba = rgba;
+ g_object_set(G_OBJECT(gl->origin), "stroke_color", rgba, NULL);
+ sp_canvas_item_request_update(SP_CANVAS_ITEM(gl));
+}
+
+void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive)
+{
+ gl->sensitive = sensitive;
+}
+
+void sp_guideline_delete(SPGuideLine *gl)
+{
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(gl));
+}
+
+/*
+ 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/display/guideline.h b/src/display/guideline.h
new file mode 100644
index 0000000..e81cb35
--- /dev/null
+++ b/src/display/guideline.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_GUIDELINE_H
+#define SEEN_SP_GUIDELINE_H
+
+/*
+ * The visual representation of SPGuide.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Johan Engelen
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ * Copyright (C) 2007 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/point.h>
+#include "sp-canvas-item.h"
+
+#define SP_TYPE_GUIDELINE (sp_guideline_get_type())
+#define SP_GUIDELINE(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_GUIDELINE, SPGuideLine))
+#define SP_IS_GUIDELINE(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_GUIDELINE))
+
+struct SPCtrl;
+
+struct SPGuideLine {
+ SPCanvasItem item;
+ Geom::Affine affine;
+
+ SPCtrl *origin; // unlike 'item', this is only held locally
+
+ guint32 rgba;
+
+ char* label;
+ bool locked;
+ Geom::Point normal_to_line;
+ Geom::Point point_on_line;
+ double angle;
+
+ unsigned int sensitive : 1;
+
+ inline bool is_horizontal() const { return (normal_to_line[Geom::X] == 0.); };
+ inline bool is_vertical() const { return (normal_to_line[Geom::Y] == 0.); };
+};
+
+struct SPGuideLineClass {
+ SPCanvasItemClass parent_class;
+};
+
+GType sp_guideline_get_type();
+
+SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, char* label, Geom::Point point_on_line, Geom::Point normal);
+
+void sp_guideline_set_label(SPGuideLine *gl, const char* label);
+void sp_guideline_set_locked(SPGuideLine *gl, const bool locked);
+void sp_guideline_set_position(SPGuideLine *gl, Geom::Point point_on_line);
+void sp_guideline_set_normal(SPGuideLine *gl, Geom::Point normal_to_line);
+void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba);
+void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive);
+void sp_guideline_delete(SPGuideLine *gl);
+
+#endif // SEEN_SP_GUIDELINE_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/display/nr-3dutils.cpp b/src/display/nr-3dutils.cpp
new file mode 100644
index 0000000..3c0eb5e
--- /dev/null
+++ b/src/display/nr-3dutils.cpp
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 3D utils.
+ *
+ * Authors:
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+
+#include "display/nr-3dutils.h"
+#include <cmath>
+#include <2geom/point.h>
+#include <2geom/affine.h>
+
+namespace NR {
+
+void convert_coord(gdouble &x, gdouble &y, gdouble &z, Geom::Affine const &trans) {
+ Geom::Point p = Geom::Point(x, y);
+ p *= trans;
+ x = p[Geom::X];
+ y = p[Geom::Y];
+ z *= trans[0];
+}
+
+gdouble norm(const Fvector &v) {
+ return sqrt(v[X_3D]*v[X_3D] + v[Y_3D]*v[Y_3D] + v[Z_3D]*v[Z_3D]);
+}
+
+void normalize_vector(Fvector &v) {
+ gdouble nv = norm(v);
+ //TODO test nv == 0
+ for (int j = 0; j < 3; j++) {
+ v[j] /= nv;
+ }
+}
+
+gdouble scalar_product(const Fvector &a, const Fvector &b) {
+ return a[X_3D] * b[X_3D] +
+ a[Y_3D] * b[Y_3D] +
+ a[Z_3D] * b[Z_3D];
+}
+
+void normalized_sum(Fvector &r, const Fvector &a, const Fvector &b) {
+ r[X_3D] = a[X_3D] + b[X_3D];
+ r[Y_3D] = a[Y_3D] + b[Y_3D];
+ r[Z_3D] = a[Z_3D] + b[Z_3D];
+ normalize_vector(r);
+}
+
+}/* namespace NR */
+
+/*
+ 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/display/nr-3dutils.h b/src/display/nr-3dutils.h
new file mode 100644
index 0000000..21fcf5f
--- /dev/null
+++ b/src/display/nr-3dutils.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_3DUTILS_H
+#define SEEN_NR_3DUTILS_H
+
+/*
+ * 3D utils. Definition of gdouble vectors of dimension 3 and of some basic
+ * functions.
+ * This looks redundant, why not just use Geom::Point for this?
+ *
+ * Authors:
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/forward.h>
+
+namespace NR {
+
+#define X_3D 0
+#define Y_3D 1
+#define Z_3D 2
+
+/**
+ * a type of 3 gdouble components vectors
+ */
+struct Fvector {
+ Fvector() {
+ v[0] = v[1] = v[2] = 0.0;
+ }
+ Fvector(double x, double y, double z) {
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+ }
+ double v[3];
+ double &operator[](unsigned i) { return v[i]; }
+ double operator[](unsigned i) const { return v[i]; }
+};
+
+/**
+ * The eye vector
+ */
+const static Fvector EYE_VECTOR(0, 0, 1);
+
+/**
+ * returns the euclidian norm of the vector v
+ *
+ * \param v a reference to a vector with double components
+ * \return the euclidian norm of v
+ */
+double norm(const Fvector &v);
+
+/**
+ * Normalizes a vector
+ *
+ * \param v a reference to a vector to normalize
+ */
+void normalize_vector(Fvector &v);
+
+/**
+ * Computes the scalar product between two Fvectors
+ *
+ * \param a a Fvector reference
+ * \param b a Fvector reference
+ * \return the scalar product of a and b
+ */
+double scalar_product(const Fvector &a, const Fvector &b);
+
+/**
+ * Computes the normalized sum of two Fvectors
+ *
+ * \param r a Fvector reference where we store the result
+ * \param a a Fvector reference
+ * \param b a Fvector reference
+ */
+void normalized_sum(Fvector &r, const Fvector &a, const Fvector &b);
+
+/**
+ * Applies the transformation matrix to (x, y, z). This function assumes that
+ * trans[0] = trans[3]. x and y are transformed according to trans, z is
+ * multiplied by trans[0].
+ *
+ * \param x a reference to a x coordinate
+ * \param y a reference to a y coordinate
+ * \param z a reference to a z coordinate
+ * \param z a reference to a transformation matrix
+ */
+void convert_coord(double &x, double &y, double &z, Geom::Affine const &trans);
+
+} /* namespace NR */
+
+#endif /* __NR_3DUTILS_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/display/nr-filter-blend.cpp b/src/display/nr-filter-blend.cpp
new file mode 100644
index 0000000..99fc2d6
--- /dev/null
+++ b/src/display/nr-filter-blend.cpp
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SVG feBlend renderer
+ *//*
+ * "This filter composites two objects together using commonly used
+ * imaging software blending modes. It performs a pixel-wise combination
+ * of two input images."
+ * http://www.w3.org/TR/SVG11/filters.html#feBlend
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2007-2008 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm.h>
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-blend.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-types.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Filters {
+
+const std::set<SPBlendMode> FilterBlend::_valid_modes {
+ SP_CSS_BLEND_NORMAL, SP_CSS_BLEND_MULTIPLY,
+ SP_CSS_BLEND_SCREEN, SP_CSS_BLEND_DARKEN,
+ SP_CSS_BLEND_LIGHTEN, SP_CSS_BLEND_OVERLAY,
+ SP_CSS_BLEND_COLORDODGE, SP_CSS_BLEND_COLORBURN,
+ SP_CSS_BLEND_HARDLIGHT, SP_CSS_BLEND_SOFTLIGHT,
+ SP_CSS_BLEND_DIFFERENCE, SP_CSS_BLEND_EXCLUSION,
+ SP_CSS_BLEND_HUE, SP_CSS_BLEND_SATURATION,
+ SP_CSS_BLEND_COLOR, SP_CSS_BLEND_LUMINOSITY
+ };
+
+FilterBlend::FilterBlend()
+ : _blend_mode(SP_CSS_BLEND_NORMAL),
+ _input2(NR_FILTER_SLOT_NOT_SET)
+{}
+
+FilterPrimitive * FilterBlend::create() {
+ return new FilterBlend();
+}
+
+FilterBlend::~FilterBlend()
+= default;
+
+static inline cairo_operator_t get_cairo_op(SPBlendMode _blend_mode)
+{
+ switch (_blend_mode) {
+ case SP_CSS_BLEND_MULTIPLY:
+ return CAIRO_OPERATOR_MULTIPLY;
+ case SP_CSS_BLEND_SCREEN:
+ return CAIRO_OPERATOR_SCREEN;
+ case SP_CSS_BLEND_DARKEN:
+ return CAIRO_OPERATOR_DARKEN;
+ case SP_CSS_BLEND_LIGHTEN:
+ return CAIRO_OPERATOR_LIGHTEN;
+ // New in CSS Compositing and Blending Level 1
+ case SP_CSS_BLEND_OVERLAY:
+ return CAIRO_OPERATOR_OVERLAY;
+ case SP_CSS_BLEND_COLORDODGE:
+ return CAIRO_OPERATOR_COLOR_DODGE;
+ case SP_CSS_BLEND_COLORBURN:
+ return CAIRO_OPERATOR_COLOR_BURN;
+ case SP_CSS_BLEND_HARDLIGHT:
+ return CAIRO_OPERATOR_HARD_LIGHT;
+ case SP_CSS_BLEND_SOFTLIGHT:
+ return CAIRO_OPERATOR_SOFT_LIGHT;
+ case SP_CSS_BLEND_DIFFERENCE:
+ return CAIRO_OPERATOR_DIFFERENCE;
+ case SP_CSS_BLEND_EXCLUSION:
+ return CAIRO_OPERATOR_EXCLUSION;
+ case SP_CSS_BLEND_HUE:
+ return CAIRO_OPERATOR_HSL_HUE;
+ case SP_CSS_BLEND_SATURATION:
+ return CAIRO_OPERATOR_HSL_SATURATION;
+ case SP_CSS_BLEND_COLOR:
+ return CAIRO_OPERATOR_HSL_COLOR;
+ case SP_CSS_BLEND_LUMINOSITY:
+ return CAIRO_OPERATOR_HSL_LUMINOSITY;
+
+ case SP_CSS_BLEND_NORMAL:
+ default:
+ return CAIRO_OPERATOR_OVER;
+ }
+}
+
+void FilterBlend::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input1 = slot.getcairo(_input);
+ cairo_surface_t *input2 = slot.getcairo(_input2);
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+ set_cairo_surface_ci( input1, ci_fp );
+ set_cairo_surface_ci( input2, ci_fp );
+
+ // input2 is the "background" image
+ // out should be ARGB32 if any of the inputs is ARGB32
+ cairo_surface_t *out = ink_cairo_surface_create_output(input1, input2);
+ set_cairo_surface_ci( out, ci_fp );
+
+ ink_cairo_surface_blit(input2, out);
+ cairo_t *out_ct = cairo_create(out);
+ cairo_set_source_surface(out_ct, input1, 0, 0);
+
+ // All of the blend modes are implemented in Cairo as of 1.10.
+ // For a detailed description, see:
+ // http://cairographics.org/operators/
+ cairo_set_operator(out_ct, get_cairo_op(_blend_mode));
+
+ cairo_paint(out_ct);
+ cairo_destroy(out_ct);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterBlend::can_handle_affine(Geom::Affine const &)
+{
+ // blend is a per-pixel primitive and is immutable under transformations
+ return true;
+}
+
+double FilterBlend::complexity(Geom::Affine const &)
+{
+ return 1.1;
+}
+
+bool FilterBlend::uses_background()
+{
+ return (_input == NR_FILTER_BACKGROUNDIMAGE || _input == NR_FILTER_BACKGROUNDALPHA ||
+ _input2 == NR_FILTER_BACKGROUNDIMAGE || _input2 == NR_FILTER_BACKGROUNDALPHA);
+}
+
+void FilterBlend::set_input(int slot) {
+ _input = slot;
+}
+
+void FilterBlend::set_input(int input, int slot) {
+ if (input == 0) _input = slot;
+ if (input == 1) _input2 = slot;
+}
+
+void FilterBlend::set_mode(SPBlendMode mode) {
+ if (_valid_modes.count(mode))
+ {
+ _blend_mode = mode;
+ }
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-blend.h b/src/display/nr-filter-blend.h
new file mode 100644
index 0000000..01cffeb
--- /dev/null
+++ b/src/display/nr-filter-blend.h
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_BLEND_H
+#define SEEN_NR_FILTER_BLEND_H
+
+/*
+ * SVG feBlend renderer
+ *
+ * "This filter composites two objects together using commonly used
+ * imaging software blending modes. It performs a pixel-wise combination
+ * of two input images."
+ * http://www.w3.org/TR/SVG11/filters.html#feBlend
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <set>
+#include "style-enums.h"
+#include "display/nr-filter-primitive.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterBlend : public FilterPrimitive {
+public:
+ FilterBlend();
+ static FilterPrimitive *create();
+ ~FilterBlend() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+ bool uses_background() override;
+
+ void set_input(int slot) override;
+ void set_input(int input, int slot) override;
+ void set_mode(SPBlendMode mode);
+
+ Glib::ustring name() override { return Glib::ustring("Blend"); }
+
+private:
+ static const std::set<SPBlendMode> _valid_modes;
+ SPBlendMode _blend_mode;
+ int _input2;
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+
+
+
+#endif /* __NR_FILTER_BLEND_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/display/nr-filter-colormatrix.cpp b/src/display/nr-filter-colormatrix.cpp
new file mode 100644
index 0000000..eff5d93
--- /dev/null
+++ b/src/display/nr-filter-colormatrix.cpp
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feColorMatrix filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+#include <algorithm>
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-colormatrix.h"
+#include "display/nr-filter-slot.h"
+#include <2geom/math-utils.h>
+
+namespace Inkscape {
+namespace Filters {
+
+FilterColorMatrix::FilterColorMatrix()
+= default;
+
+FilterPrimitive * FilterColorMatrix::create() {
+ return new FilterColorMatrix();
+}
+
+FilterColorMatrix::~FilterColorMatrix()
+= default;
+
+FilterColorMatrix::ColorMatrixMatrix::ColorMatrixMatrix(std::vector<double> const &values) {
+ unsigned limit = std::min(static_cast<size_t>(20), values.size());
+ for (unsigned i = 0; i < limit; ++i) {
+ if (i % 5 == 4) {
+ _v[i] = round(values[i]*255*255);
+ } else {
+ _v[i] = round(values[i]*255);
+ }
+ }
+ for (unsigned i = limit; i < 20; ++i) {
+ _v[i] = 0;
+ }
+}
+
+guint32 FilterColorMatrix::ColorMatrixMatrix::operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a, r, g, b)
+ // we need to un-premultiply alpha values for this type of matrix
+ // TODO: unpremul can be ignored if there is an identity mapping on the alpha channel
+ if (a != 0) {
+ r = unpremul_alpha(r, a);
+ g = unpremul_alpha(g, a);
+ b = unpremul_alpha(b, a);
+ }
+
+ gint32 ro = r*_v[0] + g*_v[1] + b*_v[2] + a*_v[3] + _v[4];
+ gint32 go = r*_v[5] + g*_v[6] + b*_v[7] + a*_v[8] + _v[9];
+ gint32 bo = r*_v[10] + g*_v[11] + b*_v[12] + a*_v[13] + _v[14];
+ gint32 ao = r*_v[15] + g*_v[16] + b*_v[17] + a*_v[18] + _v[19];
+ ro = (pxclamp(ro, 0, 255*255) + 127) / 255;
+ go = (pxclamp(go, 0, 255*255) + 127) / 255;
+ bo = (pxclamp(bo, 0, 255*255) + 127) / 255;
+ ao = (pxclamp(ao, 0, 255*255) + 127) / 255;
+
+ ro = premul_alpha(ro, ao);
+ go = premul_alpha(go, ao);
+ bo = premul_alpha(bo, ao);
+
+ ASSEMBLE_ARGB32(pxout, ao, ro, go, bo)
+ return pxout;
+}
+
+
+struct ColorMatrixSaturate {
+ ColorMatrixSaturate(double v_in) {
+ // clamp parameter instead of clamping color values
+ double v = CLAMP(v_in, 0.0, 1.0);
+ _v[0] = 0.213+0.787*v; _v[1] = 0.715-0.715*v; _v[2] = 0.072-0.072*v;
+ _v[3] = 0.213-0.213*v; _v[4] = 0.715+0.285*v; _v[5] = 0.072-0.072*v;
+ _v[6] = 0.213-0.213*v; _v[7] = 0.715-0.715*v; _v[8] = 0.072+0.928*v;
+ }
+
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a, r, g, b)
+
+ // Note: this cannot be done in fixed point, because the loss of precision
+ // causes overflow for some values of v
+ guint32 ro = r*_v[0] + g*_v[1] + b*_v[2] + 0.5;
+ guint32 go = r*_v[3] + g*_v[4] + b*_v[5] + 0.5;
+ guint32 bo = r*_v[6] + g*_v[7] + b*_v[8] + 0.5;
+
+ ASSEMBLE_ARGB32(pxout, a, ro, go, bo)
+ return pxout;
+ }
+private:
+ double _v[9];
+};
+
+struct ColorMatrixHueRotate {
+ ColorMatrixHueRotate(double v) {
+ double sinhue, coshue;
+ Geom::sincos(v * M_PI/180.0, sinhue, coshue);
+
+ _v[0] = round((0.213 +0.787*coshue -0.213*sinhue)*255);
+ _v[1] = round((0.715 -0.715*coshue -0.715*sinhue)*255);
+ _v[2] = round((0.072 -0.072*coshue +0.928*sinhue)*255);
+
+ _v[3] = round((0.213 -0.213*coshue +0.143*sinhue)*255);
+ _v[4] = round((0.715 +0.285*coshue +0.140*sinhue)*255);
+ _v[5] = round((0.072 -0.072*coshue -0.283*sinhue)*255);
+
+ _v[6] = round((0.213 -0.213*coshue -0.787*sinhue)*255);
+ _v[7] = round((0.715 -0.715*coshue +0.715*sinhue)*255);
+ _v[8] = round((0.072 +0.928*coshue +0.072*sinhue)*255);
+ }
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a, r, g, b)
+ gint32 maxpx = a*255;
+ gint32 ro = r*_v[0] + g*_v[1] + b*_v[2];
+ gint32 go = r*_v[3] + g*_v[4] + b*_v[5];
+ gint32 bo = r*_v[6] + g*_v[7] + b*_v[8];
+ ro = (pxclamp(ro, 0, maxpx) + 127) / 255;
+ go = (pxclamp(go, 0, maxpx) + 127) / 255;
+ bo = (pxclamp(bo, 0, maxpx) + 127) / 255;
+
+ ASSEMBLE_ARGB32(pxout, a, ro, go, bo)
+ return pxout;
+ }
+private:
+ gint32 _v[9];
+};
+
+struct ColorMatrixLuminanceToAlpha {
+ guint32 operator()(guint32 in) {
+ // original computation in double: r*0.2125 + g*0.7154 + b*0.0721
+ EXTRACT_ARGB32(in, a, r, g, b)
+ // unpremultiply color values
+ if (a != 0) {
+ r = unpremul_alpha(r, a);
+ g = unpremul_alpha(g, a);
+ b = unpremul_alpha(b, a);
+ }
+ guint32 ao = r*54 + g*182 + b*18;
+ return ((ao + 127) / 255) << 24;
+ }
+};
+
+void FilterColorMatrix::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = nullptr;
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+ set_cairo_surface_ci( input, ci_fp );
+
+ if (type == COLORMATRIX_LUMINANCETOALPHA) {
+ out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_ALPHA);
+ } else {
+ out = ink_cairo_surface_create_identical(input);
+ // Set ci to that used for computation
+ set_cairo_surface_ci(out, ci_fp);
+ }
+
+ switch (type) {
+ case COLORMATRIX_MATRIX:
+ ink_cairo_surface_filter(input, out, FilterColorMatrix::ColorMatrixMatrix(values));
+ break;
+ case COLORMATRIX_SATURATE:
+ ink_cairo_surface_filter(input, out, ColorMatrixSaturate(value));
+ break;
+ case COLORMATRIX_HUEROTATE:
+ ink_cairo_surface_filter(input, out, ColorMatrixHueRotate(value));
+ break;
+ case COLORMATRIX_LUMINANCETOALPHA:
+ ink_cairo_surface_filter(input, out, ColorMatrixLuminanceToAlpha());
+ break;
+ case COLORMATRIX_ENDTYPE:
+ default:
+ break;
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterColorMatrix::can_handle_affine(Geom::Affine const &)
+{
+ return true;
+}
+
+double FilterColorMatrix::complexity(Geom::Affine const &)
+{
+ return 2.0;
+}
+
+void FilterColorMatrix::set_type(FilterColorMatrixType t){
+ type = t;
+}
+
+void FilterColorMatrix::set_value(gdouble v){
+ value = v;
+}
+
+void FilterColorMatrix::set_values(std::vector<gdouble> const &v){
+ values = v;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-colormatrix.h b/src/display/nr-filter-colormatrix.h
new file mode 100644
index 0000000..8e35a35
--- /dev/null
+++ b/src/display/nr-filter-colormatrix.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_COLOR_MATRIX_H
+#define SEEN_NR_FILTER_COLOR_MATRIX_H
+
+/*
+ * feColorMatrix filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include <2geom/forward.h>
+#include "display/nr-filter-primitive.h"
+
+typedef unsigned int guint32;
+typedef signed int gint32;
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+enum FilterColorMatrixType {
+ COLORMATRIX_MATRIX,
+ COLORMATRIX_SATURATE,
+ COLORMATRIX_HUEROTATE,
+ COLORMATRIX_LUMINANCETOALPHA,
+ COLORMATRIX_ENDTYPE
+};
+
+class FilterColorMatrix : public FilterPrimitive {
+public:
+ FilterColorMatrix();
+ static FilterPrimitive *create();
+ ~FilterColorMatrix() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ virtual void set_type(FilterColorMatrixType type);
+ virtual void set_value(double value);
+ virtual void set_values(std::vector<double> const &values);
+
+ Glib::ustring name() override { return Glib::ustring("Color Matrix"); }
+
+public:
+ struct ColorMatrixMatrix {
+ ColorMatrixMatrix(std::vector<double> const &values);
+ guint32 operator()(guint32 in);
+ private:
+ gint32 _v[20];
+ };
+
+private:
+ std::vector<double> values;
+ double value;
+ FilterColorMatrixType type;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_COLOR_MATRIX_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/display/nr-filter-component-transfer.cpp b/src/display/nr-filter-component-transfer.cpp
new file mode 100644
index 0000000..4a5b458
--- /dev/null
+++ b/src/display/nr-filter-component-transfer.cpp
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feComponentTransfer filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-component-transfer.h"
+#include "display/nr-filter-slot.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterComponentTransfer::FilterComponentTransfer()
+= default;
+
+FilterPrimitive * FilterComponentTransfer::create() {
+ return new FilterComponentTransfer();
+}
+
+FilterComponentTransfer::~FilterComponentTransfer()
+= default;
+
+struct UnmultiplyAlpha {
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a, r, g, b);
+ if (a == 0 )
+ return in;
+ r = unpremul_alpha(r, a);
+ g = unpremul_alpha(g, a);
+ b = unpremul_alpha(b, a);
+ ASSEMBLE_ARGB32(out, a, r, g, b);
+ return out;
+ }
+};
+
+struct MultiplyAlpha {
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a, r, g, b);
+ if (a == 0 )
+ return in;
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+ ASSEMBLE_ARGB32(out, a, r, g, b);
+ return out;
+ }
+};
+
+struct ComponentTransfer {
+ ComponentTransfer(guint32 color)
+ : _shift(color * 8)
+ , _mask(0xff << _shift)
+ {}
+protected:
+ guint32 _shift;
+ guint32 _mask;
+};
+
+struct ComponentTransferTable : public ComponentTransfer {
+ ComponentTransferTable(guint32 color, std::vector<double> const &values)
+ : ComponentTransfer(color)
+ , _v(values.size())
+ {
+ for (unsigned i = 0; i< values.size(); ++i) {
+ _v[i] = round(CLAMP(values[i], 0.0, 1.0) * 255);
+ }
+ }
+ guint32 operator()(guint32 in) {
+ if (_v.empty()) {
+ return in;
+ }
+
+ guint32 component = (in & _mask) >> _shift;
+ if (_v.size() == 1 || component == 255) {
+ component = _v.back();
+ } else {
+ guint32 k = ((_v.size() - 1) * component);
+ guint32 dx = k % 255;
+ k /= 255;
+ component = _v[k]*255 + (_v[k+1] - _v[k])*dx;
+ component = (component + 127) / 255;
+ }
+ return (in & ~_mask) | (component << _shift);
+ }
+private:
+ std::vector<guint32> _v;
+};
+
+struct ComponentTransferDiscrete : public ComponentTransfer {
+ ComponentTransferDiscrete(guint32 color, std::vector<double> const &values)
+ : ComponentTransfer(color)
+ , _v(values.size())
+ {
+ for (unsigned i = 0; i< values.size(); ++i) {
+ _v[i] = round(CLAMP(values[i], 0.0, 1.0) * 255);
+ }
+ }
+ guint32 operator()(guint32 in) {
+ guint32 component = (in & _mask) >> _shift;
+ guint32 k = (_v.size()) * component / 255;
+ if( k == _v.size() ) --k;
+ component = _v[k];
+ return (in & ~_mask) | ((guint32)component << _shift);
+ }
+private:
+ std::vector<guint32> _v;
+};
+
+struct ComponentTransferLinear : public ComponentTransfer {
+ ComponentTransferLinear(guint32 color, double intercept, double slope)
+ : ComponentTransfer(color)
+ , _intercept(round(intercept*255*255))
+ , _slope(round(slope*255))
+ {}
+ guint32 operator()(guint32 in) {
+ gint32 component = (in & _mask) >> _shift;
+
+ // TODO: this can probably be reduced to something simpler
+ component = pxclamp(_slope * component + _intercept, 0, 255*255);
+ component = (component + 127) / 255;
+ return (in & ~_mask) | (component << _shift);
+ }
+private:
+ gint32 _intercept;
+ gint32 _slope;
+};
+
+struct ComponentTransferGamma : public ComponentTransfer {
+ ComponentTransferGamma(guint32 color, double amplitude, double exponent, double offset)
+ : ComponentTransfer(color)
+ , _amplitude(amplitude)
+ , _exponent(exponent)
+ , _offset(offset)
+ {}
+ guint32 operator()(guint32 in) {
+ double component = (in & _mask) >> _shift;
+ component /= 255.0;
+ component = _amplitude * pow(component, _exponent) + _offset;
+ guint32 cpx = pxclamp(component * 255.0, 0, 255);
+ return (in & ~_mask) | (cpx << _shift);
+ }
+private:
+ double _amplitude;
+ double _exponent;
+ double _offset;
+};
+
+void FilterComponentTransfer::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ set_cairo_surface_ci(out, ci_fp );
+ }
+ set_cairo_surface_ci( input, ci_fp );
+
+ //cairo_surface_t *outtemp = ink_cairo_surface_create_identical(out);
+ ink_cairo_surface_blit(input, out);
+
+ // We need to operate on unmultipled by alpha color values otherwise a change in alpha screws
+ // up the premultiplied by alpha r, g, b values.
+ ink_cairo_surface_filter(out, out, UnmultiplyAlpha());
+
+ // parameters: R = 0, G = 1, B = 2, A = 3
+ // Cairo: R = 2, G = 1, B = 0, A = 3
+ // If tableValues is empty, use identity.
+ for (unsigned i = 0; i < 4; ++i) {
+
+ guint32 color = 2 - i;
+ if(i==3) color = 3; // alpha
+
+ switch (type[i]) {
+ case COMPONENTTRANSFER_TYPE_TABLE:
+ if(!tableValues[i].empty()) {
+ ink_cairo_surface_filter(out, out,
+ ComponentTransferTable(color, tableValues[i]));
+ }
+ break;
+ case COMPONENTTRANSFER_TYPE_DISCRETE:
+ if(!tableValues[i].empty()) {
+ ink_cairo_surface_filter(out, out,
+ ComponentTransferDiscrete(color, tableValues[i]));
+ }
+ break;
+ case COMPONENTTRANSFER_TYPE_LINEAR:
+ ink_cairo_surface_filter(out, out,
+ ComponentTransferLinear(color, intercept[i], slope[i]));
+ break;
+ case COMPONENTTRANSFER_TYPE_GAMMA:
+ ink_cairo_surface_filter(out, out,
+ ComponentTransferGamma(color, amplitude[i], exponent[i], offset[i]));
+ break;
+ case COMPONENTTRANSFER_TYPE_ERROR:
+ case COMPONENTTRANSFER_TYPE_IDENTITY:
+ default:
+ break;
+ }
+ //ink_cairo_surface_blit(out, outtemp);
+ }
+
+ ink_cairo_surface_filter(out, out, MultiplyAlpha());
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+ //cairo_surface_destroy(outtemp);
+}
+
+bool FilterComponentTransfer::can_handle_affine(Geom::Affine const &)
+{
+ return true;
+}
+
+double FilterComponentTransfer::complexity(Geom::Affine const &)
+{
+ return 2.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-component-transfer.h b/src/display/nr-filter-component-transfer.h
new file mode 100644
index 0000000..9d1ea14
--- /dev/null
+++ b/src/display/nr-filter-component-transfer.h
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_COMPONENT_TRANSFER_H
+#define SEEN_NR_FILTER_COMPONENT_TRANSFER_H
+
+/*
+ * feComponentTransfer filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "display/nr-filter-primitive.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+enum FilterComponentTransferType {
+ COMPONENTTRANSFER_TYPE_IDENTITY,
+ COMPONENTTRANSFER_TYPE_TABLE,
+ COMPONENTTRANSFER_TYPE_DISCRETE,
+ COMPONENTTRANSFER_TYPE_LINEAR,
+ COMPONENTTRANSFER_TYPE_GAMMA,
+ COMPONENTTRANSFER_TYPE_ERROR
+};
+
+class FilterComponentTransfer : public FilterPrimitive {
+public:
+ FilterComponentTransfer();
+ static FilterPrimitive *create();
+ ~FilterComponentTransfer() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ FilterComponentTransferType type[4];
+ std::vector<double> tableValues[4];
+ double slope[4];
+ double intercept[4];
+ double amplitude[4];
+ double exponent[4];
+ double offset[4];
+
+ Glib::ustring name() override { return Glib::ustring("Component Transfer"); }
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_COMPONENT_TRANSFER_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/display/nr-filter-composite.cpp b/src/display/nr-filter-composite.cpp
new file mode 100644
index 0000000..4bb5685
--- /dev/null
+++ b/src/display/nr-filter-composite.cpp
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feComposite filter effect renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-composite.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterComposite::FilterComposite() :
+ op(COMPOSITE_DEFAULT), k1(0), k2(0), k3(0), k4(0),
+ _input2(Inkscape::Filters::NR_FILTER_SLOT_NOT_SET)
+{}
+
+FilterPrimitive * FilterComposite::create() {
+ return new FilterComposite();
+}
+
+FilterComposite::~FilterComposite()
+= default;
+
+struct ComposeArithmetic {
+ ComposeArithmetic(double k1, double k2, double k3, double k4)
+ : _k1(round(k1 * 255))
+ , _k2(round(k2 * 255*255))
+ , _k3(round(k3 * 255*255))
+ , _k4(round(k4 * 255*255*255))
+ {}
+ guint32 operator()(guint32 in1, guint32 in2) {
+ EXTRACT_ARGB32(in1, aa, ra, ga, ba)
+ EXTRACT_ARGB32(in2, ab, rb, gb, bb)
+
+ gint32 ao = _k1*aa*ab + _k2*aa + _k3*ab + _k4;
+ gint32 ro = _k1*ra*rb + _k2*ra + _k3*rb + _k4;
+ gint32 go = _k1*ga*gb + _k2*ga + _k3*gb + _k4;
+ gint32 bo = _k1*ba*bb + _k2*ba + _k3*bb + _k4;
+
+ ao = pxclamp(ao, 0, 255*255*255); // r, g and b are premultiplied, so should be clamped to the alpha channel
+ ro = (pxclamp(ro, 0, ao) + (255*255/2)) / (255*255);
+ go = (pxclamp(go, 0, ao) + (255*255/2)) / (255*255);
+ bo = (pxclamp(bo, 0, ao) + (255*255/2)) / (255*255);
+ ao = (ao + (255*255/2)) / (255*255);
+
+ ASSEMBLE_ARGB32(pxout, ao, ro, go, bo)
+ return pxout;
+ }
+private:
+ gint32 _k1, _k2, _k3, _k4;
+};
+
+void FilterComposite::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input1 = slot.getcairo(_input);
+ cairo_surface_t *input2 = slot.getcairo(_input2);
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+ set_cairo_surface_ci( input1, ci_fp );
+ set_cairo_surface_ci( input2, ci_fp );
+
+ cairo_surface_t *out = ink_cairo_surface_create_output(input1, input2);
+ set_cairo_surface_ci(out, ci_fp );
+
+ Geom::Rect vp = filter_primitive_area( slot.get_units() );
+ slot.set_primitive_area(_output, vp); // Needed for tiling
+
+ if (op == COMPOSITE_ARITHMETIC) {
+ ink_cairo_surface_blend(input1, input2, out, ComposeArithmetic(k1, k2, k3, k4));
+ } else {
+ ink_cairo_surface_blit(input2, out);
+ cairo_t *ct = cairo_create(out);
+ cairo_set_source_surface(ct, input1, 0, 0);
+ switch(op) {
+ case COMPOSITE_IN:
+ cairo_set_operator(ct, CAIRO_OPERATOR_IN);
+ break;
+ case COMPOSITE_OUT:
+ cairo_set_operator(ct, CAIRO_OPERATOR_OUT);
+ break;
+ case COMPOSITE_ATOP:
+ cairo_set_operator(ct, CAIRO_OPERATOR_ATOP);
+ break;
+ case COMPOSITE_XOR:
+ cairo_set_operator(ct, CAIRO_OPERATOR_XOR);
+ break;
+#ifdef WITH_CSSCOMPOSITE
+ /* New CSS Operators */
+ case COMPOSITE_CLEAR:
+ cairo_set_operator(ct, CAIRO_OPERATOR_CLEAR);
+ break;
+ case COMPOSITE_COPY:
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ break;
+ case COMPOSITE_DESTINATION:
+ cairo_set_operator(ct, CAIRO_OPERATOR_DEST);
+ break;
+ case COMPOSITE_DESTINATION_OVER:
+ cairo_set_operator(ct, CAIRO_OPERATOR_DEST_OVER);
+ break;
+ case COMPOSITE_DESTINATION_IN:
+ cairo_set_operator(ct, CAIRO_OPERATOR_DEST_IN);
+ break;
+ case COMPOSITE_DESTINATION_OUT:
+ cairo_set_operator(ct, CAIRO_OPERATOR_DEST_OUT);
+ break;
+ case COMPOSITE_DESTINATION_ATOP:
+ cairo_set_operator(ct, CAIRO_OPERATOR_DEST_ATOP);
+ break;
+ case COMPOSITE_LIGHTER:
+ cairo_set_operator(ct, CAIRO_OPERATOR_ADD);
+ break;
+#endif
+ case COMPOSITE_OVER:
+ case COMPOSITE_DEFAULT:
+ default:
+ // OVER is the default operator
+ break;
+ }
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterComposite::can_handle_affine(Geom::Affine const &)
+{
+ return true;
+}
+
+void FilterComposite::set_input(int input) {
+ _input = input;
+}
+
+void FilterComposite::set_input(int input, int slot) {
+ if (input == 0) _input = slot;
+ if (input == 1) _input2 = slot;
+}
+
+void FilterComposite::set_operator(FeCompositeOperator op) {
+ if (op == COMPOSITE_DEFAULT) {
+ this->op = COMPOSITE_OVER;
+ } else if (op != COMPOSITE_ENDOPERATOR) {
+ this->op = op;
+ }
+}
+
+void FilterComposite::set_arithmetic(double k1, double k2, double k3, double k4) {
+ if (!std::isfinite(k1) || !std::isfinite(k2) || !std::isfinite(k3) || !std::isfinite(k4)) {
+ g_warning("Non-finite parameter for feComposite arithmetic operator");
+ return;
+ }
+ this->k1 = k1;
+ this->k2 = k2;
+ this->k3 = k3;
+ this->k4 = k4;
+}
+
+double FilterComposite::complexity(Geom::Affine const &)
+{
+ return 1.1;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-composite.h b/src/display/nr-filter-composite.h
new file mode 100644
index 0000000..a76f4cc
--- /dev/null
+++ b/src/display/nr-filter-composite.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_COMPOSITE_H
+#define SEEN_NR_FILTER_COMPOSITE_H
+
+/*
+ * feComposite filter effect renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/filters/composite.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterComposite : public FilterPrimitive {
+public:
+ FilterComposite();
+ static FilterPrimitive *create();
+ ~FilterComposite() override;
+
+ void render_cairo(FilterSlot &) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_input(int input) override;
+ void set_input(int input, int slot) override;
+
+ void set_operator(FeCompositeOperator op);
+ void set_arithmetic(double k1, double k2, double k3, double k4);
+
+ Glib::ustring name() override { return Glib::ustring("Composite"); }
+
+private:
+ FeCompositeOperator op;
+ double k1, k2, k3, k4;
+ int _input2;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_COMPOSITE_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/display/nr-filter-convolve-matrix.cpp b/src/display/nr-filter-convolve-matrix.cpp
new file mode 100644
index 0000000..1e9bdb0
--- /dev/null
+++ b/src/display/nr-filter-convolve-matrix.cpp
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feConvolveMatrix filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ *
+ * Copyright (C) 2007,2009 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-convolve-matrix.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+#include "display/nr-filter-utils.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterConvolveMatrix::FilterConvolveMatrix()
+= default;
+
+FilterPrimitive * FilterConvolveMatrix::create() {
+ return new FilterConvolveMatrix();
+}
+
+FilterConvolveMatrix::~FilterConvolveMatrix()
+= default;
+
+enum PreserveAlphaMode {
+ PRESERVE_ALPHA,
+ NO_PRESERVE_ALPHA
+};
+
+template <PreserveAlphaMode preserve_alpha>
+struct ConvolveMatrix : public SurfaceSynth {
+ ConvolveMatrix(cairo_surface_t *s, int targetX, int targetY, int orderX, int orderY,
+ double divisor, double bias, std::vector<double> const &kernel)
+ : SurfaceSynth(s)
+ , _kernel(kernel.size())
+ , _targetX(targetX)
+ , _targetY(targetY)
+ , _orderX(orderX)
+ , _orderY(orderY)
+ , _bias(bias)
+ {
+ for (unsigned i = 0; i < _kernel.size(); ++i) {
+ _kernel[i] = kernel[i] / divisor;
+ }
+ // the matrix is given rotated 180 degrees
+ // which corresponds to reverse element order
+ std::reverse(_kernel.begin(), _kernel.end());
+ }
+
+ guint32 operator()(int x, int y) const {
+ int startx = std::max(0, x - _targetX);
+ int starty = std::max(0, y - _targetY);
+ int endx = std::min(_w, startx + _orderX);
+ int endy = std::min(_h, starty + _orderY);
+ int limitx = endx - startx;
+ int limity = endy - starty;
+ double suma = 0.0, sumr = 0.0, sumg = 0.0, sumb = 0.0;
+
+ for (int i = 0; i < limity; ++i) {
+ for (int j = 0; j < limitx; ++j) {
+ guint32 px = pixelAt(startx + j, starty + i);
+ double coeff = _kernel[i * _orderX + j];
+ EXTRACT_ARGB32(px, a,r,g,b)
+
+ sumr += r * coeff;
+ sumg += g * coeff;
+ sumb += b * coeff;
+ if (preserve_alpha == NO_PRESERVE_ALPHA) {
+ suma += a * coeff;
+ }
+ }
+ }
+ if (preserve_alpha == PRESERVE_ALPHA) {
+ suma = alphaAt(x, y);
+ } else {
+ suma += _bias * 255;
+ }
+
+ guint32 ao = pxclamp(round(suma), 0, 255);
+ guint32 ro = pxclamp(round(sumr + ao * _bias), 0, ao);
+ guint32 go = pxclamp(round(sumg + ao * _bias), 0, ao);
+ guint32 bo = pxclamp(round(sumb + ao * _bias), 0, ao);
+ ASSEMBLE_ARGB32(pxout, ao,ro,go,bo);
+ return pxout;
+ }
+
+private:
+ std::vector<double> _kernel;
+ int _targetX, _targetY, _orderX, _orderY;
+ double _bias;
+};
+
+void FilterConvolveMatrix::render_cairo(FilterSlot &slot)
+{
+ static bool bias_warning = false;
+ static bool edge_warning = false;
+
+ if (orderX<=0 || orderY<=0) {
+ g_warning("Empty kernel!");
+ return;
+ }
+ if (targetX<0 || targetX>=orderX || targetY<0 || targetY>=orderY) {
+ g_warning("Invalid target!");
+ return;
+ }
+ if (kernelMatrix.size()!=(unsigned int)(orderX*orderY)) {
+ //g_warning("kernelMatrix does not have orderX*orderY elements!");
+ return;
+ }
+
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_identical(input);
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ set_cairo_surface_ci(out, ci_fp);
+ }
+ set_cairo_surface_ci( input, ci_fp );
+
+ if (bias!=0 && !bias_warning) {
+ g_warning("It is unknown whether Inkscape's implementation of bias in feConvolveMatrix "
+ "is correct!");
+ bias_warning = true;
+ // The SVG specification implies that feConvolveMatrix is defined for premultiplied
+ // colors (which makes sense). It also says that bias should simply be added to the result
+ // for each color (without taking the alpha into account). However, it also says that one
+ // purpose of bias is "to have .5 gray value be the zero response of the filter".
+ // It seems sensible to indeed support the latter behaviour instead of the former,
+ // but this does appear to go against the standard.
+ // Note that Batik simply does not support bias!=0
+ }
+ if (edgeMode!=CONVOLVEMATRIX_EDGEMODE_NONE && !edge_warning) {
+ g_warning("Inkscape only supports edgeMode=\"none\" (and a filter uses a different one)!");
+ edge_warning = true;
+ }
+
+ //guint32 *in_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(input));
+ //guint32 *out_data = reinterpret_cast<guint32*>(cairo_image_surface_get_data(out));
+
+ //int width = cairo_image_surface_get_width(input);
+ //int height = cairo_image_surface_get_height(input);
+
+ // Set up predivided kernel matrix
+ /*std::vector<double> kernel(kernelMatrix);
+ for(size_t i=0; i<kernel.size(); i++) {
+ kernel[i] /= divisor; // The code that creates this object makes sure that divisor != 0
+ }*/
+
+ if (preserveAlpha) {
+ //convolve2D<true>(out_data, in_data, width, height, &kernel.front(), orderX, orderY,
+ // targetX, targetY, bias);
+ ink_cairo_surface_synthesize(out, ConvolveMatrix<PRESERVE_ALPHA>(input,
+ targetX, targetY, orderX, orderY, divisor, bias, kernelMatrix));
+ } else {
+ //convolve2D<false>(out_data, in_data, width, height, &kernel.front(), orderX, orderY,
+ // targetX, targetY, bias);
+ ink_cairo_surface_synthesize(out, ConvolveMatrix<NO_PRESERVE_ALPHA>(input,
+ targetX, targetY, orderX, orderY, divisor, bias, kernelMatrix));
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+void FilterConvolveMatrix::set_targetX(int coord) {
+ targetX = coord;
+}
+
+void FilterConvolveMatrix::set_targetY(int coord) {
+ targetY = coord;
+}
+
+void FilterConvolveMatrix::set_orderX(int coord) {
+ orderX = coord;
+}
+
+void FilterConvolveMatrix::set_orderY(int coord) {
+ orderY = coord;
+}
+
+void FilterConvolveMatrix::set_divisor(double d) {
+ divisor = d;
+}
+
+void FilterConvolveMatrix::set_bias(double b) {
+ bias = b;
+}
+
+void FilterConvolveMatrix::set_kernelMatrix(std::vector<gdouble> &km) {
+ kernelMatrix = km;
+}
+
+void FilterConvolveMatrix::set_edgeMode(FilterConvolveMatrixEdgeMode mode){
+ edgeMode = mode;
+}
+
+void FilterConvolveMatrix::set_preserveAlpha(bool pa){
+ preserveAlpha = pa;
+}
+
+void FilterConvolveMatrix::area_enlarge(Geom::IntRect &area, Geom::Affine const &/*trans*/)
+{
+ //Seems to me that since this filter's operation is resolution dependent,
+ // some spurious pixels may still appear at the borders when low zooming or rotating. Needs a better fix.
+ area.setMin(area.min() - Geom::IntPoint(targetX, targetY));
+ // This makes sure the last row/column in the original image corresponds
+ // to the last row/column in the new image that can be convolved without
+ // adjusting the boundary conditions).
+ area.setMax(area.max() + Geom::IntPoint(orderX - targetX - 1, orderY - targetY -1));
+}
+
+double FilterConvolveMatrix::complexity(Geom::Affine const &)
+{
+ return kernelMatrix.size();
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-convolve-matrix.h b/src/display/nr-filter-convolve-matrix.h
new file mode 100644
index 0000000..26b2bf3
--- /dev/null
+++ b/src/display/nr-filter-convolve-matrix.h
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_CONVOLVE_MATRIX_H
+#define SEEN_NR_FILTER_CONVOLVE_MATRIX_H
+
+/*
+ * feConvolveMatrix filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+#include <vector>
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+enum FilterConvolveMatrixEdgeMode {
+ CONVOLVEMATRIX_EDGEMODE_DUPLICATE,
+ CONVOLVEMATRIX_EDGEMODE_WRAP,
+ CONVOLVEMATRIX_EDGEMODE_NONE,
+ CONVOLVEMATRIX_EDGEMODE_ENDTYPE
+};
+
+class FilterConvolveMatrix : public FilterPrimitive {
+public:
+ FilterConvolveMatrix();
+ static FilterPrimitive *create();
+ ~FilterConvolveMatrix() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_targetY(int coord);
+ void set_targetX(int coord);
+ void set_orderY(int coord);
+ void set_orderX(int coord);
+ void set_kernelMatrix(std::vector<gdouble>& km);
+ void set_bias(double b);
+ void set_divisor(double d);
+ void set_edgeMode(FilterConvolveMatrixEdgeMode mode);
+ void set_preserveAlpha(bool pa);
+
+ Glib::ustring name() override { return Glib::ustring("Convolve Matrix"); }
+
+private:
+ std::vector<double> kernelMatrix;
+ int targetX, targetY;
+ int orderX, orderY;
+ double divisor, bias;
+ int dx, dy, kernelUnitLength;
+ FilterConvolveMatrixEdgeMode edgeMode;
+ bool preserveAlpha;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_CONVOLVE_MATRIX_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/display/nr-filter-diffuselighting.cpp b/src/display/nr-filter-diffuselighting.cpp
new file mode 100644
index 0000000..d33d513
--- /dev/null
+++ b/src/display/nr-filter-diffuselighting.cpp
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feDiffuseLighting renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * Jean-Rene Reinhard <jr@komite.net>
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2007-2010 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 <glib.h>
+
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-3dutils.h"
+#include "display/nr-filter-diffuselighting.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+#include "display/nr-filter-utils.h"
+#include "display/nr-light.h"
+#include "svg/svg-icc-color.h"
+#include "svg/svg-color.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterDiffuseLighting::FilterDiffuseLighting()
+{
+ light_type = NO_LIGHT;
+ diffuseConstant = 1;
+ surfaceScale = 1;
+ lighting_color = 0xffffffff;
+}
+
+FilterPrimitive * FilterDiffuseLighting::create() {
+ return new FilterDiffuseLighting();
+}
+
+FilterDiffuseLighting::~FilterDiffuseLighting()
+= default;
+
+struct DiffuseLight : public SurfaceSynth {
+ DiffuseLight(cairo_surface_t *bumpmap, double scale, double kd)
+ : SurfaceSynth(bumpmap)
+ , _scale(scale)
+ , _kd(kd)
+ {}
+
+protected:
+ guint32 diffuseLighting(int x, int y, NR::Fvector const &light, NR::Fvector const &light_components) {
+ NR::Fvector normal = surfaceNormalAt(x, y, _scale);
+ double k = _kd * NR::scalar_product(normal, light);
+
+ guint32 r = CLAMP_D_TO_U8(k * light_components[LIGHT_RED]);
+ guint32 g = CLAMP_D_TO_U8(k * light_components[LIGHT_GREEN]);
+ guint32 b = CLAMP_D_TO_U8(k * light_components[LIGHT_BLUE]);
+
+ ASSEMBLE_ARGB32(pxout, 255,r,g,b)
+ return pxout;
+ }
+ double _scale, _kd;
+};
+
+struct DiffuseDistantLight : public DiffuseLight {
+ DiffuseDistantLight(cairo_surface_t *bumpmap, SPFeDistantLight *light, guint32 color,
+ double scale, double diffuse_constant)
+ : DiffuseLight(bumpmap, scale, diffuse_constant)
+ {
+ DistantLight dl(light, color);
+ dl.light_vector(_lightv);
+ dl.light_components(_light_components);
+ }
+
+ guint32 operator()(int x, int y) {
+ return diffuseLighting(x, y, _lightv, _light_components);
+ }
+private:
+ NR::Fvector _lightv, _light_components;
+};
+
+struct DiffusePointLight : public DiffuseLight {
+ DiffusePointLight(cairo_surface_t *bumpmap, SPFePointLight *light, guint32 color,
+ Geom::Affine const &trans, double scale, double diffuse_constant,
+ double x0, double y0, int device_scale)
+ : DiffuseLight(bumpmap, scale, diffuse_constant)
+ , _light(light, color, trans, device_scale)
+ , _x0(x0)
+ , _y0(y0)
+ {
+ _light.light_components(_light_components);
+ }
+
+ guint32 operator()(int x, int y) {
+ NR::Fvector light;
+ _light.light_vector(light, _x0 + x, _y0 + y, _scale * alphaAt(x, y)/255.0);
+ return diffuseLighting(x, y, light, _light_components);
+ }
+private:
+ PointLight _light;
+ NR::Fvector _light_components;
+ double _x0, _y0;
+};
+
+struct DiffuseSpotLight : public DiffuseLight {
+ DiffuseSpotLight(cairo_surface_t *bumpmap, SPFeSpotLight *light, guint32 color,
+ Geom::Affine const &trans, double scale, double diffuse_constant,
+ double x0, double y0, int device_scale)
+ : DiffuseLight(bumpmap, scale, diffuse_constant)
+ , _light(light, color, trans, device_scale)
+ , _x0(x0)
+ , _y0(y0)
+ {}
+
+ guint32 operator()(int x, int y) {
+ NR::Fvector light, light_components;
+ _light.light_vector(light, _x0 + x, _y0 + y, _scale * alphaAt(x, y)/255.0);
+ _light.light_components(light_components, light);
+ return diffuseLighting(x, y, light, light_components);
+ }
+private:
+ SpotLight _light;
+ double _x0, _y0;
+};
+
+void FilterDiffuseLighting::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
+
+ double r = SP_RGBA32_R_F(lighting_color);
+ double g = SP_RGBA32_G_F(lighting_color);
+ double b = SP_RGBA32_B_F(lighting_color);
+
+#if defined(HAVE_LIBLCMS2)
+
+ if (icc) {
+ guchar ru, gu, bu;
+ icc_color_to_sRGB(icc, &ru, &gu, &bu);
+ r = SP_COLOR_U_TO_F(ru);
+ g = SP_COLOR_U_TO_F(gu);
+ b = SP_COLOR_U_TO_F(bu);
+ }
+#endif
+
+ // Only alpha channel of input is used, no need to check input color_interpolation_filter value.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+
+ // Lighting color is always defined in terms of sRGB, preconvert to linearRGB
+ // if color_interpolation_filters set to linearRGB (for efficiency assuming
+ // next filter primitive has same value of cif).
+ if( ci_fp == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
+ r = srgb_to_linear( r );
+ g = srgb_to_linear( g );
+ b = srgb_to_linear( b );
+ }
+ }
+ set_cairo_surface_ci(out, ci_fp );
+ guint32 color = SP_RGBA32_F_COMPOSE( r, g, b, 1.0 );
+
+ int device_scale = slot.get_device_scale();
+
+ Geom::Rect slot_area = slot.get_slot_area();
+ Geom::Point p = slot_area.min();
+
+ // trans has inverse y... so we can't just scale by device_scale! We must instead explicitly
+ // scale the point and spot light coordinates (as well as "scale").
+
+ Geom::Affine trans = slot.get_units().get_matrix_primitiveunits2pb();
+
+ double x0 = p[Geom::X], y0 = p[Geom::Y];
+ double scale = surfaceScale * trans.descrim() * device_scale;
+
+ switch (light_type) {
+ case DISTANT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ DiffuseDistantLight(input, light.distant, color, scale, diffuseConstant));
+ break;
+ case POINT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ DiffusePointLight(input, light.point, color, trans, scale, diffuseConstant, x0, y0, device_scale));
+ break;
+ case SPOT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ DiffuseSpotLight(input, light.spot, color, trans, scale, diffuseConstant, x0, y0, device_scale));
+ break;
+ default: {
+ cairo_t *ct = cairo_create(out);
+ cairo_set_source_rgba(ct, 0,0,0,1);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ } break;
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+void FilterDiffuseLighting::set_icc(SVGICCColor *icc_color) {
+ icc = icc_color;
+}
+
+void FilterDiffuseLighting::area_enlarge(Geom::IntRect &area, Geom::Affine const & /*trans*/)
+{
+ // TODO: support kernelUnitLength
+
+ // We expand the area by 1 in every direction to avoid artifacts on tile edges.
+ // However, it means that edge pixels will be incorrect.
+ area.expandBy(1);
+}
+
+double FilterDiffuseLighting::complexity(Geom::Affine const &)
+{
+ return 9.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-diffuselighting.h b/src/display/nr-filter-diffuselighting.h
new file mode 100644
index 0000000..5bf777b
--- /dev/null
+++ b/src/display/nr-filter-diffuselighting.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_DIFFUSELIGHTING_H
+#define SEEN_NR_FILTER_DIFFUSELIGHTING_H
+
+/*
+ * feDiffuseLighting renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-light-types.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+class SPFeDistantLight;
+class SPFePointLight;
+class SPFeSpotLight;
+struct SVGICCColor;
+typedef unsigned int guint32;
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterDiffuseLighting : public FilterPrimitive {
+public:
+ FilterDiffuseLighting();
+ static FilterPrimitive *create();
+ ~FilterDiffuseLighting() override;
+ void render_cairo(FilterSlot &slot) override;
+ virtual void set_icc(SVGICCColor *icc_color);
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ union {
+ SPFeDistantLight *distant;
+ SPFePointLight *point;
+ SPFeSpotLight *spot;
+ } light;
+ LightType light_type;
+ double diffuseConstant;
+ double surfaceScale;
+ guint32 lighting_color;
+
+ Glib::ustring name() override { return Glib::ustring("Diffuse Lighting"); }
+
+private:
+ SVGICCColor *icc;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* SEEN_NR_FILTER_DIFFUSELIGHTING_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/display/nr-filter-displacement-map.cpp b/src/display/nr-filter-displacement-map.cpp
new file mode 100644
index 0000000..9d83e54
--- /dev/null
+++ b/src/display/nr-filter-displacement-map.cpp
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feDisplacementMap filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-displacement-map.h"
+#include "display/nr-filter-types.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterDisplacementMap::FilterDisplacementMap()
+= default;
+
+FilterPrimitive * FilterDisplacementMap::create() {
+ return new FilterDisplacementMap();
+}
+
+FilterDisplacementMap::~FilterDisplacementMap()
+= default;
+
+struct Displace {
+ Displace(cairo_surface_t *texture, cairo_surface_t *map,
+ unsigned xch, unsigned ych, double scalex, double scaley)
+ : _texture(texture)
+ , _map(map)
+ , _xch(xch)
+ , _ych(ych)
+ , _scalex(scalex/255.0)
+ , _scaley(scaley/255.0)
+ {}
+ guint32 operator()(int x, int y) {
+ guint32 mappx = _map.pixelAt(x, y);
+ guint32 a = (mappx & 0xff000000) >> 24;
+ guint32 xpx = 0, ypx = 0;
+ double xtex = x, ytex = y;
+
+ guint32 xshift = _xch * 8, yshift = _ych * 8;
+ xpx = (mappx & (0xff << xshift)) >> xshift;
+ ypx = (mappx & (0xff << yshift)) >> yshift;
+ if (a) {
+ if (_xch != 3) xpx = unpremul_alpha(xpx, a);
+ if (_ych != 3) ypx = unpremul_alpha(ypx, a);
+ }
+ xtex += _scalex * (xpx - 127.5);
+ ytex += _scaley * (ypx - 127.5);
+
+ if (xtex >= 0 && xtex < (_texture._w - 1) &&
+ ytex >= 0 && ytex < (_texture._h - 1))
+ {
+ return _texture.pixelAt(xtex, ytex);
+ } else {
+ return 0;
+ }
+ }
+private:
+ SurfaceSynth _texture;
+ SurfaceSynth _map;
+ unsigned _xch, _ych;
+ double _scalex, _scaley;
+};
+
+void FilterDisplacementMap::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *texture = slot.getcairo(_input);
+ cairo_surface_t *map = slot.getcairo(_input2);
+ cairo_surface_t *out = ink_cairo_surface_create_identical(texture);
+ // color_interpolation_filters for out same as texture. See spec.
+ copy_cairo_surface_ci( texture, out );
+
+ // We may need to transform map surface to correct color interpolation space. The map surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the map before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+ set_cairo_surface_ci( map, ci_fp );
+
+ Geom::Affine trans = slot.get_units().get_matrix_primitiveunits2pb();
+
+ int device_scale = slot.get_device_scale();
+ double scalex = scale * trans.expansionX() * device_scale;
+ double scaley = scale * trans.expansionY() * device_scale;
+
+ ink_cairo_surface_synthesize(out, Displace(texture, map, Xchannel, Ychannel, scalex, scaley));
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+void FilterDisplacementMap::set_input(int slot) {
+ _input = slot;
+}
+
+void FilterDisplacementMap::set_scale(double s) {
+ scale = s;
+}
+
+void FilterDisplacementMap::set_input(int input, int slot) {
+ if (input == 0) _input = slot;
+ if (input == 1) _input2 = slot;
+}
+
+void FilterDisplacementMap::set_channel_selector(int s, FilterDisplacementMapChannelSelector channel) {
+ if (channel > DISPLACEMENTMAP_CHANNEL_ALPHA || channel < DISPLACEMENTMAP_CHANNEL_RED) {
+ g_warning("Selected an invalid channel value. (%d)", channel);
+ return;
+ }
+
+ // channel numbering:
+ // a = 3, r = 2, g = 1, b = 0
+ // this way we can get the component value using:
+ // component = (color & (ch*8)) >> (ch*8)
+ unsigned ch = 4;
+ switch (channel) {
+ case DISPLACEMENTMAP_CHANNEL_ALPHA:
+ ch = 3; break;
+ case DISPLACEMENTMAP_CHANNEL_RED:
+ ch = 2; break;
+ case DISPLACEMENTMAP_CHANNEL_GREEN:
+ ch = 1; break;
+ case DISPLACEMENTMAP_CHANNEL_BLUE:
+ ch = 0; break;
+ default: break;
+ }
+ if (ch == 4) return;
+
+ if (s == 0) Xchannel = ch;
+ if (s == 1) Ychannel = ch;
+}
+
+void FilterDisplacementMap::area_enlarge(Geom::IntRect &area, Geom::Affine const &trans)
+{
+ //I assume scale is in user coordinates (?!?)
+ //FIXME: trans should be multiplied by some primitiveunits2user, shouldn't it?
+
+ double scalex = scale/2.*(std::fabs(trans[0])+std::fabs(trans[1]));
+ double scaley = scale/2.*(std::fabs(trans[2])+std::fabs(trans[3]));
+
+ //FIXME: no +2 should be there!... (noticeable only for big scales at big zoom factor)
+ area.expandBy(scalex+2, scaley+2);
+}
+
+double FilterDisplacementMap::complexity(Geom::Affine const &)
+{
+ return 3.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-displacement-map.h b/src/display/nr-filter-displacement-map.h
new file mode 100644
index 0000000..32afcfb
--- /dev/null
+++ b/src/display/nr-filter-displacement-map.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_DISPLACEMENT_MAP_H
+#define SEEN_NR_FILTER_DISPLACEMENT_MAP_H
+
+/*
+ * feDisplacementMap filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/filters/displacementmap.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterDisplacementMap : public FilterPrimitive {
+public:
+ FilterDisplacementMap();
+ static FilterPrimitive *create();
+ ~FilterDisplacementMap() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_input(int slot) override;
+ void set_input(int input, int slot) override;
+ virtual void set_scale(double s);
+ virtual void set_channel_selector(int s, FilterDisplacementMapChannelSelector channel);
+
+ Glib::ustring name() override { return Glib::ustring("Displacement Map"); }
+
+private:
+ double scale;
+ int _input2;
+ unsigned Xchannel, Ychannel;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_DISPLACEMENT_MAP_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/display/nr-filter-flood.cpp b/src/display/nr-filter-flood.cpp
new file mode 100644
index 0000000..05c3293
--- /dev/null
+++ b/src/display/nr-filter-flood.cpp
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feFlood filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Tavmjong Bah <tavmjong@free.fr> (use primitive filter region)
+ *
+ * Copyright (C) 2007, 2011 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 "display/cairo-utils.h"
+#include "display/nr-filter-flood.h"
+#include "display/nr-filter-slot.h"
+#include "svg/svg-icc-color.h"
+#include "svg/svg-color.h"
+#include "color.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterFlood::FilterFlood()
+= default;
+
+FilterPrimitive * FilterFlood::create() {
+ return new FilterFlood();
+}
+
+FilterFlood::~FilterFlood()
+= default;
+
+void FilterFlood::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+
+ double r = SP_RGBA32_R_F(color);
+ double g = SP_RGBA32_G_F(color);
+ double b = SP_RGBA32_B_F(color);
+ double a = opacity;
+
+#if defined(HAVE_LIBLCMS2)
+
+ if (icc) {
+ guchar ru, gu, bu;
+ icc_color_to_sRGB(icc, &ru, &gu, &bu);
+ r = SP_COLOR_U_TO_F(ru);
+ g = SP_COLOR_U_TO_F(gu);
+ b = SP_COLOR_U_TO_F(bu);
+ }
+#endif
+
+ cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
+
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+
+ // Flood color is always defined in terms of sRGB, preconvert to linearRGB
+ // if color_interpolation_filters set to linearRGB (for efficiency assuming
+ // next filter primitive has same value of cif).
+ if( ci_fp == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
+ r = srgb_to_linear( r );
+ g = srgb_to_linear( g );
+ b = srgb_to_linear( b );
+ }
+ }
+ set_cairo_surface_ci(out, ci_fp );
+
+ // Get filter primitive area in user units
+ Geom::Rect fp = filter_primitive_area( slot.get_units() );
+
+ // Convert to Cairo units
+ Geom::Rect fp_cairo = fp * slot.get_units().get_matrix_user2pb();
+
+ // Get area in slot (tile to fill)
+ Geom::Rect sa = slot.get_slot_area();
+
+ // Get overlap
+ Geom::OptRect optoverlap = intersect( fp_cairo, sa );
+ if( optoverlap ) {
+
+ Geom::Rect overlap = *optoverlap;
+
+ double dx = fp_cairo.min()[Geom::X] - sa.min()[Geom::X];
+ double dy = fp_cairo.min()[Geom::Y] - sa.min()[Geom::Y];
+ if( dx < 0.0 ) dx = 0.0;
+ if( dy < 0.0 ) dy = 0.0;
+
+ cairo_t *ct = cairo_create(out);
+ cairo_set_source_rgba(ct, r, g, b, a);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_rectangle(ct, dx, dy, overlap.width(), overlap.height() );
+ cairo_fill(ct);
+ cairo_destroy(ct);
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterFlood::can_handle_affine(Geom::Affine const &)
+{
+ // flood is a per-pixel primitive and is immutable under transformations
+ return true;
+}
+
+void FilterFlood::set_color(guint32 c) {
+ color = c;
+}
+
+void FilterFlood::set_opacity(double o) {
+ opacity = o;
+}
+
+void FilterFlood::set_icc(SVGICCColor *icc_color) {
+ icc = icc_color;
+}
+double FilterFlood::complexity(Geom::Affine const &)
+{
+ // flood is actually less expensive than normal rendering,
+ // but when flood is processed, the object has already been rendered
+ return 1.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-flood.h b/src/display/nr-filter-flood.h
new file mode 100644
index 0000000..c2f51fa
--- /dev/null
+++ b/src/display/nr-filter-flood.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_FLOOD_H
+#define SEEN_NR_FILTER_FLOOD_H
+
+/*
+ * feFlood filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+
+struct SVGICCColor;
+typedef unsigned int guint32;
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterFlood : public FilterPrimitive {
+public:
+ FilterFlood();
+ static FilterPrimitive *create();
+ ~FilterFlood() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+ bool uses_background() override { return false; }
+
+ virtual void set_opacity(double o);
+ virtual void set_color(guint32 c);
+ virtual void set_icc(SVGICCColor *icc_color);
+
+ Glib::ustring name() override { return Glib::ustring("Flood"); }
+
+private:
+ double opacity;
+ guint32 color;
+ SVGICCColor *icc;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_FLOOD_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/display/nr-filter-gaussian.cpp b/src/display/nr-filter-gaussian.cpp
new file mode 100644
index 0000000..f145ba5
--- /dev/null
+++ b/src/display/nr-filter-gaussian.cpp
@@ -0,0 +1,758 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gaussian blur renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * bulia byak
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ *
+ * Copyright (C) 2006-2008 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 <algorithm>
+#include <cmath>
+#include <complex>
+#include <cstdlib>
+#include <glib.h>
+#include <limits>
+#if HAVE_OPENMP
+#include <omp.h>
+#endif //HAVE_OPENMP
+
+#include "display/cairo-utils.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-gaussian.h"
+#include "display/nr-filter-types.h"
+#include "display/nr-filter-units.h"
+#include "display/nr-filter-slot.h"
+#include <2geom/affine.h>
+#include "util/fixed_point.h"
+#include "preferences.h"
+
+#ifndef INK_UNUSED
+#define INK_UNUSED(x) ((void)(x))
+#endif
+
+// IIR filtering method based on:
+// L.J. van Vliet, I.T. Young, and P.W. Verbeek, Recursive Gaussian Derivative Filters,
+// in: A.K. Jain, S. Venkatesh, B.C. Lovell (eds.),
+// ICPR'98, Proc. 14th Int. Conference on Pattern Recognition (Brisbane, Aug. 16-20),
+// IEEE Computer Society Press, Los Alamitos, 1998, 509-514.
+//
+// Using the backwards-pass initialization procedure from:
+// Boundary Conditions for Young - van Vliet Recursive Filtering
+// Bill Triggs, Michael Sdika
+// IEEE Transactions on Signal Processing, Volume 54, Number 5 - may 2006
+
+// Number of IIR filter coefficients used. Currently only 3 is supported.
+// "Recursive Gaussian Derivative Filters" says this is enough though (and
+// some testing indeed shows that the quality doesn't improve much if larger
+// filters are used).
+static size_t const N = 3;
+
+template<typename InIt, typename OutIt, typename Size>
+inline void copy_n(InIt beg_in, Size N, OutIt beg_out) {
+ std::copy(beg_in, beg_in+N, beg_out);
+}
+
+// Type used for IIR filter coefficients (can be 10.21 signed fixed point, see Anisotropic Gaussian Filtering Using Fixed Point Arithmetic, Christoph H. Lampert & Oliver Wirjadi, 2006)
+typedef double IIRValue;
+
+// Type used for FIR filter coefficients (can be 16.16 unsigned fixed point, should have 8 or more bits in the fractional part, the integer part should be capable of storing approximately 20*255)
+typedef Inkscape::Util::FixedPoint<unsigned int,16> FIRValue;
+
+template<typename T> static inline T sqr(T const& v) { return v*v; }
+
+template<typename T> static inline T clip(T const& v, T const& a, T const& b) {
+ if ( v < a ) return a;
+ if ( v > b ) return b;
+ return v;
+}
+
+template<typename Tt, typename Ts>
+static inline Tt round_cast(Ts v) {
+ static Ts const rndoffset(.5);
+ return static_cast<Tt>(v+rndoffset);
+}
+/*
+template<>
+inline unsigned char round_cast(double v) {
+ // This (fast) rounding method is based on:
+ // http://stereopsis.com/sree/fpu2006.html
+#if G_BYTE_ORDER==G_LITTLE_ENDIAN
+ double const dmr = 6755399441055744.0;
+ v = v + dmr;
+ return ((unsigned char*)&v)[0];
+#elif G_BYTE_ORDER==G_BIG_ENDIAN
+ double const dmr = 6755399441055744.0;
+ v = v + dmr;
+ return ((unsigned char*)&v)[7];
+#else
+ static double const rndoffset(.5);
+ return static_cast<unsigned char>(v+rndoffset);
+#endif
+}*/
+
+template<typename Tt, typename Ts>
+static inline Tt clip_round_cast(Ts const v) {
+ Ts const minval = std::numeric_limits<Tt>::min();
+ Ts const maxval = std::numeric_limits<Tt>::max();
+ Tt const minval_rounded = std::numeric_limits<Tt>::min();
+ Tt const maxval_rounded = std::numeric_limits<Tt>::max();
+ if ( v < minval ) return minval_rounded;
+ if ( v > maxval ) return maxval_rounded;
+ return round_cast<Tt>(v);
+}
+
+template<typename Tt, typename Ts>
+static inline Tt clip_round_cast_varmax(Ts const v, Tt const maxval_rounded) {
+ Ts const minval = std::numeric_limits<Tt>::min();
+ Tt const maxval = maxval_rounded;
+ Tt const minval_rounded = std::numeric_limits<Tt>::min();
+ if ( v < minval ) return minval_rounded;
+ if ( v > maxval ) return maxval_rounded;
+ return round_cast<Tt>(v);
+}
+
+namespace Inkscape {
+namespace Filters {
+
+FilterGaussian::FilterGaussian()
+{
+ _deviation_x = _deviation_y = 0.0;
+}
+
+FilterPrimitive *FilterGaussian::create()
+{
+ return new FilterGaussian();
+}
+
+FilterGaussian::~FilterGaussian()
+{
+ // Nothing to do here
+}
+
+static int
+_effect_area_scr(double const deviation)
+{
+ return (int)std::ceil(std::fabs(deviation) * 3.0);
+}
+
+static void
+_make_kernel(FIRValue *const kernel, double const deviation)
+{
+ int const scr_len = _effect_area_scr(deviation);
+ g_assert(scr_len >= 0);
+ double const d_sq = sqr(deviation) * 2;
+ double k[scr_len+1]; // This is only called for small kernel sizes (above approximately 10 coefficients the IIR filter is used)
+
+ // Compute kernel and sum of coefficients
+ // Note that actually only half the kernel is computed, as it is symmetric
+ double sum = 0;
+ for ( int i = scr_len; i >= 0 ; i-- ) {
+ k[i] = std::exp(-sqr(i) / d_sq);
+ if ( i > 0 ) sum += k[i];
+ }
+ // the sum of the complete kernel is twice as large (plus the center element which we skipped above to prevent counting it twice)
+ sum = 2*sum + k[0];
+
+ // Normalize kernel (making sure the sum is exactly 1)
+ double ksum = 0;
+ FIRValue kernelsum = 0;
+ for ( int i = scr_len; i >= 1 ; i-- ) {
+ ksum += k[i]/sum;
+ kernel[i] = ksum-static_cast<double>(kernelsum);
+ kernelsum += kernel[i];
+ }
+ kernel[0] = FIRValue(1)-2*kernelsum;
+}
+
+// Return value (v) should satisfy:
+// 2^(2*v)*255<2^32
+// 255<2^(32-2*v)
+// 2^8<=2^(32-2*v)
+// 8<=32-2*v
+// 2*v<=24
+// v<=12
+static int
+_effect_subsample_step_log2(double const deviation, int const quality)
+{
+ // To make sure FIR will always be used (unless the kernel is VERY big):
+ // deviation/step <= 3
+ // deviation/3 <= step
+ // log(deviation/3) <= log(step)
+ // So when x below is >= 1/3 FIR will almost always be used.
+ // This means IIR is almost only used with the modes BETTER or BEST.
+ int stepsize_l2;
+ switch (quality) {
+ case BLUR_QUALITY_WORST:
+ // 2 == log(x*8/3))
+ // 2^2 == x*2^3/3
+ // x == 3/2
+ stepsize_l2 = clip(static_cast<int>(log(deviation*(3./2.))/log(2.)), 0, 12);
+ break;
+ case BLUR_QUALITY_WORSE:
+ // 2 == log(x*16/3))
+ // 2^2 == x*2^4/3
+ // x == 3/2^2
+ stepsize_l2 = clip(static_cast<int>(log(deviation*(3./4.))/log(2.)), 0, 12);
+ break;
+ case BLUR_QUALITY_BETTER:
+ // 2 == log(x*32/3))
+ // 2 == x*2^5/3
+ // x == 3/2^4
+ stepsize_l2 = clip(static_cast<int>(log(deviation*(3./16.))/log(2.)), 0, 12);
+ break;
+ case BLUR_QUALITY_BEST:
+ stepsize_l2 = 0; // no subsampling at all
+ break;
+ case BLUR_QUALITY_NORMAL:
+ default:
+ // 2 == log(x*16/3))
+ // 2 == x*2^4/3
+ // x == 3/2^3
+ stepsize_l2 = clip(static_cast<int>(log(deviation*(3./8.))/log(2.)), 0, 12);
+ break;
+ }
+ return stepsize_l2;
+}
+
+static void calcFilter(double const sigma, double b[N]) {
+ assert(N==3);
+ std::complex<double> const d1_org(1.40098, 1.00236);
+ double const d3_org = 1.85132;
+ double qbeg = 1; // Don't go lower than sigma==2 (we'd probably want a normal convolution in that case anyway)
+ double qend = 2*sigma;
+ double const sigmasqr = sqr(sigma);
+ do { // Binary search for right q (a linear interpolation scheme is suggested, but this should work fine as well)
+ double const q = (qbeg+qend)/2;
+ // Compute scaled filter coefficients
+ std::complex<double> const d1 = pow(d1_org, 1.0/q);
+ double const d3 = pow(d3_org, 1.0/q);
+ // Compute actual sigma^2
+ double const ssqr = 2*(2*(d1/sqr(d1-1.)).real()+d3/sqr(d3-1.));
+ if ( ssqr < sigmasqr ) {
+ qbeg = q;
+ } else {
+ qend = q;
+ }
+ } while(qend-qbeg>(sigma/(1<<30)));
+ // Compute filter coefficients
+ double const q = (qbeg+qend)/2;
+ std::complex<double> const d1 = pow(d1_org, 1.0/q);
+ double const d3 = pow(d3_org, 1.0/q);
+ double const absd1sqr = std::norm(d1); // d1*d2 = d1*conj(d1) = |d1|^2 = std::norm(d1)
+ double const re2d1 = 2*d1.real(); // d1+d2 = d1+conj(d1) = 2*real(d1)
+ double const bscale = 1.0/(absd1sqr*d3);
+ b[2] = -bscale;
+ b[1] = bscale*(d3+re2d1);
+ b[0] = -bscale*(absd1sqr+d3*re2d1);
+}
+
+static void calcTriggsSdikaM(double const b[N], double M[N*N]) {
+ assert(N==3);
+ double a1=b[0], a2=b[1], a3=b[2];
+ double const Mscale = 1.0/((1+a1-a2+a3)*(1-a1-a2-a3)*(1+a2+(a1-a3)*a3));
+ M[0] = 1-a2-a1*a3-sqr(a3);
+ M[1] = (a1+a3)*(a2+a1*a3);
+ M[2] = a3*(a1+a2*a3);
+ M[3] = a1+a2*a3;
+ M[4] = (1-a2)*(a2+a1*a3);
+ M[5] = a3*(1-a2-a1*a3-sqr(a3));
+ M[6] = a1*(a1+a3)+a2*(1-a2);
+ M[7] = a1*(a2-sqr(a3))+a3*(1+a2*(a2-1)-sqr(a3));
+ M[8] = a3*(a1+a2*a3);
+ for(unsigned int i=0; i<9; i++) M[i] *= Mscale;
+}
+
+template<unsigned int SIZE>
+static void calcTriggsSdikaInitialization(double const M[N*N], IIRValue const uold[N][SIZE], IIRValue const uplus[SIZE], IIRValue const vplus[SIZE], IIRValue const alpha, IIRValue vold[N][SIZE]) {
+ for(unsigned int c=0; c<SIZE; c++) {
+ double uminp[N];
+ for(unsigned int i=0; i<N; i++) uminp[i] = uold[i][c] - uplus[c];
+ for(unsigned int i=0; i<N; i++) {
+ double voldf = 0;
+ for(unsigned int j=0; j<N; j++) {
+ voldf += uminp[j]*M[i*N+j];
+ }
+ // Properly takes care of the scaling coefficient alpha and vplus (which is already appropriately scaled)
+ // This was arrived at by starting from a version of the blur filter that ignored the scaling coefficient
+ // (and scaled the final output by alpha^2) and then gradually reintroducing the scaling coefficient.
+ vold[i][c] = voldf*alpha;
+ vold[i][c] += vplus[c];
+ }
+ }
+}
+
+// Filters over 1st dimension
+template<typename PT, unsigned int PC, bool PREMULTIPLIED_ALPHA>
+static void
+filter2D_IIR(PT *const dest, int const dstr1, int const dstr2,
+ PT const *const src, int const sstr1, int const sstr2,
+ int const n1, int const n2, IIRValue const b[N+1], double const M[N*N],
+ IIRValue *const tmpdata[], int const num_threads)
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ static unsigned int const alpha_PC = PC-1;
+ #define PREMUL_ALPHA_LOOP for(unsigned int c=0; c<PC-1; ++c)
+#else
+ static unsigned int const alpha_PC = 0;
+ #define PREMUL_ALPHA_LOOP for(unsigned int c=1; c<PC; ++c)
+#endif
+
+INK_UNUSED(num_threads); // to suppress unused argument compiler warning
+#if HAVE_OPENMP
+#pragma omp parallel for num_threads(num_threads)
+#endif // HAVE_OPENMP
+ for ( int c2 = 0 ; c2 < n2 ; c2++ ) {
+#if HAVE_OPENMP
+ unsigned int tid = omp_get_thread_num();
+#else
+ unsigned int tid = 0;
+#endif // HAVE_OPENMP
+ // corresponding line in the source and output buffer
+ PT const * srcimg = src + c2*sstr2;
+ PT * dstimg = dest + c2*dstr2 + n1*dstr1;
+ // Border constants
+ IIRValue imin[PC]; copy_n(srcimg + (0)*sstr1, PC, imin);
+ IIRValue iplus[PC]; copy_n(srcimg + (n1-1)*sstr1, PC, iplus);
+ // Forward pass
+ IIRValue u[N+1][PC];
+ for(unsigned int i=0; i<N; i++) copy_n(imin, PC, u[i]);
+ for ( int c1 = 0 ; c1 < n1 ; c1++ ) {
+ for(unsigned int i=N; i>0; i--) copy_n(u[i-1], PC, u[i]);
+ copy_n(srcimg, PC, u[0]);
+ srcimg += sstr1;
+ for(unsigned int c=0; c<PC; c++) u[0][c] *= b[0];
+ for(unsigned int i=1; i<N+1; i++) {
+ for(unsigned int c=0; c<PC; c++) u[0][c] += u[i][c]*b[i];
+ }
+ copy_n(u[0], PC, tmpdata[tid]+c1*PC);
+ }
+ // Backward pass
+ IIRValue v[N+1][PC];
+ calcTriggsSdikaInitialization<PC>(M, u, iplus, iplus, b[0], v);
+ dstimg -= dstr1;
+ if ( PREMULTIPLIED_ALPHA ) {
+ dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]);
+ PREMUL_ALPHA_LOOP dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], dstimg[alpha_PC]);
+ } else {
+ for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]);
+ }
+ int c1=n1-1;
+ while(c1-->0) {
+ for(unsigned int i=N; i>0; i--) copy_n(v[i-1], PC, v[i]);
+ copy_n(tmpdata[tid]+c1*PC, PC, v[0]);
+ for(unsigned int c=0; c<PC; c++) v[0][c] *= b[0];
+ for(unsigned int i=1; i<N+1; i++) {
+ for(unsigned int c=0; c<PC; c++) v[0][c] += v[i][c]*b[i];
+ }
+ dstimg -= dstr1;
+ if ( PREMULTIPLIED_ALPHA ) {
+ dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]);
+ PREMUL_ALPHA_LOOP dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], dstimg[alpha_PC]);
+ } else {
+ for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]);
+ }
+ }
+ }
+}
+
+// Filters over 1st dimension
+// Assumes kernel is symmetric
+// Kernel should have scr_len+1 elements
+template<typename PT, unsigned int PC>
+static void
+filter2D_FIR(PT *const dst, int const dstr1, int const dstr2,
+ PT const *const src, int const sstr1, int const sstr2,
+ int const n1, int const n2, FIRValue const *const kernel, int const scr_len, int const num_threads)
+{
+ // Past pixels seen (to enable in-place operation)
+ PT history[scr_len+1][PC];
+
+INK_UNUSED(num_threads); // suppresses unused argument compiler warning
+#if HAVE_OPENMP
+#pragma omp parallel for num_threads(num_threads) private(history)
+#endif // HAVE_OPENMP
+ for ( int c2 = 0 ; c2 < n2 ; c2++ ) {
+
+ // corresponding line in the source buffer
+ int const src_line = c2 * sstr2;
+
+ // current line in the output buffer
+ int const dst_line = c2 * dstr2;
+
+ int skipbuf[4] = {INT_MIN, INT_MIN, INT_MIN, INT_MIN};
+
+ // history initialization
+ PT imin[PC]; copy_n(src + src_line, PC, imin);
+ for(int i=0; i<scr_len; i++) copy_n(imin, PC, history[i]);
+
+ for ( int c1 = 0 ; c1 < n1 ; c1++ ) {
+
+ int const src_disp = src_line + c1 * sstr1;
+ int const dst_disp = dst_line + c1 * dstr1;
+
+ // update history
+ for(int i=scr_len; i>0; i--) copy_n(history[i-1], PC, history[i]);
+ copy_n(src + src_disp, PC, history[0]);
+
+ // for all bytes of the pixel
+ for ( unsigned int byte = 0 ; byte < PC ; byte++) {
+
+ if(skipbuf[byte] > c1) continue;
+
+ FIRValue sum = 0;
+ int last_in = -1;
+ int different_count = 0;
+
+ // go over our point's neighbours in the history
+ for ( int i = 0 ; i <= scr_len ; i++ ) {
+ // value at the pixel
+ PT in_byte = history[i][byte];
+
+ // is it the same as last one we saw?
+ if(in_byte != last_in) different_count++;
+ last_in = in_byte;
+
+ // sum pixels weighted by the kernel
+ sum += in_byte * kernel[i];
+ }
+
+ // go over our point's neighborhood on x axis in the in buffer
+ int nb_src_disp = src_disp + byte;
+ for ( int i = 1 ; i <= scr_len ; i++ ) {
+ // the pixel we're looking at
+ int c1_in = c1 + i;
+ if (c1_in >= n1) {
+ c1_in = n1 - 1;
+ } else {
+ nb_src_disp += sstr1;
+ }
+
+ // value at the pixel
+ PT in_byte = src[nb_src_disp];
+
+ // is it the same as last one we saw?
+ if(in_byte != last_in) different_count++;
+ last_in = in_byte;
+
+ // sum pixels weighted by the kernel
+ sum += in_byte * kernel[i];
+ }
+
+ // store the result in bufx
+ dst[dst_disp + byte] = round_cast<PT>(sum);
+
+ // optimization: if there was no variation within this point's neighborhood,
+ // skip ahead while we keep seeing the same last_in byte:
+ // blurring flat color would not change it anyway
+ if (different_count <= 1) { // note that different_count is at least 1, because last_in is initialized to -1
+ int pos = c1 + 1;
+ int nb_src_disp = src_disp + (1+scr_len)*sstr1 + byte; // src_line + (pos+scr_len) * sstr1 + byte
+ int nb_dst_disp = dst_disp + (1) *dstr1 + byte; // dst_line + (pos) * sstr1 + byte
+ while(pos + scr_len < n1 && src[nb_src_disp] == last_in) {
+ dst[nb_dst_disp] = last_in;
+ pos++;
+ nb_src_disp += sstr1;
+ nb_dst_disp += dstr1;
+ }
+ skipbuf[byte] = pos;
+ }
+ }
+ }
+ }
+}
+
+static void
+gaussian_pass_IIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest,
+ IIRValue **tmpdata, int num_threads)
+{
+ // Filter variables
+ IIRValue b[N+1]; // scaling coefficient + filter coefficients (can be 10.21 fixed point)
+ double bf[N]; // computed filter coefficients
+ double M[N*N]; // matrix used for initialization procedure (has to be double)
+
+ // Compute filter
+ calcFilter(deviation, bf);
+ for(double & i : bf) i = -i;
+ b[0] = 1; // b[0] == alpha (scaling coefficient)
+ for(size_t i=0; i<N; i++) {
+ b[i+1] = bf[i];
+ b[0] -= b[i+1];
+ }
+
+ // Compute initialization matrix
+ calcTriggsSdikaM(bf, M);
+
+ int stride = cairo_image_surface_get_stride(src);
+ int w = cairo_image_surface_get_width(src);
+ int h = cairo_image_surface_get_height(src);
+ if (d != Geom::X) std::swap(w, h);
+
+ // Filter
+ switch (cairo_image_surface_get_format(src)) {
+ case CAIRO_FORMAT_A8: ///< Grayscale
+ filter2D_IIR<unsigned char,1,false>(
+ cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
+ cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
+ w, h, b, M, tmpdata, num_threads);
+ break;
+ case CAIRO_FORMAT_ARGB32: ///< Premultiplied 8 bit RGBA
+ filter2D_IIR<unsigned char,4,true>(
+ cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
+ cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
+ w, h, b, M, tmpdata, num_threads);
+ break;
+ default:
+ g_warning("gaussian_pass_IIR: unsupported image format");
+ };
+}
+
+static void
+gaussian_pass_FIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest,
+ int num_threads)
+{
+ int scr_len = _effect_area_scr(deviation);
+ // Filter kernel for x direction
+ std::vector<FIRValue> kernel(scr_len + 1);
+ _make_kernel(&kernel[0], deviation);
+
+ int stride = cairo_image_surface_get_stride(src);
+ int w = cairo_image_surface_get_width(src);
+ int h = cairo_image_surface_get_height(src);
+ if (d != Geom::X) std::swap(w, h);
+
+ // Filter (x)
+ switch (cairo_image_surface_get_format(src)) {
+ case CAIRO_FORMAT_A8: ///< Grayscale
+ filter2D_FIR<unsigned char,1>(
+ cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
+ cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
+ w, h, &kernel[0], scr_len, num_threads);
+ break;
+ case CAIRO_FORMAT_ARGB32: ///< Premultiplied 8 bit RGBA
+ filter2D_FIR<unsigned char,4>(
+ cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
+ cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
+ w, h, &kernel[0], scr_len, num_threads);
+ break;
+ default:
+ g_warning("gaussian_pass_FIR: unsupported image format");
+ };
+}
+
+void FilterGaussian::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *in = slot.getcairo(_input);
+ if (!in) return;
+
+ // We may need to transform input surface to correct color interpolation space. The input surface
+ // might be used as input to another primitive but it is likely that all the primitives in a given
+ // filter use the same color interpolation space so we don't copy the input before converting.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+ set_cairo_surface_ci( in, ci_fp );
+
+ // zero deviation = no change in output
+ if (_deviation_x <= 0 && _deviation_y <= 0) {
+ cairo_surface_t *cp = ink_cairo_surface_copy(in);
+ slot.set(_output, cp);
+ cairo_surface_destroy(cp);
+ return;
+ }
+
+ // Handle bounding box case.
+ double dx = _deviation_x;
+ double dy = _deviation_y;
+ if( slot.get_units().get_primitive_units() == SP_FILTER_UNITS_OBJECTBOUNDINGBOX ) {
+ Geom::OptRect const bbox = slot.get_units().get_item_bbox();
+ if( bbox ) {
+ dx *= (*bbox).width();
+ dy *= (*bbox).height();
+ }
+ }
+
+ Geom::Affine trans = slot.get_units().get_matrix_user2pb();
+
+ double deviation_x_orig = dx * trans.expansionX();
+ double deviation_y_orig = dy * trans.expansionY();
+
+ int device_scale = slot.get_device_scale();
+
+ deviation_x_orig *= device_scale;
+ deviation_y_orig *= device_scale;
+
+ cairo_format_t fmt = cairo_image_surface_get_format(in);
+ int bytes_per_pixel = 0;
+ switch (fmt) {
+ case CAIRO_FORMAT_A8:
+ bytes_per_pixel = 1; break;
+ case CAIRO_FORMAT_ARGB32:
+ default:
+ bytes_per_pixel = 4; break;
+ }
+
+#if HAVE_OPENMP
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int threads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
+#else
+ int threads = 1;
+#endif
+
+ int quality = slot.get_blurquality();
+ int x_step = 1 << _effect_subsample_step_log2(deviation_x_orig, quality);
+ int y_step = 1 << _effect_subsample_step_log2(deviation_y_orig, quality);
+ bool resampling = x_step > 1 || y_step > 1;
+ int w_orig = ink_cairo_surface_get_width(in); // Pixels
+ int h_orig = ink_cairo_surface_get_height(in);
+ int w_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(w_orig)/x_step))+1 : w_orig;
+ int h_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(h_orig)/y_step))+1 : h_orig;
+ double deviation_x = deviation_x_orig / x_step;
+ double deviation_y = deviation_y_orig / y_step;
+ int scr_len_x = _effect_area_scr(deviation_x);
+ int scr_len_y = _effect_area_scr(deviation_y);
+
+ // Decide which filter to use for X and Y
+ // This threshold was determined by trial-and-error for one specific machine,
+ // so there's a good chance that it's not optimal.
+ // Whatever you do, don't go below 1 (and preferably not even below 2), as
+ // the IIR filter gets unstable there.
+ bool use_IIR_x = deviation_x > 3;
+ bool use_IIR_y = deviation_y > 3;
+
+ // Temporary storage for IIR filter
+ // NOTE: This can be eliminated, but it reduces the precision a bit
+ IIRValue * tmpdata[threads];
+ std::fill_n(tmpdata, threads, (IIRValue*)0);
+ if ( use_IIR_x || use_IIR_y ) {
+ for(int i = 0; i < threads; ++i) {
+ tmpdata[i] = new IIRValue[std::max(w_downsampled,h_downsampled)*bytes_per_pixel];
+ }
+ }
+
+ cairo_surface_t *downsampled = nullptr;
+ if (resampling) {
+ // Divide by device scale as w_downsampled is in pixels while
+ // cairo_surface_create_similar() uses device units.
+ downsampled = cairo_surface_create_similar(in, cairo_surface_get_content(in),
+ w_downsampled/device_scale, h_downsampled/device_scale);
+ cairo_t *ct = cairo_create(downsampled);
+ cairo_scale(ct, static_cast<double>(w_downsampled)/w_orig, static_cast<double>(h_downsampled)/h_orig);
+ cairo_set_source_surface(ct, in, 0, 0);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ } else {
+ downsampled = ink_cairo_surface_copy(in);
+ }
+ cairo_surface_flush(downsampled);
+
+ if (scr_len_x > 0) {
+ if (use_IIR_x) {
+ gaussian_pass_IIR(Geom::X, deviation_x, downsampled, downsampled, tmpdata, threads);
+ } else {
+ gaussian_pass_FIR(Geom::X, deviation_x, downsampled, downsampled, threads);
+ }
+ }
+
+ if (scr_len_y > 0) {
+ if (use_IIR_y) {
+ gaussian_pass_IIR(Geom::Y, deviation_y, downsampled, downsampled, tmpdata, threads);
+ } else {
+ gaussian_pass_FIR(Geom::Y, deviation_y, downsampled, downsampled, threads);
+ }
+ }
+
+ // free the temporary data
+ if ( use_IIR_x || use_IIR_y ) {
+ for(int i = 0; i < threads; ++i) {
+ delete[] tmpdata[i];
+ }
+ }
+
+ cairo_surface_mark_dirty(downsampled);
+ if (resampling) {
+ cairo_surface_t *upsampled = cairo_surface_create_similar(downsampled, cairo_surface_get_content(downsampled),
+ w_orig/device_scale, h_orig/device_scale);
+ cairo_t *ct = cairo_create(upsampled);
+ cairo_scale(ct, static_cast<double>(w_orig)/w_downsampled, static_cast<double>(h_orig)/h_downsampled);
+ cairo_set_source_surface(ct, downsampled, 0, 0);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ set_cairo_surface_ci( upsampled, ci_fp );
+
+ slot.set(_output, upsampled);
+ cairo_surface_destroy(upsampled);
+ cairo_surface_destroy(downsampled);
+ } else {
+ set_cairo_surface_ci( downsampled, ci_fp );
+
+ slot.set(_output, downsampled);
+ cairo_surface_destroy(downsampled);
+ }
+}
+
+void FilterGaussian::area_enlarge(Geom::IntRect &area, Geom::Affine const &trans)
+{
+ int area_x = _effect_area_scr(_deviation_x * trans.expansionX());
+ int area_y = _effect_area_scr(_deviation_y * trans.expansionY());
+ // maximum is used because rotations can mix up these directions
+ // TODO: calculate a more tight-fitting rendering area
+ int area_max = std::max(area_x, area_y);
+ area.expandBy(area_max);
+}
+
+bool FilterGaussian::can_handle_affine(Geom::Affine const &)
+{
+ // Previously we tried to be smart and return true for rotations.
+ // However, the transform passed here is NOT the total transform
+ // from filter user space to screen.
+ // TODO: fix this, or replace can_handle_affine() with isotropic().
+ return false;
+}
+
+double FilterGaussian::complexity(Geom::Affine const &trans)
+{
+ int area_x = _effect_area_scr(_deviation_x * trans.expansionX());
+ int area_y = _effect_area_scr(_deviation_y * trans.expansionY());
+ return 2.0 * area_x * area_y;
+}
+
+void FilterGaussian::set_deviation(double deviation)
+{
+ if(std::isfinite(deviation) && deviation >= 0) {
+ _deviation_x = _deviation_y = deviation;
+ }
+}
+
+void FilterGaussian::set_deviation(double x, double y)
+{
+ if(std::isfinite(x) && x >= 0 && std::isfinite(y) && y >= 0) {
+ _deviation_x = x;
+ _deviation_y = y;
+ }
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-gaussian.h b/src/display/nr-filter-gaussian.h
new file mode 100644
index 0000000..28ffb00
--- /dev/null
+++ b/src/display/nr-filter-gaussian.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_GAUSSIAN_H
+#define SEEN_NR_FILTER_GAUSSIAN_H
+
+/*
+ * Gaussian blur renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * bulia byak
+ * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ *
+ * Copyright (C) 2006 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/forward.h>
+#include "display/nr-filter-primitive.h"
+
+enum {
+ BLUR_QUALITY_BEST = 2,
+ BLUR_QUALITY_BETTER = 1,
+ BLUR_QUALITY_NORMAL = 0,
+ BLUR_QUALITY_WORSE = -1,
+ BLUR_QUALITY_WORST = -2
+};
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterGaussian : public FilterPrimitive {
+public:
+ FilterGaussian();
+ static FilterPrimitive *create();
+ ~FilterGaussian() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &m) override;
+ bool can_handle_affine(Geom::Affine const &m) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ /**
+ * Set the standard deviation value for gaussian blur. Deviation along
+ * both axis is set to the provided value.
+ * Negative value, NaN and infinity are considered an error and no
+ * changes to filter state are made. If not set, default value of zero
+ * is used, which means the filter results in transparent black image.
+ */
+ void set_deviation(double deviation);
+ /**
+ * Set the standard deviation value for gaussian blur. First parameter
+ * sets the deviation alogn x-axis, second along y-axis.
+ * Negative value, NaN and infinity are considered an error and no
+ * changes to filter state are made. If not set, default value of zero
+ * is used, which means the filter results in transparent black image.
+ */
+ void set_deviation(double x, double y);
+
+ Glib::ustring name() override { return Glib::ustring("Gaussian Blur"); }
+
+private:
+ double _deviation_x;
+ double _deviation_y;
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+
+
+
+#endif /* __NR_FILTER_GAUSSIAN_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/display/nr-filter-image.cpp b/src/display/nr-filter-image.cpp
new file mode 100644
index 0000000..bfc2b6b
--- /dev/null
+++ b/src/display/nr-filter-image.cpp
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feImage filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007-2011 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "display/nr-filter-image.h"
+#include "document.h"
+#include "object/sp-item.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing.h"
+#include "display/drawing-item.h"
+#include "display/nr-filter.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+#include "enums.h"
+#include <glibmm/fileutils.h>
+
+namespace Inkscape {
+namespace Filters {
+
+FilterImage::FilterImage()
+ : SVGElem(nullptr)
+ , document(nullptr)
+ , feImageHref(nullptr)
+ , image(nullptr)
+ , broken_ref(false)
+{ }
+
+FilterPrimitive * FilterImage::create() {
+ return new FilterImage();
+}
+
+FilterImage::~FilterImage()
+{
+ if (feImageHref)
+ g_free(feImageHref);
+ delete image;
+}
+
+void FilterImage::render_cairo(FilterSlot &slot)
+{
+ std::cout << "FilterImage::render_cairo: Entrance" << std::endl;
+ if (!feImageHref)
+ return;
+
+ //cairo_surface_t *input = slot.getcairo(_input);
+
+ // Viewport is filter primitive area (in user coordinates).
+ // Note: viewport calculation in non-trivial. Do not rely
+ // on get_matrix_primitiveunits2pb().
+ Geom::Rect vp = filter_primitive_area( slot.get_units() );
+ slot.set_primitive_area(_output, vp); // Needed for tiling
+
+ double feImageX = vp.min()[Geom::X];
+ double feImageY = vp.min()[Geom::Y];
+ double feImageWidth = vp.width();
+ double feImageHeight = vp.height();
+
+ // feImage is suppose to use the same parameters as a normal SVG image.
+ // If a width or height is set to zero, the image is not suppose to be displayed.
+ // This does not seem to be what Firefox or Opera does, nor does the W3C displacement
+ // filter test expect this behavior. If the width and/or height are zero, we use
+ // the width and height of the object bounding box.
+ Geom::Affine m = slot.get_units().get_matrix_user2filterunits().inverse();
+ Geom::Point bbox_00 = Geom::Point(0,0) * m;
+ Geom::Point bbox_w0 = Geom::Point(1,0) * m;
+ Geom::Point bbox_0h = Geom::Point(0,1) * m;
+ double bbox_width = Geom::distance(bbox_00, bbox_w0);
+ double bbox_height = Geom::distance(bbox_00, bbox_0h);
+
+ if( feImageWidth == 0 ) feImageWidth = bbox_width;
+ if( feImageHeight == 0 ) feImageHeight = bbox_height;
+
+ int device_scale = slot.get_device_scale();
+
+ // Internal image, like <use>
+ if (from_element) {
+ std::cout << " Internal image" << std::endl;
+ if (!SVGElem) return;
+
+ // TODO: do not recreate the rendering tree every time
+ // TODO: the entire thing is a hack, we should give filter primitives an "update" method
+ // like the one for DrawingItems
+ document->ensureUpToDate();
+
+ Drawing drawing;
+ Geom::OptRect optarea = SVGElem->visualBounds();
+ if (!optarea) return;
+
+ unsigned const key = SPItem::display_key_new(1);
+ DrawingItem *ai = SVGElem->invoke_show(drawing, key, SP_ITEM_SHOW_DISPLAY);
+ if (!ai) {
+ g_warning("feImage renderer: error creating DrawingItem for SVG Element");
+ return;
+ }
+ drawing.setRoot(ai);
+
+ Geom::Rect area = *optarea;
+ Geom::Affine user2pb = slot.get_units().get_matrix_user2pb();
+
+ /* FIXME: These variables are currently unused. Why were they calculated?
+ double scaleX = feImageWidth / area.width();
+ double scaleY = feImageHeight / area.height();
+ */
+
+ Geom::Rect sa = slot.get_slot_area();
+ cairo_surface_t *out =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ sa.width() * device_scale,
+ sa.height() * device_scale);
+ cairo_surface_set_device_scale(out, device_scale, device_scale);
+
+ Inkscape::DrawingContext dc(out, sa.min());
+ dc.transform(user2pb); // we are now in primitive units
+ dc.translate(feImageX, feImageY);
+// dc.scale(scaleX, scaleY); No scaling should be done
+
+ Geom::IntRect render_rect = area.roundOutwards();
+// dc.translate(render_rect.min()); This seems incorrect
+
+ // Update to renderable state
+ drawing.update(render_rect);
+ drawing.render(dc, render_rect);
+ SVGElem->invoke_hide(key);
+
+ // For the moment, we'll assume that any image is in sRGB color space
+ set_cairo_surface_ci(out, SP_CSS_COLOR_INTERPOLATION_SRGB);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+ std::cout << " feImage: out: " << cairo_image_surface_get_width( out) << std::endl;
+ std::cout << "FilterImage::render_cairo: Exit 2" << std::endl;
+ return;
+ }
+
+ // External image, like <image>
+ if (!image && !broken_ref) {
+ std::cout << " External image" << std::endl;
+ broken_ref = true;
+
+ /* TODO: If feImageHref is absolute, then use that (preferably handling the
+ * case that it's not a file URI). Otherwise, go up the tree looking
+ * for an xml:base attribute, and use that as the base URI for resolving
+ * the relative feImageHref URI. Otherwise, if document->base is valid,
+ * then use that as the base URI. Otherwise, use feImageHref directly
+ * (i.e. interpreting it as relative to our current working directory).
+ * (See http://www.w3.org/TR/xmlbase/#resolution .) */
+ gchar *fullname = feImageHref;
+ if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) {
+ // Try to load from relative position combined with document base
+ if( document ) {
+ fullname = g_build_filename( document->getDocumentBase(), feImageHref, NULL );
+ }
+ }
+ if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) {
+ // Should display Broken Image png.
+ g_warning("FilterImage::render: Can not find: %s", feImageHref );
+ return;
+ }
+ image = Inkscape::Pixbuf::create_from_file(fullname);
+ if( fullname != feImageHref ) g_free( fullname );
+
+ if ( !image ) {
+ g_warning("FilterImage::render: failed to load image: %s", feImageHref);
+ return;
+ }
+
+ broken_ref = false;
+ }
+
+ if (broken_ref) {
+ return;
+ }
+
+ cairo_surface_t *image_surface = image->getSurfaceRaw();
+ std::cout << " image: " << cairo_image_surface_get_width(image_surface) << std::endl;
+ Geom::Rect sa = slot.get_slot_area();
+ cairo_surface_t *out = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ sa.width() * device_scale, sa.height() * device_scale);
+ cairo_surface_set_device_scale( out, device_scale, device_scale );
+ std::cout << " out: " << cairo_image_surface_get_width(out) << std::endl;
+
+ // For the moment, we'll assume that any image is in sRGB color space
+ // set_cairo_surface_ci(out, SP_CSS_COLOR_INTERPOLATION_SRGB);
+ // This seemed like a sensible thing to do but it breaks filters-displace-01-f.svg
+
+ cairo_t *ct = cairo_create(out);
+ cairo_translate(ct, -sa.min()[Geom::X], -sa.min()[Geom::Y]);
+
+ // now ct is in pb coordinates, note the feWidth etc. are in user units
+ ink_cairo_transform(ct, slot.get_units().get_matrix_user2pb());
+
+ // now ct is in the coordinates of feImageX etc.
+
+ // Now that we have the viewport, we must map image inside.
+ // Partially copied from sp-image.cpp.
+
+ // Do nothing if preserveAspectRatio is "none".
+ if( aspect_align != SP_ASPECT_NONE ) {
+
+ // Check aspect ratio of image vs. viewport
+ double feAspect = feImageHeight/feImageWidth;
+ double aspect = (double)image->height()/(double)image->width();
+ bool ratio = (feAspect < aspect);
+
+ double ax, ay; // Align side
+ switch( aspect_align ) {
+ case SP_ASPECT_XMIN_YMIN:
+ ax = 0.0;
+ ay = 0.0;
+ break;
+ case SP_ASPECT_XMID_YMIN:
+ ax = 0.5;
+ ay = 0.0;
+ break;
+ case SP_ASPECT_XMAX_YMIN:
+ ax = 1.0;
+ ay = 0.0;
+ break;
+ case SP_ASPECT_XMIN_YMID:
+ ax = 0.0;
+ ay = 0.5;
+ break;
+ case SP_ASPECT_XMID_YMID:
+ ax = 0.5;
+ ay = 0.5;
+ break;
+ case SP_ASPECT_XMAX_YMID:
+ ax = 1.0;
+ ay = 0.5;
+ break;
+ case SP_ASPECT_XMIN_YMAX:
+ ax = 0.0;
+ ay = 1.0;
+ break;
+ case SP_ASPECT_XMID_YMAX:
+ ax = 0.5;
+ ay = 1.0;
+ break;
+ case SP_ASPECT_XMAX_YMAX:
+ ax = 1.0;
+ ay = 1.0;
+ break;
+ default:
+ ax = 0.0;
+ ay = 0.0;
+ break;
+ }
+
+ if( aspect_clip == SP_ASPECT_SLICE ) {
+ // image clipped by viewbox
+
+ if( ratio ) {
+ // clip top/bottom
+ feImageY -= ay * (feImageWidth * aspect - feImageHeight);
+ feImageHeight = feImageWidth * aspect;
+ } else {
+ // clip sides
+ feImageX -= ax * (feImageHeight / aspect - feImageWidth);
+ feImageWidth = feImageHeight / aspect;
+ }
+
+ } else {
+ // image fits into viewbox
+
+ if( ratio ) {
+ // fit to height
+ feImageX += ax * (feImageWidth - feImageHeight / aspect );
+ feImageWidth = feImageHeight / aspect;
+ } else {
+ // fit to width
+ feImageY += ay * (feImageHeight - feImageWidth * aspect);
+ feImageHeight = feImageWidth * aspect;
+ }
+ }
+ }
+
+ double scaleX = feImageWidth / image->width();
+ double scaleY = feImageHeight / image->height();
+
+ cairo_translate(ct, feImageX, feImageY);
+ cairo_scale(ct, scaleX, scaleY);
+ cairo_set_source_surface(ct, image_surface, 0, 0);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ slot.set(_output, out);
+ std::cout << "FilterImage::render_cairo: Exit 2" << std::endl;
+}
+
+bool FilterImage::can_handle_affine(Geom::Affine const &)
+{
+ return true;
+}
+
+double FilterImage::complexity(Geom::Affine const &)
+{
+ // TODO: right now we cannot actually measure this in any meaningful way.
+ return 1.1;
+}
+
+void FilterImage::set_href(const gchar *href){
+
+ if (feImageHref) g_free (feImageHref);
+ feImageHref = (href) ? g_strdup (href) : nullptr;
+
+ delete image;
+ image = nullptr;
+ broken_ref = false;
+}
+
+void FilterImage::set_document(SPDocument *doc){
+ document = doc;
+}
+
+void FilterImage::set_align( unsigned int align ) {
+ aspect_align = align;
+}
+
+void FilterImage::set_clip( unsigned int clip ) {
+ aspect_clip = clip;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-image.h b/src/display/nr-filter-image.h
new file mode 100644
index 0000000..6080bb4
--- /dev/null
+++ b/src/display/nr-filter-image.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_IMAGE_H
+#define SEEN_NR_FILTER_IMAGE_H
+
+/*
+ * feImage filter primitive renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+
+class SPDocument;
+class SPItem;
+
+namespace Inkscape {
+class Pixbuf;
+
+namespace Filters {
+class FilterSlot;
+
+class FilterImage : public FilterPrimitive {
+public:
+ FilterImage();
+ static FilterPrimitive *create();
+ ~FilterImage() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_document( SPDocument *document );
+ void set_href(char const *href);
+ void set_align( unsigned int align );
+ void set_clip( unsigned int clip );
+ bool from_element;
+ SPItem* SVGElem;
+
+ Glib::ustring name() override { return Glib::ustring("Image"); }
+
+private:
+ SPDocument *document;
+ char *feImageHref;
+ Inkscape::Pixbuf *image;
+ float feImageX, feImageY, feImageWidth, feImageHeight;
+ unsigned int aspect_align, aspect_clip;
+ bool broken_ref;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/display/nr-filter-merge.cpp b/src/display/nr-filter-merge.cpp
new file mode 100644
index 0000000..9ebfe40
--- /dev/null
+++ b/src/display/nr-filter-merge.cpp
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feMerge filter effect renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "display/cairo-utils.h"
+#include "display/nr-filter-merge.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-utils.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterMerge::FilterMerge() :
+ _input_image(1, NR_FILTER_SLOT_NOT_SET)
+{}
+
+FilterPrimitive * FilterMerge::create() {
+ return new FilterMerge();
+}
+
+FilterMerge::~FilterMerge()
+= default;
+
+void FilterMerge::render_cairo(FilterSlot &slot)
+{
+ if (_input_image.empty()) return;
+
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+ }
+
+ Geom::Rect vp = filter_primitive_area( slot.get_units() );
+ slot.set_primitive_area(_output, vp); // Needed for tiling
+
+ // output is RGBA if at least one input is RGBA
+ bool rgba32 = false;
+ cairo_surface_t *out = nullptr;
+ for (int & i : _input_image) {
+ cairo_surface_t *in = slot.getcairo(i);
+ if (cairo_surface_get_content(in) == CAIRO_CONTENT_COLOR_ALPHA) {
+ out = ink_cairo_surface_create_identical(in);
+ set_cairo_surface_ci( out, ci_fp );
+ rgba32 = true;
+ break;
+ }
+ }
+
+ if (!rgba32) {
+ out = ink_cairo_surface_create_identical(slot.getcairo(_input_image[0]));
+ }
+ cairo_t *out_ct = cairo_create(out);
+
+ for (int & i : _input_image) {
+ cairo_surface_t *in = slot.getcairo(i);
+
+ set_cairo_surface_ci( in, ci_fp );
+ cairo_set_source_surface(out_ct, in, 0, 0);
+ cairo_paint(out_ct);
+ }
+
+ cairo_destroy(out_ct);
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterMerge::can_handle_affine(Geom::Affine const &)
+{
+ // Merge is a per-pixel primitive and is immutable under transformations
+ return true;
+}
+
+double FilterMerge::complexity(Geom::Affine const &)
+{
+ return 1.02;
+}
+
+bool FilterMerge::uses_background()
+{
+ for (int input : _input_image) {
+ if (input == NR_FILTER_BACKGROUNDIMAGE || input == NR_FILTER_BACKGROUNDALPHA) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void FilterMerge::set_input(int slot) {
+ _input_image[0] = slot;
+}
+
+void FilterMerge::set_input(int input, int slot) {
+ if (input < 0) return;
+
+ if (static_cast<int>(_input_image.size()) > input) {
+ _input_image[input] = slot;
+ } else {
+ for (int i = static_cast<int>(_input_image.size()) ; i < input ; i++) {
+ _input_image.push_back(NR_FILTER_SLOT_NOT_SET);
+ }
+ _input_image.push_back(slot);
+ }
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-merge.h b/src/display/nr-filter-merge.h
new file mode 100644
index 0000000..72356dc
--- /dev/null
+++ b/src/display/nr-filter-merge.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_MERGE_H
+#define SEEN_NR_FILTER_MERGE_H
+
+/*
+ * feMerge filter effect renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "display/nr-filter-primitive.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterMerge : public FilterPrimitive {
+public:
+ FilterMerge();
+ static FilterPrimitive *create();
+ ~FilterMerge() override;
+
+ void render_cairo(FilterSlot &) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+ bool uses_background() override;
+
+ void set_input(int input) override;
+ void set_input(int input, int slot) override;
+
+ Glib::ustring name() override { return Glib::ustring("Merge"); }
+
+private:
+ std::vector<int> _input_image;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_MERGE_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/display/nr-filter-morphology.cpp b/src/display/nr-filter-morphology.cpp
new file mode 100644
index 0000000..700d69f
--- /dev/null
+++ b/src/display/nr-filter-morphology.cpp
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feMorphology filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.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 <cmath>
+#include <algorithm>
+#include <deque>
+#include <functional>
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-morphology.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterMorphology::FilterMorphology()
+= default;
+
+FilterPrimitive * FilterMorphology::create() {
+ return new FilterMorphology();
+}
+
+FilterMorphology::~FilterMorphology()
+= default;
+
+enum MorphologyOp {
+ ERODE,
+ DILATE
+};
+
+namespace {
+
+/* This performs one "half" of the morphology operation by calculating
+ * the componentwise extreme in the specified axis with the given radius.
+ * Extreme of row extremes is equal to the extreme of components, so this
+ * doesn't change the result.
+ * The algorithm is due to: Petr DoklĂĄdal, Eva DoklĂĄdalovĂĄ (2011), "Computationally efficient, one-pass algorithm for morphological filters"
+ * TODO: Currently only the 1D algorithm is implemented, but it should not be too difficult (and at the very least more memory efficient) to implement the full 2D algorithm.
+ * One problem with the 2D algorithm is that it is harder to parallelize.
+ */
+template <typename Comparison, Geom::Dim2 axis, int BPP>
+void morphologicalFilter1D(cairo_surface_t * const input, cairo_surface_t * const out, double radius) {
+ Comparison comp;
+
+ int w = cairo_image_surface_get_width(out);
+ int h = cairo_image_surface_get_height(out);
+ if (axis == Geom::Y) std::swap(w,h);
+
+ int stridein = cairo_image_surface_get_stride(input);
+ int strideout = cairo_image_surface_get_stride(out);
+
+ unsigned char *in_data = cairo_image_surface_get_data(input);
+ unsigned char *out_data = cairo_image_surface_get_data(out);
+
+ int ri = round(radius); // TODO: Support fractional radii?
+ int wi = 2*ri+1;
+
+ #if HAVE_OPENMP
+ int limit = w * h;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int numOfThreads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
+ (void) numOfThreads; // suppress unused variable warning
+ #pragma omp parallel for if(limit > OPENMP_THRESHOLD) num_threads(numOfThreads)
+ #endif // HAVE_OPENMP
+ for (int i = 0; i < h; ++i) {
+ // TODO: Store position and value in one 32 bit integer? 24 bits should be enough for a position, it would be quite strange to have an image with a width/height of more than 16 million(!).
+ std::deque< std::pair<int,unsigned char> > vals[BPP]; // In my tests it was actually slightly faster to allocate it here than allocate it once for all threads and retrieving the correct set based on the thread id.
+
+ // Initialize with transparent black
+ for(int p = 0; p < BPP; ++p) {
+ vals[p].push_back(std::pair<int,unsigned char>(-1,0)); // TODO: Only do this when performing an erosion?
+ }
+
+ // Process "row"
+ unsigned char *in_p = in_data + i * (axis == Geom::X ? stridein : BPP);
+ unsigned char *out_p = out_data + i * (axis == Geom::X ? strideout : BPP);
+ /* This is the "short but slow" version, which might be easier to follow, the longer but faster version follows.
+ for (int j = 0; j < w+ri; ++j) {
+ for(int p = 0; p < BPP; ++p) { // Iterate over channels
+ // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
+ if (!vals[p].empty() && vals[p].front().first+wi <= j) vals[p].pop_front(); // out-of-range
+ if (j < w) {
+ while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
+ vals[p].push_back(std::make_pair(j, *in_p));
+ ++in_p;
+ } else if (j == w) { // Transparent black beyond the image. TODO: Only do this when performing an erosion?
+ while(!vals[p].empty() && !comp(vals[p].back().second, 0)) vals[p].pop_back();
+ vals[p].push_back(std::make_pair(j, 0));
+ }
+ // Set output
+ if (j >= ri) {
+ *out_p = vals[p].front().second;
+ ++out_p;
+ }
+ }
+ if (axis == Geom::Y && j < w ) in_p += stridein - BPP;
+ if (axis == Geom::Y && j >= ri) out_p += strideout - BPP;
+ }*/
+ for (int j = 0; j < std::min(ri,w); ++j) {
+ for(int p = 0; p < BPP; ++p) { // Iterate over channels
+ // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
+ if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
+ while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
+ vals[p].push_back(std::make_pair(j+wi, *in_p));
+ ++in_p;
+ }
+ if (axis == Geom::Y) in_p += stridein - BPP;
+ }
+ // We have now done all preparatory work.
+ // If w<=ri, then the following loop does nothing (which is as it should).
+ for (int j = ri; j < w; ++j) {
+ for(int p = 0; p < BPP; ++p) { // Iterate over channels
+ // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
+ if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
+ while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
+ vals[p].push_back(std::make_pair(j+wi, *in_p));
+ ++in_p;
+ // Set output
+ *out_p = vals[p].front().second;
+ ++out_p;
+ }
+ if (axis == Geom::Y) {
+ in_p += stridein - BPP;
+ out_p += strideout - BPP;
+ }
+ }
+ // We have now done all work which involves both input and output.
+ // The following loop makes sure that the border is handled correctly.
+ for(int p = 0; p < BPP; ++p) { // Iterate over channels
+ while(!vals[p].empty() && !comp(vals[p].back().second, 0)) vals[p].pop_back();
+ vals[p].push_back(std::make_pair(w+wi, 0));
+ }
+ // Now we just have to finish the output.
+ for (int j = std::max(w,ri); j < w+ri; ++j) {
+ for(int p = 0; p < BPP; ++p) { // Iterate over channels
+ // Remove out-of-range values
+ if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
+ // Set output
+ *out_p = vals[p].front().second;
+ ++out_p;
+ }
+ if (axis == Geom::Y) out_p += strideout - BPP;
+ }
+ }
+
+ cairo_surface_mark_dirty(out);
+}
+
+} // end anonymous namespace
+
+void FilterMorphology::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+
+ if (xradius == 0.0 || yradius == 0.0) {
+ // output is transparent black
+ cairo_surface_t *out = ink_cairo_surface_create_identical(input);
+ copy_cairo_surface_ci(input, out);
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+ return;
+ }
+
+ int device_scale = slot.get_device_scale();
+ Geom::Affine p2pb = slot.get_units().get_matrix_primitiveunits2pb();
+ double xr = fabs(xradius * p2pb.expansionX()) * device_scale;
+ double yr = fabs(yradius * p2pb.expansionY()) * device_scale;
+ int bpp = cairo_image_surface_get_format(input) == CAIRO_FORMAT_A8 ? 1 : 4;
+
+ cairo_surface_t *interm = ink_cairo_surface_create_identical(input);
+
+ if (Operator == MORPHOLOGY_OPERATOR_DILATE) {
+ if (bpp == 1) {
+ morphologicalFilter1D< std::greater<unsigned char>, Geom::X, 1 >(input, interm, xr);
+ } else {
+ morphologicalFilter1D< std::greater<unsigned char>, Geom::X, 4 >(input, interm, xr);
+ }
+ } else {
+ if (bpp == 1) {
+ morphologicalFilter1D< std::less<unsigned char>, Geom::X, 1 >(input, interm, xr);
+ } else {
+ morphologicalFilter1D< std::less<unsigned char>, Geom::X, 4 >(input, interm, xr);
+ }
+ }
+
+ cairo_surface_t *out = ink_cairo_surface_create_identical(interm);
+
+ // color_interpolation_filters for out same as input. See spec (DisplacementMap).
+ copy_cairo_surface_ci(input, out);
+
+ if (Operator == MORPHOLOGY_OPERATOR_DILATE) {
+ if (bpp == 1) {
+ morphologicalFilter1D< std::greater<unsigned char>, Geom::Y, 1 >(interm, out, yr);
+ } else {
+ morphologicalFilter1D< std::greater<unsigned char>, Geom::Y, 4 >(interm, out, yr);
+ }
+ } else {
+ if (bpp == 1) {
+ morphologicalFilter1D< std::less<unsigned char>, Geom::Y, 1 >(interm, out, yr);
+ } else {
+ morphologicalFilter1D< std::less<unsigned char>, Geom::Y, 4 >(interm, out, yr);
+ }
+ }
+
+ cairo_surface_destroy(interm);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+void FilterMorphology::area_enlarge(Geom::IntRect &area, Geom::Affine const &trans)
+{
+ int enlarge_x = ceil(xradius * trans.expansionX());
+ int enlarge_y = ceil(yradius * trans.expansionY());
+
+ area.expandBy(enlarge_x, enlarge_y);
+}
+
+double FilterMorphology::complexity(Geom::Affine const &trans)
+{
+ int enlarge_x = ceil(xradius * trans.expansionX());
+ int enlarge_y = ceil(yradius * trans.expansionY());
+ return enlarge_x * enlarge_y;
+}
+
+void FilterMorphology::set_operator(FilterMorphologyOperator &o){
+ Operator = o;
+}
+
+void FilterMorphology::set_xradius(double x){
+ xradius = x;
+}
+
+void FilterMorphology::set_yradius(double y){
+ yradius = y;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-morphology.h b/src/display/nr-filter-morphology.h
new file mode 100644
index 0000000..f91192b
--- /dev/null
+++ b/src/display/nr-filter-morphology.h
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_MORPHOLOGY_H
+#define SEEN_NR_FILTER_MORPHOLOGY_H
+
+/*
+ * feMorphology filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+enum FilterMorphologyOperator {
+ MORPHOLOGY_OPERATOR_ERODE,
+ MORPHOLOGY_OPERATOR_DILATE,
+ MORPHOLOGY_OPERATOR_END
+};
+
+class FilterMorphology : public FilterPrimitive {
+public:
+ FilterMorphology();
+ static FilterPrimitive *create();
+ ~FilterMorphology() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_operator(FilterMorphologyOperator &o);
+ void set_xradius(double x);
+ void set_yradius(double y);
+
+ Glib::ustring name() override { return Glib::ustring("Morphology"); }
+
+private:
+ FilterMorphologyOperator Operator;
+ double xradius;
+ double yradius;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_MORPHOLOGY_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/display/nr-filter-offset.cpp b/src/display/nr-filter-offset.cpp
new file mode 100644
index 0000000..2f73389
--- /dev/null
+++ b/src/display/nr-filter-offset.cpp
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feOffset filter primitive renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/cairo-utils.h"
+#include "display/nr-filter-offset.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+using Geom::X;
+using Geom::Y;
+
+FilterOffset::FilterOffset() :
+ dx(0), dy(0)
+{}
+
+FilterPrimitive * FilterOffset::create() {
+ return new FilterOffset();
+}
+
+FilterOffset::~FilterOffset()
+= default;
+
+void FilterOffset::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *in = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_identical(in);
+ // color_interpolation_filters for out same as in. See spec (DisplacementMap).
+ copy_cairo_surface_ci(in, out);
+ cairo_t *ct = cairo_create(out);
+
+ Geom::Rect vp = filter_primitive_area( slot.get_units() );
+ slot.set_primitive_area(_output, vp); // Needed for tiling
+
+ Geom::Affine p2pb = slot.get_units().get_matrix_primitiveunits2pb();
+ double x = dx * p2pb.expansionX();
+ double y = dy * p2pb.expansionY();
+
+ cairo_set_source_surface(ct, in, x, y);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+bool FilterOffset::can_handle_affine(Geom::Affine const &)
+{
+ return true;
+}
+
+void FilterOffset::set_dx(double amount) {
+ dx = amount;
+}
+
+void FilterOffset::set_dy(double amount) {
+ dy = amount;
+}
+
+void FilterOffset::area_enlarge(Geom::IntRect &area, Geom::Affine const &trans)
+{
+ Geom::Point offset(dx, dy);
+ offset *= trans;
+ offset[X] -= trans[4];
+ offset[Y] -= trans[5];
+ double x0, y0, x1, y1;
+ x0 = area.left();
+ y0 = area.top();
+ x1 = area.right();
+ y1 = area.bottom();
+
+ if (offset[X] > 0) {
+ x0 -= ceil(offset[X]);
+ } else {
+ x1 -= floor(offset[X]);
+ }
+
+ if (offset[Y] > 0) {
+ y0 -= ceil(offset[Y]);
+ } else {
+ y1 -= floor(offset[Y]);
+ }
+ area = Geom::IntRect(x0, y0, x1, y1);
+}
+
+double FilterOffset::complexity(Geom::Affine const &)
+{
+ return 1.02;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-offset.h b/src/display/nr-filter-offset.h
new file mode 100644
index 0000000..30e8527
--- /dev/null
+++ b/src/display/nr-filter-offset.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_OFFSET_H
+#define SEEN_NR_FILTER_OFFSET_H
+
+/*
+ * feOffset filter primitive renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterOffset : public FilterPrimitive {
+public:
+ FilterOffset();
+ static FilterPrimitive *create();
+ ~FilterOffset() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ bool can_handle_affine(Geom::Affine const &) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ void set_dx(double amount);
+ void set_dy(double amount);
+
+ Glib::ustring name() override { return Glib::ustring("Offset"); }
+
+private:
+ double dx, dy;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_OFFSET_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/display/nr-filter-primitive.cpp b/src/display/nr-filter-primitive.cpp
new file mode 100644
index 0000000..65133ea
--- /dev/null
+++ b/src/display/nr-filter-primitive.cpp
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG filters rendering
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ * Tavmjong Bah <tavmjong@free.fr> (primitive subregion)
+ *
+ * Copyright (C) 2006 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-types.h"
+#include "svg/svg-length.h"
+
+#include "inkscape.h"
+#include "desktop.h"
+
+#include "document.h"
+#include "style.h"
+
+namespace Inkscape {
+namespace Filters {
+
+using Geom::X;
+using Geom::Y;
+
+FilterPrimitive::FilterPrimitive()
+{
+ _input = NR_FILTER_SLOT_NOT_SET;
+ _output = NR_FILTER_SLOT_NOT_SET;
+
+ // Primitive subregion, should default to the union of all subregions of referenced nodes
+ // (i.e. other filter primitives except feTile). If no referenced nodes, defaults to filter
+ // region expressed in percent. At the moment, we do not check referenced nodes.
+
+ // We must keep track if a value is set or not, if not set then the region defaults to 0%, 0%,
+ // 100%, 100% ("x", "y", "width", "height") of the -> filter <- region. If set, then
+ // percentages are in terms of bounding box or viewbox, depending on value of "primitiveUnits".
+
+ // NB: SVGLength.set takes prescaled percent values: 1 means 100%
+ _subregion_x.unset(SVGLength::PERCENT, 0, 0);
+ _subregion_y.unset(SVGLength::PERCENT, 0, 0);
+ _subregion_width.unset(SVGLength::PERCENT, 1, 0);
+ _subregion_height.unset(SVGLength::PERCENT, 1, 0);
+
+ _style = nullptr;
+}
+
+FilterPrimitive::~FilterPrimitive()
+{
+ if(_style)
+ sp_style_unref(_style);
+}
+
+void FilterPrimitive::render_cairo(FilterSlot &slot)
+{
+ // passthrough
+ cairo_surface_t *in = slot.getcairo(_input);
+ slot.set(_output, in);
+}
+
+void FilterPrimitive::area_enlarge(Geom::IntRect &/*area*/, Geom::Affine const &/*m*/)
+{
+ // This doesn't need to do anything by default
+}
+
+void FilterPrimitive::set_input(int slot) {
+ set_input(0, slot);
+}
+
+void FilterPrimitive::set_input(int input, int slot) {
+ if (input == 0) _input = slot;
+}
+
+void FilterPrimitive::set_output(int slot) {
+ if (slot >= 0) _output = slot;
+}
+
+// We need to copy reference even if unset as we need to know if
+// someone has unset a value.
+void FilterPrimitive::set_x(SVGLength const &length)
+{
+ _subregion_x = length;
+}
+
+void FilterPrimitive::set_y(SVGLength const &length)
+{
+ _subregion_y = length;
+}
+void FilterPrimitive::set_width(SVGLength const &length)
+{
+ _subregion_width = length;
+}
+void FilterPrimitive::set_height(SVGLength const &length)
+{
+ _subregion_height = length;
+}
+
+void FilterPrimitive::set_subregion(SVGLength const &x, SVGLength const &y,
+ SVGLength const &width, SVGLength const &height) {
+ _subregion_x = x;
+ _subregion_y = y;
+ _subregion_width = width;
+ _subregion_height = height;
+}
+
+Geom::Rect FilterPrimitive::filter_primitive_area(FilterUnits const &units)
+{
+ Geom::OptRect const fa_opt = units.get_filter_area();
+ if (!fa_opt) {
+ std::cerr << "FilterPrimitive::filter_primitive_area: filter area undefined." << std::endl;
+ return Geom::Rect (Geom::Point(0.,0.), Geom::Point(0.,0.));
+ }
+ Geom::Rect fa = *fa_opt;
+
+ // x, y, width, and height are independently defined (i.e. one can be defined, by default, to
+ // the filter area (via default value ) while another is defined relative to the bounding
+ // box). It is better to keep track of them separately and then compose the Rect at the end.
+ double x = 0;
+ double y = 0;
+ double width = 0;
+ double height = 0;
+
+ // If subregion not set, by special case use filter region.
+ if( !_subregion_x._set ) x = fa.min()[X];
+ if( !_subregion_y._set ) y = fa.min()[Y];
+ if( !_subregion_width._set ) width = fa.width();
+ if( !_subregion_height._set ) height = fa.height();
+
+ if( units.get_primitive_units() == SP_FILTER_UNITS_OBJECTBOUNDINGBOX ) {
+
+ Geom::OptRect const bb_opt = units.get_item_bbox();
+ if (!bb_opt) {
+ std::cerr << "FilterPrimitive::filter_primitive_area: bounding box undefined and 'primitiveUnits' is 'objectBoundingBox'." << std::endl;
+ return Geom::Rect (Geom::Point(0.,0.), Geom::Point(0.,0.));
+ }
+ Geom::Rect bb = *bb_opt;
+
+
+ // Update computed values for ex, em, %.
+ // For %, assumes primitive unit is objectBoundingBox.
+ // TODO: fetch somehow the object ex and em lengths; 12, 6 are just dummy values.
+ double len_x = bb.width();
+ double len_y = bb.height();
+ _subregion_x.update(12, 6, len_x);
+ _subregion_y.update(12, 6, len_y);
+ _subregion_width.update(12, 6, len_x);
+ _subregion_height.update(12, 6, len_y);
+
+ // Values are in terms of fraction of bounding box.
+ if( _subregion_x._set && (_subregion_x.unit != SVGLength::PERCENT) ) x = bb.min()[X] + bb.width() * _subregion_x.value;
+ if( _subregion_y._set && (_subregion_y.unit != SVGLength::PERCENT) ) y = bb.min()[Y] + bb.height() * _subregion_y.value;
+ if( _subregion_width._set && (_subregion_width.unit != SVGLength::PERCENT) ) width = bb.width() * _subregion_width.value;
+ if( _subregion_height._set && (_subregion_height.unit != SVGLength::PERCENT) ) height = bb.height() * _subregion_height.value;
+ // Values are in terms of percent
+ if( _subregion_x._set && (_subregion_x.unit == SVGLength::PERCENT) ) x = bb.min()[X] + _subregion_x.computed;
+ if( _subregion_y._set && (_subregion_y.unit == SVGLength::PERCENT) ) y = bb.min()[Y] + _subregion_y.computed;
+ if( _subregion_width._set && (_subregion_width.unit == SVGLength::PERCENT) ) width = _subregion_width.computed;
+ if( _subregion_height._set && (_subregion_height.unit == SVGLength::PERCENT) ) height = _subregion_height.computed;
+ } else {
+ // Values are in terms of user space coordinates or percent of viewport (already calculated in sp-filter-primitive.cpp).
+ if( _subregion_x._set ) x = _subregion_x.computed;
+ if( _subregion_y._set ) y = _subregion_y.computed;
+ if( _subregion_width._set ) width = _subregion_width.computed;
+ if( _subregion_height._set ) height = _subregion_height.computed;
+ }
+
+ return Geom::Rect (Geom::Point(x,y), Geom::Point(x + width, y + height));
+}
+
+void FilterPrimitive::setStyle(SPStyle *style)
+{
+ if( style != _style ) {
+ if (style) sp_style_ref(style);
+ if (_style) sp_style_unref(_style);
+ _style = style;
+ }
+}
+
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-primitive.h b/src/display/nr-filter-primitive.h
new file mode 100644
index 0000000..89cffac
--- /dev/null
+++ b/src/display/nr-filter-primitive.h
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG filters rendering
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006-2007 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_NR_FILTER_PRIMITIVE_H
+#define SEEN_NR_FILTER_PRIMITIVE_H
+
+#include <2geom/forward.h>
+#include <2geom/rect.h>
+
+#include <glibmm/ustring.h>
+
+#include "display/nr-filter-types.h"
+#include "svg/svg-length.h"
+
+class SPStyle;
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+class FilterUnits;
+
+class FilterPrimitive {
+public:
+ FilterPrimitive();
+ virtual ~FilterPrimitive();
+
+ virtual void render_cairo(FilterSlot &slot);
+ virtual int render(FilterSlot & /*slot*/, FilterUnits const & /*units*/) { return 0; } // pure virtual?
+ virtual void area_enlarge(Geom::IntRect &area, Geom::Affine const &m);
+
+ /**
+ * Sets the input slot number 'slot' to be used as input in rendering
+ * filter primitive 'primitive'
+ * For filter primitive types accepting more than one input, this sets the
+ * first input.
+ * If any of the required input slots is not set, the output of previous
+ * filter primitive is used, or SourceGraphic if this is the first
+ * primitive for this filter.
+ */
+ virtual void set_input(int slot);
+
+ /**
+ * Sets the input slot number 'slot' to be user as input number 'input' in
+ * rendering filter primitive 'primitive'
+ * First input for a filter primitive is number 0. For primitives with
+ * attributes 'in' and 'in2', these are numbered 0 and 1, respectively.
+ * If any of required input slots for a filter is not set, the output of
+ * previous filter primitive is used, or SourceGraphic if this is the first
+ * filter primitive for this filter.
+ */
+ virtual void set_input(int input, int slot);
+
+ /**
+ * Sets the slot number 'slot' to be used as output from filter primitive
+ * 'primitive'
+ * If output slot for a filter element is not set, one of the unused image
+ * slots is used.
+ * It is an error to specify a pre-defined slot as 'slot'. Such call does
+ * not have any effect to the state of filter or its primitives.
+ */
+ virtual void set_output(int slot);
+
+ // returns cache score factor, reflecting the cost of rendering this filter
+ // this should return how many times slower this primitive is that normal rendering
+ virtual double complexity(Geom::Affine const &/*ctm*/) { return 1.0; }
+
+ virtual bool uses_background() {
+ if (_input == NR_FILTER_BACKGROUNDIMAGE || _input == NR_FILTER_BACKGROUNDALPHA) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the filter primitive subregion. Passing an unset length
+ * (length._set == false) WILL change the parameter as it is
+ * important to know if a parameter is unset.
+ */
+ void set_x(SVGLength const &length);
+ void set_y(SVGLength const &length);
+ void set_width(SVGLength const &length);
+ void set_height(SVGLength const &length);
+ void set_subregion(SVGLength const &x, SVGLength const &y,
+ SVGLength const &width, SVGLength const &height);
+
+ /**
+ * Resets the filter primitive subregion to its default value
+ */
+ void reset_subregion(); // Not implemented
+
+ /**
+ * Returns the filter primitive area in user coordinate system.
+ */
+ Geom::Rect filter_primitive_area(FilterUnits const &units);
+
+ /**
+ *Indicate whether the filter primitive can handle the given affine.
+ *
+ * Results of some filter primitives depend on the coordinate system used when rendering.
+ * A gaussian blur with equal x and y deviation will remain unchanged by rotations.
+ * Per-pixel filters like color matrix and blend will not change regardless of
+ * the transformation.
+ *
+ * When any filter returns false, filter rendering is performed on an intermediate surface
+ * with edges parallel to the axes of the user coordinate system. This means
+ * the matrices from FilterUnits will contain at most a (possibly non-uniform) scale
+ * and a translation. When all primitives of the filter return true, the rendering is
+ * performed in display coordinate space and no intermediate surface is used.
+ */
+ virtual bool can_handle_affine(Geom::Affine const &) { return false; }
+
+ /**
+ * Sets style for access to properties used by filter primitives.
+ */
+ void setStyle(SPStyle *style);
+
+ // Useful for debugging
+ virtual Glib::ustring name() { return Glib::ustring("No name"); }
+
+protected:
+ int _input;
+ int _output;
+
+ /* Filter primitive subregion */
+ SVGLength _subregion_x;
+ SVGLength _subregion_y;
+ SVGLength _subregion_width;
+ SVGLength _subregion_height;
+
+ SPStyle *_style;
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* SEEN_NR_FILTER_PRIMITIVE_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/display/nr-filter-skeleton.cpp b/src/display/nr-filter-skeleton.cpp
new file mode 100644
index 0000000..072abc1
--- /dev/null
+++ b/src/display/nr-filter-skeleton.cpp
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Filter primitive renderer skeleton class
+ * You can create your new filter primitive renderer by replacing
+ * all occurrences of Skeleton, skeleton and SKELETON with your filter
+ * type, like gaussian or blend in respective case.
+ *
+ * This can be accomplished with the following sed command:
+ * sed -e "s/Skeleton/Name/g" -e "s/skeleton/name/" -e "s/SKELETON/NAME/"
+ * nr-filter-skeleton.cpp >nr-filter-name.cpp
+ *
+ * (on one line, replace occurrences of 'name' with your filter name)
+ *
+ * Remember to convert the .h file too. The sed command is same for both
+ * files.
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-skeleton.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterSkeleton::FilterSkeleton()
+{}
+
+FilterPrimitive * FilterSkeleton::create() {
+ return new FilterSkeleton();
+}
+
+FilterSkeleton::~FilterSkeleton()
+{}
+
+void FilterSkeleton::render_cairo(FilterSlot &slot) {
+ cairo_surface_t *in = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_identical(in);
+
+// cairo_t *ct = cairo_create(out);
+
+// cairo_set_source_surface(ct, in, offset[X], offset[Y]);
+// cairo_paint(ct);
+// cairo_destroy(ct);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-skeleton.h b/src/display/nr-filter-skeleton.h
new file mode 100644
index 0000000..83f1807
--- /dev/null
+++ b/src/display/nr-filter-skeleton.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_SKELETON_H
+#define SEEN_NR_FILTER_SKELETON_H
+
+/*
+ * Filter primitive renderer skeleton class
+ * You can create your new filter primitive renderer by replacing
+ * all occurrences of Skeleton, skeleton and SKELETON with your filter
+ * type, like gaussian or blend in respective case.
+ *
+ * This can be accomplished with the following sed command:
+ * sed -e "s/Skeleton/Name/g" -e "s/skeleton/name/" -e "s/SKELETON/NAME/"
+ * nr-filter-skeleton.h >nr-filter-name.h
+ *
+ * (on one line, replace occurrences of 'name' with your filter name)
+ *
+ * Remember to convert the .cpp file too. The sed command is same for both
+ * files.
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSkeleton : public FilterPrimitive {
+public:
+ FilterSkeleton();
+ static FilterPrimitive *create();
+ virtual ~FilterSkeleton();
+
+ virtual void render_cairo(FilterSlot &slot);
+
+private:
+
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_SKELETON_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/display/nr-filter-slot.cpp b/src/display/nr-filter-slot.cpp
new file mode 100644
index 0000000..4d9dc23
--- /dev/null
+++ b/src/display/nr-filter-slot.cpp
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A container class for filter slots. Allows for simple getting and
+ * setting images in filter slots without having to bother with
+ * table indexes and such.
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006,2007 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cassert>
+#include <cstring>
+
+#include <2geom/transforms.h>
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/nr-filter-types.h"
+#include "display/nr-filter-gaussian.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterSlot::FilterSlot(DrawingItem *item, DrawingContext *bgdc,
+ DrawingContext &graphic, FilterUnits const &u)
+ : _item(item)
+ , _source_graphic(graphic.rawTarget())
+ , _background_ct(bgdc ? bgdc->raw() : nullptr)
+ , _source_graphic_area(graphic.targetLogicalBounds().roundOutwards()) // fixme
+ , _background_area(bgdc ? bgdc->targetLogicalBounds().roundOutwards() : Geom::IntRect()) // fixme
+ , _units(u)
+ , _last_out(NR_FILTER_SOURCEGRAPHIC)
+ , filterquality(FILTER_QUALITY_BEST)
+ , blurquality(BLUR_QUALITY_BEST)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ // compute slot bbox
+ Geom::Affine trans = _units.get_matrix_display2pb();
+ Geom::Rect bbox_trans = graphic.targetLogicalBounds() * trans;
+ Geom::Point min = bbox_trans.min();
+ _slot_x = min[X];
+ _slot_y = min[Y];
+
+ if (trans.isTranslation()) {
+ _slot_w = _source_graphic_area.width();
+ _slot_h = _source_graphic_area.height();
+ } else {
+ _slot_w = ceil(bbox_trans.width());
+ _slot_h = ceil(bbox_trans.height());
+ }
+}
+
+FilterSlot::~FilterSlot()
+{
+ for (auto & _slot : _slots) {
+ cairo_surface_destroy(_slot.second);
+ }
+}
+
+cairo_surface_t *FilterSlot::getcairo(int slot_nr)
+{
+ if (slot_nr == NR_FILTER_SLOT_NOT_SET)
+ slot_nr = _last_out;
+
+ SlotMap::iterator s = _slots.find(slot_nr);
+
+ /* If we didn't have the specified image, but we could create it
+ * from the other information we have, let's do that */
+ if (s == _slots.end()
+ && (slot_nr == NR_FILTER_SOURCEGRAPHIC
+ || slot_nr == NR_FILTER_SOURCEALPHA
+ || slot_nr == NR_FILTER_BACKGROUNDIMAGE
+ || slot_nr == NR_FILTER_BACKGROUNDALPHA
+ || slot_nr == NR_FILTER_FILLPAINT
+ || slot_nr == NR_FILTER_STROKEPAINT))
+ {
+ switch (slot_nr) {
+ case NR_FILTER_SOURCEGRAPHIC: {
+ cairo_surface_t *tr = _get_transformed_source_graphic();
+ // Assume all source graphics are sRGB
+ set_cairo_surface_ci( tr, SP_CSS_COLOR_INTERPOLATION_SRGB );
+ _set_internal(NR_FILTER_SOURCEGRAPHIC, tr);
+ cairo_surface_destroy(tr);
+ } break;
+ case NR_FILTER_BACKGROUNDIMAGE: {
+ cairo_surface_t *bg = _get_transformed_background();
+ // Assume all backgrounds are sRGB
+ set_cairo_surface_ci( bg, SP_CSS_COLOR_INTERPOLATION_SRGB );
+ _set_internal(NR_FILTER_BACKGROUNDIMAGE, bg);
+ cairo_surface_destroy(bg);
+ } break;
+ case NR_FILTER_SOURCEALPHA: {
+ cairo_surface_t *src = getcairo(NR_FILTER_SOURCEGRAPHIC);
+ cairo_surface_t *alpha = ink_cairo_extract_alpha(src);
+ _set_internal(NR_FILTER_SOURCEALPHA, alpha);
+ cairo_surface_destroy(alpha);
+ } break;
+ case NR_FILTER_BACKGROUNDALPHA: {
+ cairo_surface_t *src = getcairo(NR_FILTER_BACKGROUNDIMAGE);
+ cairo_surface_t *ba = ink_cairo_extract_alpha(src);
+ _set_internal(NR_FILTER_BACKGROUNDALPHA, ba);
+ cairo_surface_destroy(ba);
+ } break;
+ case NR_FILTER_FILLPAINT: //TODO
+ case NR_FILTER_STROKEPAINT: //TODO
+ default:
+ break;
+ }
+ s = _slots.find(slot_nr);
+ }
+
+ if (s == _slots.end()) {
+ // create empty surface
+ cairo_surface_t *empty = cairo_surface_create_similar(
+ _source_graphic, cairo_surface_get_content(_source_graphic),
+ _slot_w, _slot_h);
+ _set_internal(slot_nr, empty);
+ cairo_surface_destroy(empty);
+ s = _slots.find(slot_nr);
+ }
+ return s->second;
+}
+
+cairo_surface_t *FilterSlot::_get_transformed_source_graphic()
+{
+ Geom::Affine trans = _units.get_matrix_display2pb();
+
+ if (trans.isTranslation()) {
+ cairo_surface_reference(_source_graphic);
+ return _source_graphic;
+ }
+
+ cairo_surface_t *tsg = cairo_surface_create_similar(
+ _source_graphic, cairo_surface_get_content(_source_graphic),
+ _slot_w, _slot_h);
+ cairo_t *tsg_ct = cairo_create(tsg);
+
+ cairo_translate(tsg_ct, -_slot_x, -_slot_y);
+ ink_cairo_transform(tsg_ct, trans);
+ cairo_translate(tsg_ct, _source_graphic_area.left(), _source_graphic_area.top());
+ cairo_set_source_surface(tsg_ct, _source_graphic, 0, 0);
+ cairo_set_operator(tsg_ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(tsg_ct);
+ cairo_destroy(tsg_ct);
+
+ return tsg;
+}
+
+cairo_surface_t *FilterSlot::_get_transformed_background()
+{
+ Geom::Affine trans = _units.get_matrix_display2pb();
+
+ cairo_surface_t *tbg;
+
+ if (_background_ct) {
+ cairo_surface_t *bg = cairo_get_group_target(_background_ct);
+ tbg = cairo_surface_create_similar(
+ bg, cairo_surface_get_content(bg),
+ _slot_w, _slot_h);
+ cairo_t *tbg_ct = cairo_create(tbg);
+
+ cairo_translate(tbg_ct, -_slot_x, -_slot_y);
+ ink_cairo_transform(tbg_ct, trans);
+ cairo_translate(tbg_ct, _background_area.left(), _background_area.top());
+ cairo_set_source_surface(tbg_ct, bg, 0, 0);
+ cairo_set_operator(tbg_ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(tbg_ct);
+ cairo_destroy(tbg_ct);
+ } else {
+ tbg = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, _slot_w, _slot_h);
+ }
+
+ return tbg;
+}
+
+cairo_surface_t *FilterSlot::get_result(int res)
+{
+ cairo_surface_t *result = getcairo(res);
+
+ Geom::Affine trans = _units.get_matrix_pb2display();
+ if (trans.isIdentity()) {
+ cairo_surface_reference(result);
+ return result;
+ }
+
+ cairo_surface_t *r = cairo_surface_create_similar(_source_graphic,
+ cairo_surface_get_content(_source_graphic),
+ _source_graphic_area.width(),
+ _source_graphic_area.height());
+ copy_cairo_surface_ci( result, r );
+ cairo_t *r_ct = cairo_create(r);
+
+ cairo_translate(r_ct, -_source_graphic_area.left(), -_source_graphic_area.top());
+ ink_cairo_transform(r_ct, trans);
+ cairo_translate(r_ct, _slot_x, _slot_y);
+ cairo_set_source_surface(r_ct, result, 0, 0);
+ cairo_set_operator(r_ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(r_ct);
+ cairo_destroy(r_ct);
+
+ return r;
+}
+
+void FilterSlot::_set_internal(int slot_nr, cairo_surface_t *surface)
+{
+ // destroy after referencing
+ // this way assigning a surface to a slot it already occupies will not cause errors
+ cairo_surface_reference(surface);
+
+ SlotMap::iterator s = _slots.find(slot_nr);
+ if (s != _slots.end()) {
+ cairo_surface_destroy(s->second);
+ }
+
+ _slots[slot_nr] = surface;
+}
+
+void FilterSlot::set(int slot_nr, cairo_surface_t *surface)
+{
+ g_return_if_fail(surface != nullptr);
+
+ if (slot_nr == NR_FILTER_SLOT_NOT_SET)
+ slot_nr = NR_FILTER_UNNAMED_SLOT;
+
+ _set_internal(slot_nr, surface);
+ _last_out = slot_nr;
+}
+
+void FilterSlot::set_primitive_area(int slot_nr, Geom::Rect &area)
+{
+ if (slot_nr == NR_FILTER_SLOT_NOT_SET)
+ slot_nr = NR_FILTER_UNNAMED_SLOT;
+
+ _primitiveAreas[slot_nr] = area;
+}
+
+Geom::Rect FilterSlot::get_primitive_area(int slot_nr)
+{
+ if (slot_nr == NR_FILTER_SLOT_NOT_SET)
+ slot_nr = _last_out;
+
+ PrimitiveAreaMap::iterator s = _primitiveAreas.find(slot_nr);
+
+ if (s == _primitiveAreas.end()) {
+ return *(_units.get_filter_area());
+ }
+ return s->second;
+}
+
+int FilterSlot::get_slot_count()
+{
+ return _slots.size();
+}
+
+void FilterSlot::set_quality(FilterQuality const q) {
+ filterquality = q;
+}
+
+void FilterSlot::set_blurquality(int const q) {
+ blurquality = q;
+}
+
+int FilterSlot::get_blurquality() {
+ return blurquality;
+}
+
+void FilterSlot::set_device_scale(int const s) {
+ device_scale = s;
+}
+
+int FilterSlot::get_device_scale() {
+ return device_scale;
+}
+
+Geom::Rect FilterSlot::get_slot_area() const {
+ Geom::Point p(_slot_x, _slot_y);
+ Geom::Point dim(_slot_w, _slot_h);
+ Geom::Rect r(p, p+dim);
+ return r;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-slot.h b/src/display/nr-filter-slot.h
new file mode 100644
index 0000000..1a695c4
--- /dev/null
+++ b/src/display/nr-filter-slot.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_SLOT_H
+#define SEEN_NR_FILTER_SLOT_H
+
+/*
+ * A container class for filter slots. Allows for simple getting and
+ * setting images in filter slots without having to bother with
+ * table indexes and such.
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006,2007 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <map>
+#include "display/nr-filter-types.h"
+#include "display/nr-filter-units.h"
+
+extern "C" {
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+}
+
+namespace Inkscape {
+class DrawingContext;
+class DrawingItem;
+
+namespace Filters {
+
+class FilterSlot {
+public:
+ /** Creates a new FilterSlot object. */
+ FilterSlot(DrawingItem *item, DrawingContext *bgdc,
+ DrawingContext &graphic, FilterUnits const &u);
+ /** Destroys the FilterSlot object and all its contents */
+ virtual ~FilterSlot();
+
+ /** Returns the pixblock in specified slot.
+ * Parameter 'slot' may be either an positive integer or one of
+ * pre-defined filter slot types: NR_FILTER_SLOT_NOT_SET,
+ * NR_FILTER_SOURCEGRAPHIC, NR_FILTER_SOURCEALPHA,
+ * NR_FILTER_BACKGROUNDIMAGE, NR_FILTER_BACKGROUNDALPHA,
+ * NR_FILTER_FILLPAINT, NR_FILTER_SOURCEPAINT.
+ */
+ cairo_surface_t *getcairo(int slot);
+
+ /** Sets or re-sets the pixblock associated with given slot.
+ * If there was a pixblock already assigned with this slot,
+ * that pixblock is destroyed.
+ */
+ void set(int slot, cairo_surface_t *s);
+
+ cairo_surface_t *get_result(int slot_nr);
+
+ void set_primitive_area(int slot, Geom::Rect &area);
+ Geom::Rect get_primitive_area(int slot);
+
+ /** Returns the number of slots in use. */
+ int get_slot_count();
+
+ /** Sets the unit system to be used for the internal images. */
+ //void set_units(FilterUnits const &units);
+
+ /** Sets the filtering quality. Affects used interpolation methods */
+ void set_quality(FilterQuality const q);
+
+ /** Sets the gaussian filtering quality. Affects used interpolation methods */
+ void set_blurquality(int const q);
+
+ /** Gets the gaussian filtering quality. Affects used interpolation methods */
+ int get_blurquality();
+
+ /** Sets the device scale; for high DPI monitors. */
+ void set_device_scale(int const s);
+
+ /** Gets the device scale; for high DPI monitors. */
+ int get_device_scale();
+
+ FilterUnits const &get_units() const { return _units; }
+ Geom::Rect get_slot_area() const;
+
+private:
+ typedef std::map<int, cairo_surface_t *> SlotMap;
+ SlotMap _slots;
+
+ // We need to keep track of the primitive area as this is needed in feTile
+ typedef std::map<int, Geom::Rect> PrimitiveAreaMap;
+ PrimitiveAreaMap _primitiveAreas;
+
+ DrawingItem *_item;
+
+ //Geom::Rect _source_bbox; ///< bounding box of source graphic surface
+ //Geom::Rect _intermediate_bbox; ///< bounding box of intermediate surfaces
+
+ int _slot_w, _slot_h;
+ double _slot_x, _slot_y;
+ cairo_surface_t *_source_graphic;
+ cairo_t *_background_ct;
+ Geom::IntRect _source_graphic_area;
+ Geom::IntRect _background_area; ///< needed to extract background
+ FilterUnits const &_units;
+ int _last_out;
+ FilterQuality filterquality;
+ int blurquality;
+ int device_scale;
+
+ cairo_surface_t *_get_transformed_source_graphic();
+ cairo_surface_t *_get_transformed_background();
+ cairo_surface_t *_get_fill_paint();
+ cairo_surface_t *_get_stroke_paint();
+
+ void _set_internal(int slot, cairo_surface_t *s);
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif // __NR_FILTER_SLOT_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/display/nr-filter-specularlighting.cpp b/src/display/nr-filter-specularlighting.cpp
new file mode 100644
index 0000000..330f19c
--- /dev/null
+++ b/src/display/nr-filter-specularlighting.cpp
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feSpecularLighting renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * 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 <glib.h>
+#include <cmath>
+
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-3dutils.h"
+#include "display/nr-filter-specularlighting.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+#include "display/nr-filter-utils.h"
+#include "display/nr-light.h"
+#include "svg/svg-icc-color.h"
+#include "svg/svg-color.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterSpecularLighting::FilterSpecularLighting()
+{
+ light_type = NO_LIGHT;
+ specularConstant = 1;
+ specularExponent = 1;
+ surfaceScale = 1;
+ lighting_color = 0xffffffff;
+}
+
+FilterPrimitive * FilterSpecularLighting::create() {
+ return new FilterSpecularLighting();
+}
+
+FilterSpecularLighting::~FilterSpecularLighting()
+= default;
+
+struct SpecularLight : public SurfaceSynth {
+ SpecularLight(cairo_surface_t *bumpmap, double scale, double specular_constant,
+ double specular_exponent)
+ : SurfaceSynth(bumpmap)
+ , _scale(scale)
+ , _ks(specular_constant)
+ , _exp(specular_exponent)
+ {}
+protected:
+ guint32 specularLighting(int x, int y, NR::Fvector const &halfway, NR::Fvector const &light_components) {
+ NR::Fvector normal = surfaceNormalAt(x, y, _scale);
+ double sp = NR::scalar_product(normal, halfway);
+ double k = sp <= 0.0 ? 0.0 : _ks * pow(sp, _exp);
+
+ guint32 r = CLAMP_D_TO_U8(k * light_components[LIGHT_RED]);
+ guint32 g = CLAMP_D_TO_U8(k * light_components[LIGHT_GREEN]);
+ guint32 b = CLAMP_D_TO_U8(k * light_components[LIGHT_BLUE]);
+ guint32 a = std::max(std::max(r, g), b);
+
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+
+ ASSEMBLE_ARGB32(pxout, a,r,g,b)
+ return pxout;
+ }
+ double _scale, _ks, _exp;
+};
+
+struct SpecularDistantLight : public SpecularLight {
+ SpecularDistantLight(cairo_surface_t *bumpmap, SPFeDistantLight *light, guint32 color,
+ double scale, double specular_constant, double specular_exponent)
+ : SpecularLight(bumpmap, scale, specular_constant, specular_exponent)
+ {
+ DistantLight dl(light, color);
+ NR::Fvector lv;
+ dl.light_vector(lv);
+ dl.light_components(_light_components);
+ NR::normalized_sum(_halfway, lv, NR::EYE_VECTOR);
+ }
+ guint32 operator()(int x, int y) {
+ return specularLighting(x, y, _halfway, _light_components);
+ }
+private:
+ NR::Fvector _halfway, _light_components;
+};
+
+struct SpecularPointLight : public SpecularLight {
+ SpecularPointLight(cairo_surface_t *bumpmap, SPFePointLight *light, guint32 color,
+ Geom::Affine const &trans, double scale, double specular_constant,
+ double specular_exponent, double x0, double y0, int device_scale)
+ : SpecularLight(bumpmap, scale, specular_constant, specular_exponent)
+ , _light(light, color, trans, device_scale)
+ , _x0(x0)
+ , _y0(y0)
+ {
+ _light.light_components(_light_components);
+ }
+
+ guint32 operator()(int x, int y) {
+ NR::Fvector light, halfway;
+ _light.light_vector(light, _x0 + x, _y0 + y, _scale * alphaAt(x, y)/255.0);
+ NR::normalized_sum(halfway, light, NR::EYE_VECTOR);
+ return specularLighting(x, y, halfway, _light_components);
+ }
+private:
+ PointLight _light;
+ NR::Fvector _light_components;
+ double _x0, _y0;
+};
+
+struct SpecularSpotLight : public SpecularLight {
+ SpecularSpotLight(cairo_surface_t *bumpmap, SPFeSpotLight *light, guint32 color,
+ Geom::Affine const &trans, double scale, double specular_constant,
+ double specular_exponent, double x0, double y0, int device_scale)
+ : SpecularLight(bumpmap, scale, specular_constant, specular_exponent)
+ , _light(light, color, trans, device_scale)
+ , _x0(x0)
+ , _y0(y0)
+ {}
+
+ guint32 operator()(int x, int y) {
+ NR::Fvector light, halfway, light_components;
+ _light.light_vector(light, _x0 + x, _y0 + y, _scale * alphaAt(x, y)/255.0);
+ _light.light_components(light_components, light);
+ NR::normalized_sum(halfway, light, NR::EYE_VECTOR);
+ return specularLighting(x, y, halfway, light_components);
+ }
+private:
+ SpotLight _light;
+ double _x0, _y0;
+};
+
+void FilterSpecularLighting::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
+
+ double r = SP_RGBA32_R_F(lighting_color);
+ double g = SP_RGBA32_G_F(lighting_color);
+ double b = SP_RGBA32_B_F(lighting_color);
+
+#if defined(HAVE_LIBLCMS2)
+
+ if (icc) {
+ guchar ru, gu, bu;
+ icc_color_to_sRGB(icc, &ru, &gu, &bu);
+ r = SP_COLOR_U_TO_F(ru);
+ g = SP_COLOR_U_TO_F(gu);
+ b = SP_COLOR_U_TO_F(bu);
+ }
+#endif
+
+ // Only alpha channel of input is used, no need to check input color_interpolation_filter value.
+ SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO;
+ if( _style ) {
+ ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed;
+
+ // Lighting color is always defined in terms of sRGB, preconvert to linearRGB
+ // if color_interpolation_filters set to linearRGB (for efficiency assuming
+ // next filter primitive has same value of cif).
+ if( ci_fp == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
+ r = srgb_to_linear( r );
+ g = srgb_to_linear( g );
+ b = srgb_to_linear( b );
+ }
+ }
+ set_cairo_surface_ci(out, ci_fp );
+ guint32 color = SP_RGBA32_F_COMPOSE( r, g, b, 1.0 );
+
+ int device_scale = slot.get_device_scale();
+
+ // trans has inverse y... so we can't just scale by device_scale! We must instead explicitly
+ // scale the point and spot light coordinates (as well as "scale").
+
+ Geom::Affine trans = slot.get_units().get_matrix_primitiveunits2pb();
+
+ Geom::Point p = slot.get_slot_area().min();
+ double x0 = p[Geom::X];
+ double y0 = p[Geom::Y];
+ double scale = surfaceScale * trans.descrim() * device_scale;
+ double ks = specularConstant;
+ double se = specularExponent;
+
+ switch (light_type) {
+ case DISTANT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ SpecularDistantLight(input, light.distant, color, scale, ks, se));
+ break;
+ case POINT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ SpecularPointLight(input, light.point, color, trans, scale, ks, se, x0, y0, device_scale));
+ break;
+ case SPOT_LIGHT:
+ ink_cairo_surface_synthesize(out,
+ SpecularSpotLight(input, light.spot, color, trans, scale, ks, se, x0, y0, device_scale));
+ break;
+ default: {
+ cairo_t *ct = cairo_create(out);
+ cairo_set_source_rgba(ct, 0,0,0,1);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ } break;
+ }
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+void FilterSpecularLighting::set_icc(SVGICCColor *icc_color) {
+ icc = icc_color;
+}
+
+void FilterSpecularLighting::area_enlarge(Geom::IntRect &area, Geom::Affine const & /*trans*/)
+{
+ // TODO: support kernelUnitLength
+ area.expandBy(1);
+}
+
+double FilterSpecularLighting::complexity(Geom::Affine const &)
+{
+ return 9.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-specularlighting.h b/src/display/nr-filter-specularlighting.h
new file mode 100644
index 0000000..86fd8b5
--- /dev/null
+++ b/src/display/nr-filter-specularlighting.h
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_SPECULARLIGHTING_H
+#define SEEN_NR_FILTER_SPECULARLIGHTING_H
+
+/*
+ * feSpecularLighting renderer
+ *
+ * Authors:
+ * Niko Kiirala <niko@kiirala.com>
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-light-types.h"
+#include "display/nr-filter-primitive.h"
+
+class SPFeDistantLight;
+class SPFePointLight;
+class SPFeSpotLight;
+struct SVGICCColor;
+typedef unsigned int guint32;
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+class FilterSpecularLighting : public FilterPrimitive {
+public:
+ FilterSpecularLighting();
+ static FilterPrimitive *create();
+ ~FilterSpecularLighting() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ virtual void set_icc(SVGICCColor *icc_color);
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ union {
+ SPFeDistantLight *distant;
+ SPFePointLight *point;
+ SPFeSpotLight *spot;
+ } light;
+ LightType light_type;
+ double surfaceScale;
+ double specularConstant;
+ double specularExponent;
+ guint32 lighting_color;
+
+ Glib::ustring name() override { return Glib::ustring("Specular Lighting"); }
+
+private:
+ SVGICCColor *icc;
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_SPECULARLIGHTING_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/display/nr-filter-tile.cpp b/src/display/nr-filter-tile.cpp
new file mode 100644
index 0000000..4605dde
--- /dev/null
+++ b/src/display/nr-filter-tile.cpp
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feTile filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+
+#include "display/cairo-utils.h"
+#include "display/nr-filter-tile.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+FilterTile::FilterTile()
+= default;
+
+FilterPrimitive * FilterTile::create() {
+ return new FilterTile();
+}
+
+FilterTile::~FilterTile()
+= default;
+
+void FilterTile::render_cairo(FilterSlot &slot)
+{
+ // This input source contains only the "rendering" tile.
+ cairo_surface_t *in = slot.getcairo(_input);
+
+ // For debugging
+ // static int i = 0;
+ // ++i;
+ // std::stringstream filename;
+ // filename << "dump." << i << ".png";
+ // cairo_surface_write_to_png( in, filename.str().c_str() );
+
+ // This is the feTile source area as determined by the input primitive area (see SVG spec).
+ Geom::Rect tile_area = slot.get_primitive_area(_input);
+
+ if( tile_area.width() == 0.0 || tile_area.height() == 0.0 ) {
+
+ slot.set(_output, in);
+ std::cerr << "FileTile::render_cairo: tile has zero width or height" << std::endl;
+
+ } else {
+
+ cairo_surface_t *out = ink_cairo_surface_create_identical(in);
+ // color_interpolation_filters for out same as in.
+ copy_cairo_surface_ci(in, out);
+ cairo_t *ct = cairo_create(out);
+
+ // The rectangle of the "rendering" tile.
+ Geom::Rect sa = slot.get_slot_area();
+
+ Geom::Affine trans = slot.get_units().get_matrix_user2pb();
+
+ // Create feTile tile ----------------
+
+ // Get tile area in pixbuf units (tile transformed).
+ Geom::Rect tt = tile_area * trans;
+
+ // Shift between "rendering" tile and feTile tile
+ Geom::Point shift = sa.min() - tt.min();
+
+ // Create feTile tile surface
+ cairo_surface_t *tile = cairo_surface_create_similar(in, cairo_surface_get_content(in),
+ tt.width(), tt.height());
+ cairo_t *ct_tile = cairo_create(tile);
+ cairo_set_source_surface(ct_tile, in, shift[Geom::X], shift[Geom::Y]);
+ cairo_paint(ct_tile);
+
+ // Paint tiles ------------------
+
+ // For debugging
+ // std::stringstream filename;
+ // filename << "tile." << i << ".png";
+ // cairo_surface_write_to_png( tile, filename.str().c_str() );
+
+ // Determine number of feTile rows and columns
+ Geom::Rect pr = filter_primitive_area( slot.get_units() );
+ int tile_cols = ceil( pr.width() / tile_area.width() );
+ int tile_rows = ceil( pr.height() / tile_area.height() );
+
+ // Do tiling (TO DO: restrict to slot area.)
+ for( int col=0; col < tile_cols; ++col ) {
+ for( int row=0; row < tile_rows; ++row ) {
+
+ Geom::Point offset( col*tile_area.width(), row*tile_area.height() );
+ offset *= trans;
+ offset[Geom::X] -= trans[4];
+ offset[Geom::Y] -= trans[5];
+
+ cairo_set_source_surface(ct, tile, offset[Geom::X], offset[Geom::Y]);
+ cairo_paint(ct);
+ }
+ }
+ slot.set(_output, out);
+
+ // Clean up
+ cairo_destroy(ct);
+ cairo_surface_destroy(out);
+ cairo_destroy(ct_tile);
+ cairo_surface_destroy(tile);
+ }
+}
+
+void FilterTile::area_enlarge(Geom::IntRect &area, Geom::Affine const &trans)
+{
+ // Set to very large rectangle so we get tile source. It will be clipped later.
+
+ // Note, setting to infinite using Geom::IntRect::infinite() causes overflow/underflow problems.
+ Geom::IntCoord max = std::numeric_limits<Geom::IntCoord>::max()/4;
+ area = Geom::IntRect(-max,-max,max,max);
+}
+
+double FilterTile::complexity(Geom::Affine const &)
+{
+ return 1.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-tile.h b/src/display/nr-filter-tile.h
new file mode 100644
index 0000000..867d129
--- /dev/null
+++ b/src/display/nr-filter-tile.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_TILE_H
+#define SEEN_NR_FILTER_TILE_H
+
+/*
+ * feTile filter primitive renderer
+ *
+ * Authors:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-filter-primitive.h"
+
+namespace Inkscape {
+namespace Filters {
+
+class FilterSlot;
+
+class FilterTile : public FilterPrimitive {
+public:
+ FilterTile();
+ static FilterPrimitive *create();
+ ~FilterTile() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) override;
+ double complexity(Geom::Affine const &ctm) override;
+
+ Glib::ustring name() override { return Glib::ustring("Tile"); }
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_TILE_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/display/nr-filter-turbulence.cpp b/src/display/nr-filter-turbulence.cpp
new file mode 100644
index 0000000..91a71ab
--- /dev/null
+++ b/src/display/nr-filter-turbulence.cpp
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * feTurbulence filter primitive renderer
+ *
+ * Authors:
+ * World Wide Web Consortium <http://www.w3.org/>
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ *
+ * This file has a considerable amount of code adapted from
+ * the W3C SVG filter specs, available at:
+ * http://www.w3.org/TR/SVG11/filters.html#feTurbulence
+ *
+ * W3C original code is licensed under the terms of
+ * the (GPL compatible) W3CÂź SOFTWARE NOTICE AND LICENSE:
+ * http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+ *
+ * Copyright (C) 2007 authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/cairo-templates.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter.h"
+#include "display/nr-filter-turbulence.h"
+#include "display/nr-filter-units.h"
+#include "display/nr-filter-utils.h"
+#include <cmath>
+
+namespace Inkscape {
+namespace Filters{
+
+class TurbulenceGenerator {
+public:
+ TurbulenceGenerator() :
+ _tile(),
+ _baseFreq(),
+ _latticeSelector(),
+ _gradient(),
+ _seed(0),
+ _octaves(0),
+ _stitchTiles(false),
+ _wrapx(0),
+ _wrapy(0),
+ _wrapw(0),
+ _wraph(0),
+ _inited(false),
+ _fractalnoise(false)
+ {}
+
+ void init(long seed, Geom::Rect const &tile, Geom::Point const &freq, bool stitch,
+ bool fractalnoise, int octaves)
+ {
+ // setup random number generator
+ _setupSeed(seed);
+
+ // set values
+ _tile = tile;
+ _baseFreq = freq;
+ _stitchTiles = stitch;
+ _fractalnoise = fractalnoise;
+ _octaves = octaves;
+
+ int i;
+ for (int k = 0; k < 4; ++k) {
+ for (i = 0; i < BSize; ++i) {
+ _latticeSelector[i] = i;
+
+ do {
+ _gradient[i][k][0] = static_cast<double>(_random() % (BSize*2) - BSize) / BSize;
+ _gradient[i][k][1] = static_cast<double>(_random() % (BSize*2) - BSize) / BSize;
+ } while(_gradient[i][k][0] == 0 && _gradient[i][k][1] == 0);
+
+ // normalize gradient
+ double s = hypot(_gradient[i][k][0], _gradient[i][k][1]);
+ _gradient[i][k][0] /= s;
+ _gradient[i][k][1] /= s;
+ }
+ }
+ while (--i) {
+ // shuffle lattice selectors
+ int j = _random() % BSize;
+ std::swap(_latticeSelector[i], _latticeSelector[j]);
+ }
+
+ // fill out the remaining part of the gradient
+ for (i = 0; i < BSize + 2; ++i)
+ {
+ _latticeSelector[BSize + i] = _latticeSelector[i];
+
+ for(int k = 0; k < 4; ++k) {
+ _gradient[BSize + i][k][0] = _gradient[i][k][0];
+ _gradient[BSize + i][k][1] = _gradient[i][k][1];
+ }
+ }
+
+ // When stitching tiled turbulence, the frequencies must be adjusted
+ // so that the tile borders will be continuous.
+ if (_stitchTiles) {
+ if (_baseFreq[Geom::X] != 0.0)
+ {
+ double freq = _baseFreq[Geom::X];
+ double lo = floor(_tile.width() * freq) / _tile.width();
+ double hi = ceil(_tile.width() * freq) / _tile.width();
+ _baseFreq[Geom::X] = freq / lo < hi / freq ? lo : hi;
+ }
+ if (_baseFreq[Geom::Y] != 0.0)
+ {
+ double freq = _baseFreq[Geom::Y];
+ double lo = floor(_tile.height() * freq) / _tile.height();
+ double hi = ceil(_tile.height() * freq) / _tile.height();
+ _baseFreq[Geom::Y] = freq / lo < hi / freq ? lo : hi;
+ }
+
+ _wrapw = _tile.width() * _baseFreq[Geom::X] + 0.5;
+ _wraph = _tile.height() * _baseFreq[Geom::Y] + 0.5;
+ _wrapx = _tile.left() * _baseFreq[Geom::X] + PerlinOffset + _wrapw;
+ _wrapy = _tile.top() * _baseFreq[Geom::Y] + PerlinOffset + _wraph;
+ }
+ _inited = true;
+ }
+
+ G_GNUC_PURE
+ guint32 turbulencePixel(Geom::Point const &p) const {
+ int wrapx = _wrapx, wrapy = _wrapy, wrapw = _wrapw, wraph = _wraph;
+
+ double pixel[4];
+ double x = p[Geom::X] * _baseFreq[Geom::X];
+ double y = p[Geom::Y] * _baseFreq[Geom::Y];
+ double ratio = 1.0;
+
+ for (double & k : pixel)
+ k = 0.0;
+
+ for(int octave = 0; octave < _octaves; ++octave)
+ {
+ double tx = x + PerlinOffset;
+ double bx = floor(tx);
+ double rx0 = tx - bx, rx1 = rx0 - 1.0;
+ int bx0 = bx, bx1 = bx0 + 1;
+
+ double ty = y + PerlinOffset;
+ double by = floor(ty);
+ double ry0 = ty - by, ry1 = ry0 - 1.0;
+ int by0 = by, by1 = by0 + 1;
+
+ if (_stitchTiles) {
+ if (bx0 >= wrapx) bx0 -= wrapw;
+ if (bx1 >= wrapx) bx1 -= wrapw;
+ if (by0 >= wrapy) by0 -= wraph;
+ if (by1 >= wrapy) by1 -= wraph;
+ }
+ bx0 &= BMask;
+ bx1 &= BMask;
+ by0 &= BMask;
+ by1 &= BMask;
+
+ int i = _latticeSelector[bx0];
+ int j = _latticeSelector[bx1];
+ int b00 = _latticeSelector[i + by0];
+ int b01 = _latticeSelector[i + by1];
+ int b10 = _latticeSelector[j + by0];
+ int b11 = _latticeSelector[j + by1];
+
+ double sx = _scurve(rx0);
+ double sy = _scurve(ry0);
+
+ double result[4];
+ // channel numbering: R=0, G=1, B=2, A=3
+ for (int k = 0; k < 4; ++k) {
+ double const *qxa = _gradient[b00][k];
+ double const *qxb = _gradient[b10][k];
+ double a = _lerp(sx, rx0 * qxa[0] + ry0 * qxa[1],
+ rx1 * qxb[0] + ry0 * qxb[1]);
+ double const *qya = _gradient[b01][k];
+ double const *qyb = _gradient[b11][k];
+ double b = _lerp(sx, rx0 * qya[0] + ry1 * qya[1],
+ rx1 * qyb[0] + ry1 * qyb[1]);
+ result[k] = _lerp(sy, a, b);
+ }
+
+ if (_fractalnoise) {
+ for (int k = 0; k < 4; ++k)
+ pixel[k] += result[k] / ratio;
+ } else {
+ for (int k = 0; k < 4; ++k)
+ pixel[k] += fabs(result[k]) / ratio;
+ }
+
+ x *= 2;
+ y *= 2;
+ ratio *= 2;
+
+ if(_stitchTiles)
+ {
+ // Update stitch values. Subtracting PerlinOffset before the multiplication and
+ // adding it afterward simplifies to subtracting it once.
+ wrapw *= 2;
+ wraph *= 2;
+ wrapx = wrapx*2 - PerlinOffset;
+ wrapy = wrapy*2 - PerlinOffset;
+ }
+ }
+
+ if (_fractalnoise) {
+ guint32 r = CLAMP_D_TO_U8((pixel[0]*255.0 + 255.0) / 2);
+ guint32 g = CLAMP_D_TO_U8((pixel[1]*255.0 + 255.0) / 2);
+ guint32 b = CLAMP_D_TO_U8((pixel[2]*255.0 + 255.0) / 2);
+ guint32 a = CLAMP_D_TO_U8((pixel[3]*255.0 + 255.0) / 2);
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+ ASSEMBLE_ARGB32(pxout, a,r,g,b);
+ return pxout;
+ } else {
+ guint32 r = CLAMP_D_TO_U8(pixel[0]*255.0);
+ guint32 g = CLAMP_D_TO_U8(pixel[1]*255.0);
+ guint32 b = CLAMP_D_TO_U8(pixel[2]*255.0);
+ guint32 a = CLAMP_D_TO_U8(pixel[3]*255.0);
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+ ASSEMBLE_ARGB32(pxout, a,r,g,b);
+ return pxout;
+ }
+ }
+
+ //G_GNUC_PURE
+ /*guint32 turbulencePixel(Geom::Point const &p) const {
+ if (!_fractalnoise) {
+ guint32 r = CLAMP_D_TO_U8(turbulence(0, p)*255.0);
+ guint32 g = CLAMP_D_TO_U8(turbulence(1, p)*255.0);
+ guint32 b = CLAMP_D_TO_U8(turbulence(2, p)*255.0);
+ guint32 a = CLAMP_D_TO_U8(turbulence(3, p)*255.0);
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+ ASSEMBLE_ARGB32(pxout, a,r,g,b);
+ return pxout;
+ } else {
+ guint32 r = CLAMP_D_TO_U8((turbulence(0, p)*255.0 + 255.0) / 2);
+ guint32 g = CLAMP_D_TO_U8((turbulence(1, p)*255.0 + 255.0) / 2);
+ guint32 b = CLAMP_D_TO_U8((turbulence(2, p)*255.0 + 255.0) / 2);
+ guint32 a = CLAMP_D_TO_U8((turbulence(3, p)*255.0 + 255.0) / 2);
+ r = premul_alpha(r, a);
+ g = premul_alpha(g, a);
+ b = premul_alpha(b, a);
+ ASSEMBLE_ARGB32(pxout, a,r,g,b);
+ return pxout;
+ }
+ }*/
+
+ bool ready() const { return _inited; }
+ void dirty() { _inited = false; }
+
+private:
+ void _setupSeed(long seed) {
+ _seed = seed;
+ if (_seed <= 0) _seed = -(_seed % (RAND_m - 1)) + 1;
+ if (_seed > RAND_m - 1) _seed = RAND_m - 1;
+ }
+ long _random() {
+ /* Produces results in the range [1, 2**31 - 2].
+ * Algorithm is: r = (a * r) mod m
+ * where a = 16807 and m = 2**31 - 1 = 2147483647
+ * See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
+ * To test: the algorithm should produce the result 1043618065
+ * as the 10,000th generated number if the original seed is 1. */
+ _seed = RAND_a * (_seed % RAND_q) - RAND_r * (_seed / RAND_q);
+ if (_seed <= 0) _seed += RAND_m;
+ return _seed;
+ }
+ static inline double _scurve(double t) {
+ return t * t * (3.0 - 2.0*t);
+ }
+ static inline double _lerp(double t, double a, double b) {
+ return a + t * (b-a);
+ }
+
+ // random number generator constants
+ static long const
+ RAND_m = 2147483647, // 2**31 - 1
+ RAND_a = 16807, // 7**5; primitive root of m
+ RAND_q = 127773, // m / a
+ RAND_r = 2836; // m % a
+
+ // other constants
+ static int const BSize = 0x100;
+ static int const BMask = 0xff;
+
+ static double constexpr PerlinOffset = 4096.0;
+
+ Geom::Rect _tile;
+ Geom::Point _baseFreq;
+ int _latticeSelector[2*BSize + 2];
+ double _gradient[2*BSize + 2][4][2];
+ long _seed;
+ int _octaves;
+ bool _stitchTiles;
+ int _wrapx;
+ int _wrapy;
+ int _wrapw;
+ int _wraph;
+ bool _inited;
+ bool _fractalnoise;
+};
+
+FilterTurbulence::FilterTurbulence()
+ : gen(new TurbulenceGenerator())
+ , XbaseFrequency(0)
+ , YbaseFrequency(0)
+ , numOctaves(1)
+ , seed(0)
+ , updated(false)
+ , fTileWidth(10) //guessed
+ , fTileHeight(10) //guessed
+ , fTileX(1) //guessed
+ , fTileY(1) //guessed
+{
+}
+
+FilterPrimitive * FilterTurbulence::create() {
+ return new FilterTurbulence();
+}
+
+FilterTurbulence::~FilterTurbulence()
+{
+ delete gen;
+}
+
+void FilterTurbulence::set_baseFrequency(int axis, double freq){
+ if (axis==0) XbaseFrequency=freq;
+ if (axis==1) YbaseFrequency=freq;
+ gen->dirty();
+}
+
+void FilterTurbulence::set_numOctaves(int num){
+ numOctaves = num;
+ gen->dirty();
+}
+
+void FilterTurbulence::set_seed(double s){
+ seed = s;
+ gen->dirty();
+}
+
+void FilterTurbulence::set_stitchTiles(bool st){
+ stitchTiles = st;
+ gen->dirty();
+}
+
+void FilterTurbulence::set_type(FilterTurbulenceType t){
+ type = t;
+ gen->dirty();
+}
+
+void FilterTurbulence::set_updated(bool /*u*/)
+{
+}
+
+struct Turbulence {
+ Turbulence(TurbulenceGenerator const &gen, Geom::Affine const &trans, int x0, int y0)
+ : _gen(gen)
+ , _trans(trans)
+ , _x0(x0), _y0(y0)
+ {}
+ guint32 operator()(int x, int y) {
+ Geom::Point point(x + _x0, y + _y0);
+ point *= _trans;
+ return _gen.turbulencePixel(point);
+ }
+private:
+ TurbulenceGenerator const &_gen;
+ Geom::Affine _trans;
+ int _x0, _y0;
+};
+
+void FilterTurbulence::render_cairo(FilterSlot &slot)
+{
+ cairo_surface_t *input = slot.getcairo(_input);
+ cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
+
+ // It is probably possible to render at a device scale greater than one
+ // but for the moment rendering at a device scale of one is the easiest.
+ // cairo_image_surface_get_width() returns width in pixels but
+ // cairo_surface_create_similar() requires width in device units so divide by device scale.
+ // We are rendering at a device scale of 1... so divide by device scale again!
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale(input, &x_scale, &y_scale);
+ int width = ceil(cairo_image_surface_get_width( input)/x_scale/x_scale);
+ int height = ceil(cairo_image_surface_get_height(input)/y_scale/y_scale);
+ cairo_surface_t *temp = cairo_surface_create_similar (input, CAIRO_CONTENT_COLOR_ALPHA, width, height);
+ cairo_surface_set_device_scale( temp, 1, 1 );
+
+ // color_interpolation_filter is determined by CSS value (see spec. Turbulence).
+ if( _style ) {
+ set_cairo_surface_ci(out, (SPColorInterpolation)_style->color_interpolation_filters.computed );
+ }
+
+ if (!gen->ready()) {
+ Geom::Point ta(fTileX, fTileY);
+ Geom::Point tb(fTileX + fTileWidth, fTileY + fTileHeight);
+ gen->init(seed, Geom::Rect(ta, tb),
+ Geom::Point(XbaseFrequency, YbaseFrequency), stitchTiles,
+ type == TURBULENCE_FRACTALNOISE, numOctaves);
+ }
+
+ Geom::Affine unit_trans = slot.get_units().get_matrix_primitiveunits2pb().inverse();
+ Geom::Rect slot_area = slot.get_slot_area();
+ double x0 = slot_area.min()[Geom::X];
+ double y0 = slot_area.min()[Geom::Y];
+ ink_cairo_surface_synthesize(temp, Turbulence(*gen, unit_trans, x0, y0));
+
+ // cairo_surface_write_to_png( temp, "turbulence0.png" );
+
+ cairo_t *ct = cairo_create(out);
+ cairo_set_source_surface(ct, temp, 0, 0);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ cairo_surface_destroy(temp);
+
+ cairo_surface_mark_dirty(out);
+
+ slot.set(_output, out);
+ cairo_surface_destroy(out);
+}
+
+double FilterTurbulence::complexity(Geom::Affine const &)
+{
+ return 5.0;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-turbulence.h b/src/display/nr-filter-turbulence.h
new file mode 100644
index 0000000..de21c58
--- /dev/null
+++ b/src/display/nr-filter-turbulence.h
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_TURBULENCE_H
+#define SEEN_NR_FILTER_TURBULENCE_H
+
+/*
+ * feTurbulence filter primitive renderer
+ *
+ * Authors:
+ * World Wide Web Consortium <http://www.w3.org/>
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * This file has a considerable amount of code adapted from
+ * the W3C SVG filter specs, available at:
+ * http://www.w3.org/TR/SVG11/filters.html#feTurbulence
+ *
+ * W3C original code is licensed under the terms of
+ * the (GPL compatible) W3CÂź SOFTWARE NOTICE AND LICENSE:
+ * http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+ *
+ * Copyright (C) 2007 authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/point.h>
+
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-units.h"
+
+namespace Inkscape {
+namespace Filters {
+
+enum FilterTurbulenceType {
+ TURBULENCE_FRACTALNOISE,
+ TURBULENCE_TURBULENCE,
+ TURBULENCE_ENDTYPE
+};
+
+class TurbulenceGenerator;
+
+class FilterTurbulence : public FilterPrimitive {
+public:
+ FilterTurbulence();
+ static FilterPrimitive *create();
+ ~FilterTurbulence() override;
+
+ void render_cairo(FilterSlot &slot) override;
+ double complexity(Geom::Affine const &ctm) override;
+ bool uses_background() override { return false; }
+
+ void set_baseFrequency(int axis, double freq);
+ void set_numOctaves(int num);
+ void set_seed(double s);
+ void set_stitchTiles(bool st);
+ void set_type(FilterTurbulenceType t);
+ void set_updated(bool u);
+
+ Glib::ustring name() override { return Glib::ustring("Turbulence"); }
+
+private:
+
+ TurbulenceGenerator *gen;
+
+ void turbulenceInit(long seed);
+
+ double XbaseFrequency, YbaseFrequency;
+ int numOctaves;
+ double seed;
+ bool stitchTiles;
+ FilterTurbulenceType type;
+ bool updated;
+ unsigned char *pix_data;
+
+ double fTileWidth;
+ double fTileHeight;
+
+ double fTileX;
+ double fTileY;
+
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_TURBULENCE_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/display/nr-filter-types.h b/src/display/nr-filter-types.h
new file mode 100644
index 0000000..34cfdeb
--- /dev/null
+++ b/src/display/nr-filter-types.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_NR_FILTER_TYPES_H
+#define SEEN_NR_FILTER_TYPES_H
+
+namespace Inkscape {
+namespace Filters {
+
+enum FilterPrimitiveType {
+ NR_FILTER_BLEND,
+ NR_FILTER_COLORMATRIX,
+ NR_FILTER_COMPONENTTRANSFER,
+ NR_FILTER_COMPOSITE,
+ NR_FILTER_CONVOLVEMATRIX,
+ NR_FILTER_DIFFUSELIGHTING,
+ NR_FILTER_DISPLACEMENTMAP,
+ NR_FILTER_FLOOD,
+ NR_FILTER_GAUSSIANBLUR,
+ NR_FILTER_IMAGE,
+ NR_FILTER_MERGE,
+ NR_FILTER_MORPHOLOGY,
+ NR_FILTER_OFFSET,
+ NR_FILTER_SPECULARLIGHTING,
+ NR_FILTER_TILE,
+ NR_FILTER_TURBULENCE,
+ NR_FILTER_ENDPRIMITIVETYPE // This must be last
+};
+//const int Filter::_filter_primitive_type_count = 16;
+
+enum FilterSlotType {
+ NR_FILTER_SLOT_NOT_SET = -1,
+ NR_FILTER_SOURCEGRAPHIC = -2,
+ NR_FILTER_SOURCEALPHA = -3,
+ NR_FILTER_BACKGROUNDIMAGE = -4,
+ NR_FILTER_BACKGROUNDALPHA = -5,
+ NR_FILTER_FILLPAINT = -6,
+ NR_FILTER_STROKEPAINT = -7,
+ NR_FILTER_UNNAMED_SLOT = -8
+};
+/* Unnamed slot is for Inkscape::Filters::FilterSlot internal use. Passing it as
+ * parameter to Inkscape::Filters::FilterSlot accessors may have unforeseen consequences. */
+
+enum FilterQuality {
+ FILTER_QUALITY_BEST = 2,
+ FILTER_QUALITY_BETTER = 1,
+ FILTER_QUALITY_NORMAL = 0,
+ FILTER_QUALITY_WORSE = -1,
+ FILTER_QUALITY_WORST = -2
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif // __NR_FILTER_TYPES_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/display/nr-filter-units.cpp b/src/display/nr-filter-units.cpp
new file mode 100644
index 0000000..7fdf936
--- /dev/null
+++ b/src/display/nr-filter-units.cpp
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Utilities for handling coordinate system transformations in filters
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+
+#include "display/nr-filter-units.h"
+#include "object/sp-filter-units.h"
+#include <2geom/transforms.h>
+
+using Geom::X;
+using Geom::Y;
+
+namespace Inkscape {
+namespace Filters {
+
+FilterUnits::FilterUnits() :
+ filterUnits(SP_FILTER_UNITS_OBJECTBOUNDINGBOX),
+ primitiveUnits(SP_FILTER_UNITS_USERSPACEONUSE),
+ resolution_x(-1), resolution_y(-1),
+ paraller_axis(false), automatic_resolution(true)
+{}
+
+FilterUnits::FilterUnits(SPFilterUnits const filterUnits, SPFilterUnits const primitiveUnits) :
+ filterUnits(filterUnits), primitiveUnits(primitiveUnits),
+ resolution_x(-1), resolution_y(-1),
+ paraller_axis(false), automatic_resolution(true)
+{}
+
+void FilterUnits::set_ctm(Geom::Affine const &ctm) {
+ this->ctm = ctm;
+}
+
+void FilterUnits::set_resolution(double const x_res, double const y_res) {
+ g_assert(x_res > 0);
+ g_assert(y_res > 0);
+
+ resolution_x = x_res;
+ resolution_y = y_res;
+}
+
+void FilterUnits::set_item_bbox(Geom::OptRect const &bbox) {
+ item_bbox = bbox;
+}
+
+void FilterUnits::set_filter_area(Geom::OptRect const &area) {
+ filter_area = area;
+}
+
+void FilterUnits::set_paraller(bool const paraller) {
+ paraller_axis = paraller;
+}
+
+void FilterUnits::set_automatic_resolution(bool const automatic) {
+ automatic_resolution = automatic;
+}
+
+Geom::Affine FilterUnits::get_matrix_user2pb() const {
+ g_assert(resolution_x > 0);
+ g_assert(resolution_y > 0);
+ g_assert(filter_area);
+
+ Geom::Affine u2pb = ctm;
+
+ if (paraller_axis || !automatic_resolution) {
+ u2pb[0] = resolution_x / filter_area->width();
+ u2pb[1] = 0;
+ u2pb[2] = 0;
+ u2pb[3] = resolution_y / filter_area->height();
+ u2pb[4] = ctm[4];
+ u2pb[5] = ctm[5];
+ }
+
+ return u2pb;
+}
+
+Geom::Affine FilterUnits::get_matrix_units2pb(SPFilterUnits units) const {
+ if ( item_bbox && (units == SP_FILTER_UNITS_OBJECTBOUNDINGBOX) ) {
+
+ Geom::Affine u2pb = get_matrix_user2pb();
+
+ /* TODO: make sure that user coordinate system (0,0) is in correct
+ * place in pixblock coordinates */
+ Geom::Scale scaling(item_bbox->width(), item_bbox->height());
+ u2pb *= scaling;
+ return u2pb;
+
+ } else if (units == SP_FILTER_UNITS_USERSPACEONUSE) {
+ return get_matrix_user2pb();
+ } else {
+ g_warning("Error in Inkscape::Filters::FilterUnits::get_matrix_units2pb: unrecognized unit type (%d)", units);
+ return Geom::Affine();
+ }
+}
+
+Geom::Affine FilterUnits::get_matrix_filterunits2pb() const {
+ return get_matrix_units2pb(filterUnits);
+}
+
+Geom::Affine FilterUnits::get_matrix_primitiveunits2pb() const {
+ return get_matrix_units2pb(primitiveUnits);
+}
+
+Geom::Affine FilterUnits::get_matrix_display2pb() const {
+ Geom::Affine d2pb = ctm.inverse();
+ d2pb *= get_matrix_user2pb();
+ return d2pb;
+}
+
+Geom::Affine FilterUnits::get_matrix_pb2display() const {
+ Geom::Affine pb2d = get_matrix_user2pb().inverse();
+ pb2d *= ctm;
+ return pb2d;
+}
+
+Geom::Affine FilterUnits::get_matrix_user2units(SPFilterUnits units) const {
+ if (item_bbox && units == SP_FILTER_UNITS_OBJECTBOUNDINGBOX) {
+ /* No need to worry about rotations: bounding box coordinates
+ * always have base vectors paraller with userspace coordinates */
+ Geom::Point min(item_bbox->min());
+ Geom::Point max(item_bbox->max());
+ double scale_x = 1.0 / (max[X] - min[X]);
+ double scale_y = 1.0 / (max[Y] - min[Y]);
+ //return Geom::Translate(min) * Geom::Scale(scale_x,scale_y); ?
+ return Geom::Affine(scale_x, 0,
+ 0, scale_y,
+ min[X] * scale_x, min[Y] * scale_y);
+ } else if (units == SP_FILTER_UNITS_USERSPACEONUSE) {
+ return Geom::identity();
+ } else {
+ g_warning("Error in Inkscape::Filters::FilterUnits::get_matrix_user2units: unrecognized unit type (%d)", units);
+ return Geom::Affine();
+ }
+}
+
+Geom::Affine FilterUnits::get_matrix_user2filterunits() const {
+ return get_matrix_user2units(filterUnits);
+}
+
+Geom::Affine FilterUnits::get_matrix_user2primitiveunits() const {
+ return get_matrix_user2units(primitiveUnits);
+}
+
+Geom::IntRect FilterUnits::get_pixblock_filterarea_paraller() const {
+ g_assert(filter_area);
+
+ Geom::Affine u2pb = get_matrix_user2pb();
+ Geom::Rect r = *filter_area * u2pb;
+ Geom::IntRect ir = r.roundOutwards();
+ return ir;
+}
+
+FilterUnits& FilterUnits::operator=(FilterUnits const &other) = default;
+
+} /* namespace Filters */
+} /* 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/display/nr-filter-units.h b/src/display/nr-filter-units.h
new file mode 100644
index 0000000..432e681
--- /dev/null
+++ b/src/display/nr-filter-units.h
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_UNITS_H
+#define SEEN_NR_FILTER_UNITS_H
+
+/*
+ * Utilities for handling coordinate system transformations in filters
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2007 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/sp-filter-units.h"
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+
+namespace Inkscape {
+namespace Filters {
+
+/* Notes:
+ * - "filter units" is a coordinate system where the filter region is contained
+ * between (0,0) and (1,1). Do not confuse this with the filterUnits property
+ * - "primitive units" is the coordinate system in which all lengths and distances
+ * in the filter definition should be interpreted. They are affected by the value
+ * of the primitiveUnits attribute
+ * - "pb" is the coordinate system in which filter rendering happens.
+ * It might be aligned with user or screen coordinates depending on
+ * the filter primitives used in the filter.
+ * - "display" are world coordinates of the canvas - pixel grid coordinates
+ * of the drawing area translated so that (0,0) corresponds to the document origin
+ */
+class FilterUnits {
+public:
+ FilterUnits();
+ FilterUnits(SPFilterUnits const filterUnits, SPFilterUnits const primitiveUnits);
+
+ /**
+ * Sets the current transformation matrix, i.e. transformation matrix
+ * from object's user coordinates to screen coordinates
+ */
+ void set_ctm(Geom::Affine const &ctm);
+
+ /**
+ * Sets the resolution, the filter should be rendered with.
+ */
+ void set_resolution(double const x_res, double const y_res);
+
+ /**
+ * Sets the item bounding box in user coordinates
+ */
+ void set_item_bbox(Geom::OptRect const &bbox);
+
+ /**
+ * Sets the filter effects area in user coordinates
+ */
+ void set_filter_area(Geom::OptRect const &area);
+
+ /**
+ * Sets, if x and y axis in pixblock coordinates should be paraller
+ * to x and y of user coordinates.
+ */
+ void set_paraller(bool const paraller);
+
+ /**
+ * Sets, if filter resolution is automatic.
+ * NOTE: even if resolution is automatic, it must be set with
+ * set_resolution. This only tells, if the set value is automatic.
+ */
+ void set_automatic_resolution(bool const automatic);
+
+ /**
+ * Gets the item bounding box in user coordinates
+ */
+ Geom::OptRect get_item_bbox() const { return item_bbox; };
+
+ /**
+ * Gets the filter effects area in user coordinates
+ */
+ Geom::OptRect get_filter_area() const { return filter_area; };
+
+ /**
+ * Gets Filter Units (userSpaceOnUse or objectBoundingBox)
+ */
+ SPFilterUnits get_filter_units() const { return filterUnits; };
+
+ /**
+ * Gets Primitive Units (userSpaceOnUse or objectBoundingBox)
+ */
+ SPFilterUnits get_primitive_units() const { return primitiveUnits; };
+
+ /**
+ * Gets the user coordinates to pixblock coordinates transformation matrix.
+ */
+ Geom::Affine get_matrix_user2pb() const;
+
+ /**
+ * Gets the filterUnits to pixblock coordinates transformation matrix.
+ */
+ Geom::Affine get_matrix_filterunits2pb() const;
+
+ /**
+ * Gets the primitiveUnits to pixblock coordinates transformation matrix.
+ */
+ Geom::Affine get_matrix_primitiveunits2pb() const;
+
+ /**
+ * Gets the display coordinates to pixblock coordinates transformation
+ * matrix.
+ */
+ Geom::Affine get_matrix_display2pb() const;
+
+ /**
+ * Gets the pixblock coordinates to display coordinates transformation
+ * matrix
+ */
+ Geom::Affine get_matrix_pb2display() const;
+
+ /**
+ * Gets the user coordinates to filterUnits transformation matrix.
+ */
+ Geom::Affine get_matrix_user2filterunits() const;
+
+ /**
+ * Gets the user coordinates to primitiveUnits transformation matrix.
+ */
+ Geom::Affine get_matrix_user2primitiveunits() const;
+
+ /**
+ * Returns the filter area in pixblock coordinates.
+ * NOTE: use only in filters, that define TRAIT_PARALLER in
+ * get_input_traits. The filter effects area may not be representable
+ * by simple rectangle otherwise. */
+ Geom::IntRect get_pixblock_filterarea_paraller() const;
+
+ FilterUnits& operator=(FilterUnits const &other);
+
+private:
+ Geom::Affine get_matrix_units2pb(SPFilterUnits units) const;
+ Geom::Affine get_matrix_user2units(SPFilterUnits units) const;
+
+ SPFilterUnits filterUnits, primitiveUnits;
+ double resolution_x, resolution_y;
+ bool paraller_axis;
+ bool automatic_resolution;
+ Geom::Affine ctm;
+ Geom::OptRect item_bbox;
+ Geom::OptRect filter_area;
+
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+
+#endif /* __NR_FILTER_UNITS_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/display/nr-filter-utils.h b/src/display/nr-filter-utils.h
new file mode 100644
index 0000000..0e7612d
--- /dev/null
+++ b/src/display/nr-filter-utils.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_UTILS_H
+#define SEEN_NR_FILTER_UTILS_H
+
+/**
+ * @file
+ * Definition of functions needed by several filters.
+ */
+/*
+ * Authors:
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+namespace Inkscape {
+namespace Filters {
+
+/**
+ * Clamps an integer value to a value between 0 and 255. Needed by filters where
+ * rendering computations can lead to component values out of bound.
+ *
+ * \return 0 if the value is smaller than 0, 255 if it is greater 255, else v
+ * \param v the value to clamp
+ */
+inline int clamp(int const val) {
+ if (val < 0) return 0;
+ if (val > 255) return 255;
+ return val;
+}
+
+/**
+ * Clamps an integer value to a value between 0 and 255^3.
+ *
+ * \return 0 if the value is smaller than 0, 255^3 (16581375) if it is greater than 255^3, else v
+ * \param v the value to clamp
+ */
+inline int clamp3(int const val) {
+ if (val < 0) return 0;
+ if (val > 16581375) return 16581375;
+ return val;
+}
+
+/**
+ * Macro to use the clamp function with double inputs and unsigned char output
+ */
+#define CLAMP_D_TO_U8(v) (unsigned char) clamp((int)round((v)))
+
+/**
+ * Clamps an integer to a value between 0 and alpha. Useful when handling
+ * images with premultiplied alpha, as setting some of RGB channels
+ * to a value bigger than alpha confuses the alpha blending in Inkscape
+ * \return 0 if val is negative, alpha if val is bigger than alpha, val otherwise
+ * \param val the value to clamp
+ * \param alpha the maximum value to clamp to
+ */
+inline int clamp_alpha(int const val, int const alpha) {
+ if (val < 0) return 0;
+ if (val > alpha) return alpha;
+ return val;
+}
+
+/**
+ * Macro to use the clamp function with double inputs and unsigned char output
+ */
+#define CLAMP_D_TO_U8_ALPHA(v,a) (unsigned char) clamp_alpha((int)round((v)),(a))
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif /* __NR_FILTER_UTILS_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/display/nr-filter.cpp b/src/display/nr-filter.cpp
new file mode 100644
index 0000000..eec7c1a
--- /dev/null
+++ b/src/display/nr-filter.cpp
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG filters rendering
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006-2008 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <cairo.h>
+
+#include "display/nr-filter.h"
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-slot.h"
+#include "display/nr-filter-types.h"
+#include "display/nr-filter-units.h"
+
+#include "display/nr-filter-blend.h"
+#include "display/nr-filter-composite.h"
+#include "display/nr-filter-convolve-matrix.h"
+#include "display/nr-filter-colormatrix.h"
+#include "display/nr-filter-component-transfer.h"
+#include "display/nr-filter-diffuselighting.h"
+#include "display/nr-filter-displacement-map.h"
+#include "display/nr-filter-image.h"
+#include "display/nr-filter-flood.h"
+#include "display/nr-filter-gaussian.h"
+#include "display/nr-filter-merge.h"
+#include "display/nr-filter-morphology.h"
+#include "display/nr-filter-offset.h"
+#include "display/nr-filter-specularlighting.h"
+#include "display/nr-filter-tile.h"
+#include "display/nr-filter-turbulence.h"
+
+#include "display/cairo-utils.h"
+#include "display/drawing.h"
+#include "display/drawing-item.h"
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+#include "svg/svg-length.h"
+//#include "sp-filter-units.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Filters {
+
+using Geom::X;
+using Geom::Y;
+
+Filter::Filter()
+{
+ _common_init();
+}
+
+Filter::Filter(int n)
+{
+ if (n > 0) _primitive.reserve(n);
+ _common_init();
+}
+
+void Filter::_common_init() {
+ _slot_count = 1;
+ // Having "not set" here as value means the output of last filter
+ // primitive will be used as output of this filter
+ _output_slot = NR_FILTER_SLOT_NOT_SET;
+
+ // These are the default values for filter region,
+ // as specified in SVG standard
+ // NB: SVGLength.set takes prescaled percent values: -.10 means -10%
+ _region_x.set(SVGLength::PERCENT, -.10, 0);
+ _region_y.set(SVGLength::PERCENT, -.10, 0);
+ _region_width.set(SVGLength::PERCENT, 1.20, 0);
+ _region_height.set(SVGLength::PERCENT, 1.20, 0);
+
+ // Filter resolution, negative value here stands for "automatic"
+ _x_pixels = -1.0;
+ _y_pixels = -1.0;
+
+ _filter_units = SP_FILTER_UNITS_OBJECTBOUNDINGBOX;
+ _primitive_units = SP_FILTER_UNITS_USERSPACEONUSE;
+}
+
+Filter::~Filter()
+{
+ clear_primitives();
+}
+
+
+int Filter::render(Inkscape::DrawingItem const *item, DrawingContext &graphic, DrawingContext *bgdc)
+{
+ // std::cout << "Filter::render() for: " << const_cast<Inkscape::DrawingItem *>(item)->name() << std::endl;
+ // std::cout << " graphic drawing_scale: " << graphic.surface()->device_scale() << std::endl;
+
+ if (_primitive.empty()) {
+ // when no primitives are defined, clear source graphic
+ graphic.setSource(0,0,0,0);
+ graphic.setOperator(CAIRO_OPERATOR_SOURCE);
+ graphic.paint();
+ graphic.setOperator(CAIRO_OPERATOR_OVER);
+ return 1;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ item->drawing().setFilterQuality(prefs->getInt("/options/filterquality/value", 0));
+ item->drawing().setBlurQuality(prefs->getInt("/options/blurquality/value", 0));
+ FilterQuality const filterquality = (FilterQuality)item->drawing().filterQuality();
+ int const blurquality = item->drawing().blurQuality();
+
+ Geom::Affine trans = item->ctm();
+
+ Geom::OptRect filter_area = filter_effect_area(item->itemBounds());
+ if (!filter_area) return 1;
+
+ FilterUnits units(_filter_units, _primitive_units);
+ units.set_ctm(trans);
+ units.set_item_bbox(item->itemBounds());
+ units.set_filter_area(*filter_area);
+
+ std::pair<double,double> resolution
+ = _filter_resolution(*filter_area, trans, filterquality);
+ if (!(resolution.first > 0 && resolution.second > 0)) {
+ // zero resolution - clear source graphic and return
+ graphic.setSource(0,0,0,0);
+ graphic.setOperator(CAIRO_OPERATOR_SOURCE);
+ graphic.paint();
+ graphic.setOperator(CAIRO_OPERATOR_OVER);
+ return 1;
+ }
+
+ units.set_resolution(resolution.first, resolution.second);
+ if (_x_pixels > 0) {
+ units.set_automatic_resolution(false);
+ }
+ else {
+ units.set_automatic_resolution(true);
+ }
+
+ units.set_paraller(false);
+ Geom::Affine pbtrans = units.get_matrix_display2pb();
+ for (auto & i : _primitive) {
+ if (!i->can_handle_affine(pbtrans)) {
+ units.set_paraller(true);
+ break;
+ }
+ }
+
+ FilterSlot slot(const_cast<Inkscape::DrawingItem*>(item), bgdc, graphic, units);
+ slot.set_quality(filterquality);
+ slot.set_blurquality(blurquality);
+ slot.set_device_scale(graphic.surface()->device_scale());
+
+ for (auto & i : _primitive) {
+ i->render_cairo(slot);
+ }
+
+ Geom::Point origin = graphic.targetLogicalBounds().min();
+ cairo_surface_t *result = slot.get_result(_output_slot);
+
+ // Assume for the moment that we paint the filter in sRGB
+ set_cairo_surface_ci( result, SP_CSS_COLOR_INTERPOLATION_SRGB );
+
+ graphic.setSource(result, origin[Geom::X], origin[Geom::Y]);
+ graphic.setOperator(CAIRO_OPERATOR_SOURCE);
+ graphic.paint();
+ graphic.setOperator(CAIRO_OPERATOR_OVER);
+ cairo_surface_destroy(result);
+
+ return 0;
+}
+
+void Filter::set_filter_units(SPFilterUnits unit) {
+ _filter_units = unit;
+}
+
+void Filter::set_primitive_units(SPFilterUnits unit) {
+ _primitive_units = unit;
+}
+
+void Filter::area_enlarge(Geom::IntRect &bbox, Inkscape::DrawingItem const *item) const {
+ for (auto i : _primitive) {
+ if (i) i->area_enlarge(bbox, item->ctm());
+ }
+
+/*
+ TODO: something. See images at the bottom of filters.svg with medium-low
+ filtering quality.
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ FilterQuality const filterquality = (FilterQuality)prefs->getInt("/options/filterquality/value");
+
+ if (_x_pixels <= 0 && (filterquality == FILTER_QUALITY_BEST ||
+ filterquality == FILTER_QUALITY_BETTER)) {
+ return;
+ }
+
+ Geom::Rect item_bbox;
+ Geom::OptRect maybe_bbox = item->itemBounds();
+ if (maybe_bbox.empty()) {
+ // Code below needs a bounding box
+ return;
+ }
+ item_bbox = *maybe_bbox;
+
+ std::pair<double,double> res_low
+ = _filter_resolution(item_bbox, item->ctm(), filterquality);
+ //std::pair<double,double> res_full
+ // = _filter_resolution(item_bbox, item->ctm(), FILTER_QUALITY_BEST);
+ double pixels_per_block = fmax(item_bbox.width() / res_low.first,
+ item_bbox.height() / res_low.second);
+ bbox.x0 -= (int)pixels_per_block;
+ bbox.x1 += (int)pixels_per_block;
+ bbox.y0 -= (int)pixels_per_block;
+ bbox.y1 += (int)pixels_per_block;
+*/
+}
+
+Geom::OptRect Filter::filter_effect_area(Geom::OptRect const &bbox)
+{
+ Geom::Point minp, maxp;
+
+ if (_filter_units == SP_FILTER_UNITS_OBJECTBOUNDINGBOX) {
+
+ double len_x = bbox ? bbox->width() : 0;
+ double len_y = bbox ? bbox->height() : 0;
+ /* TODO: fetch somehow the object ex and em lengths */
+
+ // Update for em, ex, and % values
+ _region_x.update(12, 6, len_x);
+ _region_y.update(12, 6, len_y);
+ _region_width.update(12, 6, len_x);
+ _region_height.update(12, 6, len_y);
+
+ if (!bbox) return Geom::OptRect();
+
+ if (_region_x.unit == SVGLength::PERCENT) {
+ minp[X] = bbox->left() + _region_x.computed;
+ } else {
+ minp[X] = bbox->left() + _region_x.computed * len_x;
+ }
+ if (_region_width.unit == SVGLength::PERCENT) {
+ maxp[X] = minp[X] + _region_width.computed;
+ } else {
+ maxp[X] = minp[X] + _region_width.computed * len_x;
+ }
+
+ if (_region_y.unit == SVGLength::PERCENT) {
+ minp[Y] = bbox->top() + _region_y.computed;
+ } else {
+ minp[Y] = bbox->top() + _region_y.computed * len_y;
+ }
+ if (_region_height.unit == SVGLength::PERCENT) {
+ maxp[Y] = minp[Y] + _region_height.computed;
+ } else {
+ maxp[Y] = minp[Y] + _region_height.computed * len_y;
+ }
+ } else if (_filter_units == SP_FILTER_UNITS_USERSPACEONUSE) {
+ // Region already set in sp-filter.cpp
+ minp[X] = _region_x.computed;
+ maxp[X] = minp[X] + _region_width.computed;
+ minp[Y] = _region_y.computed;
+ maxp[Y] = minp[Y] + _region_height.computed;
+ } else {
+ g_warning("Error in Inkscape::Filters::Filter::filter_effect_area: unrecognized value of _filter_units");
+ }
+
+ Geom::OptRect area(minp, maxp);
+ // std::cout << "Filter::filter_effect_area: area: " << *area << std::endl;
+ return area;
+}
+
+double Filter::complexity(Geom::Affine const &ctm)
+{
+ double factor = 1.0;
+ for (auto & i : _primitive) {
+ if (i) {
+ double f = i->complexity(ctm);
+ factor += (f - 1.0);
+ }
+ }
+ return factor;
+}
+
+bool Filter::uses_background()
+{
+ for (auto & i : _primitive) {
+ if (i && i->uses_background()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Constructor table holds pointers to static methods returning filter
+ * primitives. This table is indexed with FilterPrimitiveType, so that
+ * for example method in _constructor[NR_FILTER_GAUSSIANBLUR]
+ * returns a filter object of type Inkscape::Filters::FilterGaussian.
+ */
+typedef FilterPrimitive*(*FilterConstructor)();
+static FilterConstructor _constructor[NR_FILTER_ENDPRIMITIVETYPE];
+
+void Filter::_create_constructor_table()
+{
+ // Constructor table won't change in run-time, so no need to recreate
+ static bool created = false;
+ if(created) return;
+
+/* Some filter classes are not implemented yet.
+ Some of them still have only boilerplate code.*/
+ _constructor[NR_FILTER_BLEND] = &FilterBlend::create;
+ _constructor[NR_FILTER_COLORMATRIX] = &FilterColorMatrix::create;
+ _constructor[NR_FILTER_COMPONENTTRANSFER] = &FilterComponentTransfer::create;
+ _constructor[NR_FILTER_COMPOSITE] = &FilterComposite::create;
+ _constructor[NR_FILTER_CONVOLVEMATRIX] = &FilterConvolveMatrix::create;
+ _constructor[NR_FILTER_DIFFUSELIGHTING] = &FilterDiffuseLighting::create;
+ _constructor[NR_FILTER_DISPLACEMENTMAP] = &FilterDisplacementMap::create;
+ _constructor[NR_FILTER_FLOOD] = &FilterFlood::create;
+ _constructor[NR_FILTER_GAUSSIANBLUR] = &FilterGaussian::create;
+ _constructor[NR_FILTER_IMAGE] = &FilterImage::create;
+ _constructor[NR_FILTER_MERGE] = &FilterMerge::create;
+ _constructor[NR_FILTER_MORPHOLOGY] = &FilterMorphology::create;
+ _constructor[NR_FILTER_OFFSET] = &FilterOffset::create;
+ _constructor[NR_FILTER_SPECULARLIGHTING] = &FilterSpecularLighting::create;
+ _constructor[NR_FILTER_TILE] = &FilterTile::create;
+ _constructor[NR_FILTER_TURBULENCE] = &FilterTurbulence::create;
+ created = true;
+}
+
+int Filter::add_primitive(FilterPrimitiveType type)
+{
+ _create_constructor_table();
+
+ // Check that we can create a new filter of specified type
+ if (type < 0 || type >= NR_FILTER_ENDPRIMITIVETYPE)
+ return -1;
+ if (!_constructor[type]) return -1;
+ FilterPrimitive *created = _constructor[type]();
+
+ int handle = _primitive.size();
+ _primitive.push_back(created);
+ return handle;
+}
+
+int Filter::replace_primitive(int target, FilterPrimitiveType type)
+{
+ _create_constructor_table();
+
+ // Check that target is valid primitive inside this filter
+ if (target < 0) return -1;
+ if (static_cast<unsigned>(target) >= _primitive.size()) return -1;
+
+ // Check that we can create a new filter of specified type
+ if (type < 0 || type >= NR_FILTER_ENDPRIMITIVETYPE)
+ return -1;
+ if (!_constructor[type]) return -1;
+ FilterPrimitive *created = _constructor[type]();
+
+ delete _primitive[target];
+ _primitive[target] = created;
+ return target;
+}
+
+FilterPrimitive *Filter::get_primitive(int handle) {
+ if (handle < 0 || handle >= static_cast<int>(_primitive.size())) return nullptr;
+ return _primitive[handle];
+}
+
+void Filter::clear_primitives()
+{
+ for (auto & i : _primitive) {
+ delete i;
+ }
+ _primitive.clear();
+}
+
+void Filter::set_x(SVGLength const &length)
+{
+ if (length._set)
+ _region_x = length;
+}
+void Filter::set_y(SVGLength const &length)
+{
+ if (length._set)
+ _region_y = length;
+}
+void Filter::set_width(SVGLength const &length)
+{
+ if (length._set)
+ _region_width = length;
+}
+void Filter::set_height(SVGLength const &length)
+{
+ if (length._set)
+ _region_height = length;
+}
+
+void Filter::set_resolution(double const pixels) {
+ if (pixels > 0) {
+ _x_pixels = pixels;
+ _y_pixels = pixels;
+ }
+}
+
+void Filter::set_resolution(double const x_pixels, double const y_pixels) {
+ if (x_pixels >= 0 && y_pixels >= 0) {
+ _x_pixels = x_pixels;
+ _y_pixels = y_pixels;
+ }
+}
+
+void Filter::reset_resolution() {
+ _x_pixels = -1;
+ _y_pixels = -1;
+}
+
+int Filter::_resolution_limit(FilterQuality const quality) const {
+ int limit = -1;
+ switch (quality) {
+ case FILTER_QUALITY_WORST:
+ limit = 32;
+ break;
+ case FILTER_QUALITY_WORSE:
+ limit = 64;
+ break;
+ case FILTER_QUALITY_NORMAL:
+ limit = 256;
+ break;
+ case FILTER_QUALITY_BETTER:
+ case FILTER_QUALITY_BEST:
+ default:
+ break;
+ }
+ return limit;
+}
+
+std::pair<double,double> Filter::_filter_resolution(
+ Geom::Rect const &area, Geom::Affine const &trans,
+ FilterQuality const filterquality) const
+{
+ std::pair<double,double> resolution;
+ if (_x_pixels > 0) {
+ double y_len;
+ if (_y_pixels > 0) {
+ y_len = _y_pixels;
+ } else {
+ y_len = (_x_pixels * (area.max()[Y] - area.min()[Y]))
+ / (area.max()[X] - area.min()[X]);
+ }
+ resolution.first = _x_pixels;
+ resolution.second = y_len;
+ } else {
+ Geom::Point origo = area.min();
+ origo *= trans;
+ Geom::Point max_i(area.max()[X], area.min()[Y]);
+ max_i *= trans;
+ Geom::Point max_j(area.min()[X], area.max()[Y]);
+ max_j *= trans;
+ double i_len = sqrt((origo[X] - max_i[X]) * (origo[X] - max_i[X])
+ + (origo[Y] - max_i[Y]) * (origo[Y] - max_i[Y]));
+ double j_len = sqrt((origo[X] - max_j[X]) * (origo[X] - max_j[X])
+ + (origo[Y] - max_j[Y]) * (origo[Y] - max_j[Y]));
+ int limit = _resolution_limit(filterquality);
+ if (limit > 0 && (i_len > limit || j_len > limit)) {
+ double aspect_ratio = i_len / j_len;
+ if (i_len > j_len) {
+ i_len = limit;
+ j_len = i_len / aspect_ratio;
+ }
+ else {
+ j_len = limit;
+ i_len = j_len * aspect_ratio;
+ }
+ }
+ resolution.first = i_len;
+ resolution.second = j_len;
+ }
+ return resolution;
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-filter.h b/src/display/nr-filter.h
new file mode 100644
index 0000000..caa7188
--- /dev/null
+++ b/src/display/nr-filter.h
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NR_FILTER_H
+#define SEEN_NR_FILTER_H
+
+/*
+ * SVG filters rendering
+ *
+ * Author:
+ * Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006 Niko Kiirala
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+//#include "display/nr-arena-item.h"
+#include <cairo.h>
+#include "display/nr-filter-primitive.h"
+#include "display/nr-filter-types.h"
+#include "svg/svg-length.h"
+#include "object/sp-filter-units.h"
+#include "inkgc/gc-managed.h"
+
+namespace Inkscape {
+class DrawingContext;
+class DrawingItem;
+
+namespace Filters {
+
+class Filter {
+public:
+ /** Given background state from @a bgdc and an intermediate rendering from the surface
+ * backing @a graphic, modify the contents of the surface backing @a graphic to represent
+ * the results of filter rendering. @a bgarea and @a area specify bounding boxes
+ * of both surfaces in world coordinates; Cairo contexts are assumed to be in default state
+ * (0,0 = surface origin, no path, OVER operator) */
+ int render(Inkscape::DrawingItem const *item, DrawingContext &graphic, DrawingContext *bgdc);
+
+ /**
+ * Creates a new filter primitive under this filter object.
+ * New primitive is placed so that it will be executed after all filter
+ * primitives defined beforehand for this filter object.
+ * Should this filter not have enough space for a new primitive, the filter
+ * is enlarged to accommodate the new filter element. It may be enlarged by
+ * more that one element.
+ * Returns a handle (non-negative integer) to the filter primitive created.
+ * Returns -1, if type is not valid filter primitive type or filter
+ * primitive of such type cannot be created.
+ */
+ int add_primitive(FilterPrimitiveType type);
+ /**
+ * Removes all filter primitives from this filter.
+ * All pointers to filter primitives inside this filter should be
+ * considered invalid after calling this function.
+ */
+ void clear_primitives();
+ /**
+ * Replaces filter primitive pointed by 'target' with a new filter
+ * primitive of type 'type'
+ * If 'target' does not correspond to any primitive inside this filter OR
+ * 'type' is not a valid filter primitive type OR
+ * filter primitive of such type cannot be created,
+ * this function returns -1 and doesn't change the internal state of this
+ * filter.
+ * Otherwise, a new filter primitive is created. Any pointers to filter
+ * primitive 'target' should be considered invalid. A handle to the
+ * newly created primitive is returned.
+ */
+ int replace_primitive(int primitive, FilterPrimitiveType type);
+
+ /**
+ * Returns a pointer to the primitive, which the handle corresponds to.
+ * If the handle is not valid, returns NULL.
+ */
+ FilterPrimitive *get_primitive(int handle);
+
+ /**
+ * Sets the slot number 'slot' to be used as result from this filter.
+ * If output is not set, the output from last filter primitive is used as
+ * output from the filter.
+ * It is an error to specify a pre-defined slot as 'slot'. Such call does
+ * not have any effect to the state of filter or its primitives.
+ */
+ void set_output(int slot);
+
+ void set_x(SVGLength const &length);
+ void set_y(SVGLength const &length);
+ void set_width(SVGLength const &length);
+ void set_height(SVGLength const &length);
+
+ /**
+ * Sets the filter effects region.
+ * Passing an unset length (length._set == false) as any of the parameters
+ * results in that parameter not being changed.
+ * Filter will not hold any references to the passed SVGLength object after
+ * function returns.
+ * If any of these parameters does not get set, the default value, as
+ * defined in SVG standard, for that parameter is used instead.
+ */
+ void set_region(SVGLength const &x, SVGLength const &y,
+ SVGLength const &width, SVGLength const &height);
+
+ /**
+ * Resets the filter effects region to its default value as defined
+ * in SVG standard.
+ */
+ void reset_region();
+
+ /**
+ * Sets the width of intermediate images in pixels. If not set, suitable
+ * resolution is determined automatically. If x_pixels is less than zero,
+ * calling this function results in no changes to filter state.
+ */
+ void set_resolution(double const x_pixels);
+
+ /**
+ * Sets the width and height of intermediate images in pixels. If not set,
+ * suitable resolution is determined automatically. If either parameter is
+ * less than zero, calling this function results in no changes to filter
+ * state.
+ */
+ void set_resolution(double const x_pixels, double const y_pixels);
+
+ /**
+ * Resets the filter resolution to its default value, i.e. automatically
+ * determined.
+ */
+ void reset_resolution();
+
+ /**
+ * Set the filterUnits-property. If not set, the default value of
+ * objectBoundingBox is used. If the parameter value is not a
+ * valid enumeration value from SPFilterUnits, no changes to filter state
+ * are made.
+ */
+ void set_filter_units(SPFilterUnits unit);
+
+ /**
+ * Set the primitiveUnits-property. If not set, the default value of
+ * userSpaceOnUse is used. If the parameter value is not a valid
+ * enumeration value from SPFilterUnits, no changes to filter state
+ * are made.
+ */
+ void set_primitive_units(SPFilterUnits unit);
+
+ /**
+ * Modifies the given area to accommodate for filters needing pixels
+ * outside the rendered area.
+ * When this function returns, area contains the area that needs
+ * to be rendered so that after filtering, the original area is
+ * drawn correctly.
+ */
+ void area_enlarge(Geom::IntRect &area, Inkscape::DrawingItem const *item) const;
+ /**
+ * Returns the filter effects area in user coordinate system.
+ * The given bounding box should be a bounding box as specified in
+ * SVG standard and in user coordinate system.
+ */
+ Geom::OptRect filter_effect_area(Geom::OptRect const &bbox);
+
+ // returns cache score factor
+ double complexity(Geom::Affine const &ctm);
+
+ // says whether the filter accesses any of the background images
+ bool uses_background();
+
+ /** Creates a new filter with space for one filter element */
+ Filter();
+ /**
+ * Creates a new filter with space for n filter elements. If number of
+ * filter elements is known beforehand, it's better to use this
+ * constructor.
+ */
+ Filter(int n);
+ /** Destroys the filter and all its primitives */
+ virtual ~Filter();
+
+private:
+ std::vector<FilterPrimitive*> _primitive;
+ /** Amount of image slots used, when this filter was rendered last time */
+ int _slot_count;
+
+ /** Image slot, from which filter output should be read.
+ * Negative values mean 'not set' */
+ int _output_slot;
+
+ SVGLength _region_x;
+ SVGLength _region_y;
+ SVGLength _region_width;
+ SVGLength _region_height;
+
+ /* x- and y-resolutions for filter rendering.
+ * Negative values mean 'not set'.
+ * If _y_pixels is set, _x_pixels should be set, too. */
+ double _x_pixels;
+ double _y_pixels;
+
+ SPFilterUnits _filter_units;
+ SPFilterUnits _primitive_units;
+
+ void _create_constructor_table();
+ void _common_init();
+ int _resolution_limit(FilterQuality const quality) const;
+ std::pair<double,double> _filter_resolution(Geom::Rect const &area,
+ Geom::Affine const &trans,
+ FilterQuality const q) const;
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+
+#endif /* __NR_FILTER_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/display/nr-light-types.h b/src/display/nr-light-types.h
new file mode 100644
index 0000000..8de142f
--- /dev/null
+++ b/src/display/nr-light-types.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_NR_LIGHT_TYPES_H
+#define SEEN_NR_LIGHT_TYPES_H
+
+namespace Inkscape {
+namespace Filters {
+
+enum LightType{
+ NO_LIGHT = 0,
+ DISTANT_LIGHT,
+ POINT_LIGHT,
+ SPOT_LIGHT
+};
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif // __NR_LIGHT_TYPES_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/display/nr-light.cpp b/src/display/nr-light.cpp
new file mode 100644
index 0000000..3009955
--- /dev/null
+++ b/src/display/nr-light.cpp
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Light rendering helpers
+ *
+ * Author:
+ * Jean-Rene Reinhard <jr@komite.net>
+ *
+ * Copyright (C) 2006 Jean-Rene Reinhard
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+
+#include "display/nr-light.h"
+#include "display/nr-3dutils.h"
+#include "object/filters/distantlight.h"
+#include "object/filters/pointlight.h"
+#include "object/filters/spotlight.h"
+#include "color.h"
+
+namespace Inkscape {
+namespace Filters {
+
+DistantLight::DistantLight(SPFeDistantLight *light, guint32 lighting_color) {
+ color = lighting_color;
+ azimuth = M_PI / 180 * light->azimuth;
+ elevation = M_PI / 180 * light->elevation;
+}
+
+DistantLight::~DistantLight() = default;
+
+void DistantLight::light_vector(NR::Fvector &v) {
+ v[X_3D] = std::cos(azimuth)*std::cos(elevation);
+ v[Y_3D] = std::sin(azimuth)*std::cos(elevation);
+ v[Z_3D] = std::sin(elevation);
+}
+
+void DistantLight::light_components(NR::Fvector &lc) {
+ lc[LIGHT_RED] = SP_RGBA32_R_U(color);
+ lc[LIGHT_GREEN] = SP_RGBA32_G_U(color);
+ lc[LIGHT_BLUE] = SP_RGBA32_B_U(color);
+}
+
+PointLight::PointLight(SPFePointLight *light, guint32 lighting_color, const Geom::Affine &trans, int device_scale) {
+ color = lighting_color;
+ l_x = light->x * device_scale;
+ l_y = light->y * device_scale;
+ l_z = light->z * device_scale;
+ NR::convert_coord(l_x, l_y, l_z, trans);
+}
+
+PointLight::~PointLight() = default;
+
+void PointLight::light_vector(NR::Fvector &v, double x, double y, double z) {
+ v[X_3D] = l_x - x;
+ v[Y_3D] = l_y - y;
+ v[Z_3D] = l_z - z;
+ NR::normalize_vector(v);
+}
+
+void PointLight::light_components(NR::Fvector &lc) {
+ lc[LIGHT_RED] = SP_RGBA32_R_U(color);
+ lc[LIGHT_GREEN] = SP_RGBA32_G_U(color);
+ lc[LIGHT_BLUE] = SP_RGBA32_B_U(color);
+}
+
+SpotLight::SpotLight(SPFeSpotLight *light, guint32 lighting_color, const Geom::Affine &trans, int device_scale) {
+ double p_x, p_y, p_z;
+ color = lighting_color;
+ l_x = light->x * device_scale;
+ l_y = light->y * device_scale;
+ l_z = light->z * device_scale;
+ p_x = light->pointsAtX * device_scale;
+ p_y = light->pointsAtY * device_scale;
+ p_z = light->pointsAtZ * device_scale;
+ cos_lca = std::cos(M_PI / 180 * light->limitingConeAngle);
+ speExp = light->specularExponent;
+ NR::convert_coord(l_x, l_y, l_z, trans);
+ NR::convert_coord(p_x, p_y, p_z, trans);
+ S[X_3D] = p_x - l_x;
+ S[Y_3D] = p_y - l_y;
+ S[Z_3D] = p_z - l_z;
+ NR::normalize_vector(S);
+
+}
+
+SpotLight::~SpotLight() = default;
+
+void SpotLight::light_vector(NR::Fvector &v, double x, double y, double z) {
+ v[X_3D] = l_x - x;
+ v[Y_3D] = l_y - y;
+ v[Z_3D] = l_z - z;
+ NR::normalize_vector(v);
+}
+
+void SpotLight::light_components(NR::Fvector &lc, const NR::Fvector &L) {
+ double spmod = (-1) * NR::scalar_product(L, S);
+ if (spmod <= cos_lca)
+ spmod = 0;
+ else
+ spmod = std::pow(spmod, speExp);
+ lc[LIGHT_RED] = spmod * SP_RGBA32_R_U(color);
+ lc[LIGHT_GREEN] = spmod * SP_RGBA32_G_U(color);
+ lc[LIGHT_BLUE] = spmod * SP_RGBA32_B_U(color);
+}
+
+} /* namespace Filters */
+} /* 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/display/nr-light.h b/src/display/nr-light.h
new file mode 100644
index 0000000..ae119ce
--- /dev/null
+++ b/src/display/nr-light.h
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2017 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_NR_LIGHT_H
+#define SEEN_NR_LIGHT_H
+
+/** \file
+ * These classes provide tools to compute interesting objects relative to light
+ * sources. Each class provides a constructor converting information contained
+ * in a sp light object into information useful in the current setting, a
+ * method to get the light vector (at a given point) and a method to get the
+ * light color components (at a given point).
+ */
+
+#include <2geom/forward.h>
+
+#include "display/nr-3dutils.h"
+#include "display/nr-light-types.h"
+
+class SPFeDistantLight;
+class SPFePointLight;
+class SPFeSpotLight;
+typedef unsigned int guint32;
+
+namespace Inkscape {
+namespace Filters {
+
+enum LightComponent {
+ LIGHT_RED = 0,
+ LIGHT_GREEN,
+ LIGHT_BLUE
+};
+
+class DistantLight {
+ public:
+ /**
+ * Constructor
+ *
+ * \param light the sp light object
+ * \param lighting_color the lighting_color used
+ */
+ DistantLight(SPFeDistantLight *light, guint32 lighting_color);
+ virtual ~DistantLight();
+
+ /**
+ * Computes the light vector of the distant light
+ *
+ * \param v a Fvector reference where we store the result
+ */
+ void light_vector(NR::Fvector &v);
+
+ /**
+ * Computes the light components of the distant light
+ *
+ * \param lc a Fvector reference where we store the result, X=R, Y=G, Z=B
+ */
+ void light_components(NR::Fvector &lc);
+
+ private:
+ guint32 color;
+ double azimuth; //azimuth in rad
+ double elevation; //elevation in rad
+};
+
+class PointLight {
+ public:
+ /**
+ * Constructor
+ *
+ * \param light the sp light object
+ * \param lighting_color the lighting_color used
+ * \param trans the transformation between absolute coordinate (those
+ * employed in the sp light object) and current coordinate (those
+ * employed in the rendering)
+ * \param device_scale for high DPI monitors.
+ */
+ PointLight(SPFePointLight *light, guint32 lighting_color, const Geom::Affine &trans, int device_scale = 1);
+ virtual ~PointLight();
+ /**
+ * Computes the light vector of the distant light at point (x,y,z).
+ * x, y and z are given in the arena_item coordinate, they are used as
+ * is
+ *
+ * \param v a Fvector reference where we store the result
+ * \param x x coordinate of the current point
+ * \param y y coordinate of the current point
+ * \param z z coordinate of the current point
+ */
+ void light_vector(NR::Fvector &v, double x, double y, double z);
+
+ /**
+ * Computes the light components of the distant light
+ *
+ * \param lc a Fvector reference where we store the result, X=R, Y=G, Z=B
+ */
+ void light_components(NR::Fvector &lc);
+
+ private:
+ guint32 color;
+ //light position coordinates in render setting
+ double l_x;
+ double l_y;
+ double l_z;
+};
+
+class SpotLight {
+ public:
+ /**
+ * Constructor
+ *
+ * \param light the sp light object
+ * \param lighting_color the lighting_color used
+ * \param trans the transformation between absolute coordinate (those
+ * employed in the sp light object) and current coordinate (those
+ * employed in the rendering)
+ * \param device_scale for high DPI monitors.
+ */
+ SpotLight(SPFeSpotLight *light, guint32 lighting_color, const Geom::Affine &trans, int device_scale = 1);
+ virtual ~SpotLight();
+
+ /**
+ * Computes the light vector of the distant light at point (x,y,z).
+ * x, y and z are given in the arena_item coordinate, they are used as
+ * is
+ *
+ * \param v a Fvector reference where we store the result
+ * \param x x coordinate of the current point
+ * \param y y coordinate of the current point
+ * \param z z coordinate of the current point
+ */
+ void light_vector(NR::Fvector &v, double x, double y, double z);
+
+ /**
+ * Computes the light components of the distant light at the current
+ * point. We only need the light vector to compute these
+ *
+ * \param lc a Fvector reference where we store the result, X=R, Y=G, Z=B
+ * \param L the light vector of the current point
+ */
+ void light_components(NR::Fvector &lc, const NR::Fvector &L);
+
+ private:
+ guint32 color;
+ //light position coordinates in render setting
+ double l_x;
+ double l_y;
+ double l_z;
+ double cos_lca; //cos of the limiting cone angle
+ double speExp; //specular exponent;
+ NR::Fvector S; //unit vector from light position in the direction
+ //the spot point at
+};
+
+
+} /* namespace Filters */
+} /* namespace Inkscape */
+
+#endif // __NR_LIGHT_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/display/nr-style.cpp b/src/display/nr-style.cpp
new file mode 100644
index 0000000..fc1b19a
--- /dev/null
+++ b/src/display/nr-style.cpp
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Style information for rendering.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/nr-style.h"
+#include "style.h"
+#include "object/sp-paint-server.h"
+#include "display/canvas-bpath.h" // contains SPStrokeJoinType, SPStrokeCapType etc. (WTF!)
+#include "display/drawing-context.h"
+#include "display/drawing-pattern.h"
+
+void NRStyle::Paint::clear()
+{
+ if (server) {
+ sp_object_unref(server, nullptr);
+ server = nullptr;
+ }
+ type = PAINT_NONE;
+}
+
+void NRStyle::Paint::set(SPColor const &c)
+{
+ clear();
+ type = PAINT_COLOR;
+ color = c;
+}
+
+void NRStyle::Paint::set(SPPaintServer *ps)
+{
+ clear();
+ if (ps) {
+ type = PAINT_SERVER;
+ server = ps;
+ sp_object_ref(server, nullptr);
+ }
+}
+
+void NRStyle::Paint::set(const SPIPaint* paint)
+{
+ if (paint->isPaintserver()) {
+ SPPaintServer* server = paint->value.href->getObject();
+ if (server && server->isValid()) {
+ set(server);
+ } else if (paint->colorSet) {
+ set(paint->value.color);
+ } else {
+ clear();
+ }
+ } else if (paint->isColor()) {
+ set(paint->value.color);
+ } else if (paint->isNone()) {
+ clear();
+ } else if (paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ||
+ paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) {
+ // A marker in the defs section will result in ending up here.
+ // std::cerr << "NRStyle::Paint::set: Double" << std::endl;
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+
+NRStyle::NRStyle()
+ : fill()
+ , stroke()
+ , stroke_width(0.0)
+ , miter_limit(0.0)
+ , n_dash(0)
+ , dash(nullptr)
+ , dash_offset(0.0)
+ , fill_rule(CAIRO_FILL_RULE_EVEN_ODD)
+ , line_cap(CAIRO_LINE_CAP_BUTT)
+ , line_join(CAIRO_LINE_JOIN_MITER)
+ , fill_pattern(nullptr)
+ , stroke_pattern(nullptr)
+ , text_decoration_fill_pattern(nullptr)
+ , text_decoration_stroke_pattern(nullptr)
+ , text_decoration_line(TEXT_DECORATION_LINE_CLEAR)
+ , text_decoration_style(TEXT_DECORATION_STYLE_CLEAR)
+ , text_decoration_fill()
+ , text_decoration_stroke()
+ , text_decoration_stroke_width(0.0)
+ , phase_length(0.0)
+ , tspan_line_start(false)
+ , tspan_line_end(false)
+ , tspan_width(0)
+ , ascender(0)
+ , descender(0)
+ , underline_thickness(0)
+ , underline_position(0)
+ , line_through_thickness(0)
+ , line_through_position(0)
+ , font_size(0)
+{
+ paint_order_layer[0] = PAINT_ORDER_NORMAL;
+}
+
+NRStyle::~NRStyle()
+{
+ if (fill_pattern) cairo_pattern_destroy(fill_pattern);
+ if (stroke_pattern) cairo_pattern_destroy(stroke_pattern);
+ if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern);
+ if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern);
+ if (dash){
+ delete [] dash;
+ }
+ fill.clear();
+ stroke.clear();
+ text_decoration_fill.clear();
+ text_decoration_stroke.clear();
+}
+
+void NRStyle::set(SPStyle *style, SPStyle *context_style)
+{
+ // Handle 'context-fill' and 'context-stroke': Work in progress
+ const SPIPaint *style_fill = &(style->fill);
+ if( style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ) {
+ if( context_style != nullptr ) {
+ style_fill = &(context_style->fill);
+ } else {
+ // A marker in the defs section will result in ending up here.
+ //std::cerr << "NRStyle::set: 'context-fill': 'context_style' is NULL" << std::endl;
+ }
+ } else if ( style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ) {
+ if( context_style != nullptr ) {
+ style_fill = &(context_style->stroke);
+ } else {
+ //std::cerr << "NRStyle::set: 'context-stroke': 'context_style' is NULL" << std::endl;
+ }
+ }
+
+ fill.set(style_fill);
+ fill.opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+
+ switch (style->fill_rule.computed) {
+ case SP_WIND_RULE_EVENODD:
+ fill_rule = CAIRO_FILL_RULE_EVEN_ODD;
+ break;
+ case SP_WIND_RULE_NONZERO:
+ fill_rule = CAIRO_FILL_RULE_WINDING;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ const SPIPaint *style_stroke = &(style->stroke);
+ if( style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ) {
+ if( context_style != nullptr ) {
+ style_stroke = &(context_style->fill);
+ } else {
+ //std::cerr << "NRStyle::set: 'context-fill': 'context_style' is NULL" << std::endl;
+ }
+ } else if ( style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ) {
+ if( context_style != nullptr ) {
+ style_stroke = &(context_style->stroke);
+ } else {
+ //std::cerr << "NRStyle::set: 'context-stroke': 'context_style' is NULL" << std::endl;
+ }
+ }
+
+ stroke.set(style_stroke);
+ stroke.opacity = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ stroke_width = style->stroke_width.computed;
+ switch (style->stroke_linecap.computed) {
+ case SP_STROKE_LINECAP_ROUND:
+ line_cap = CAIRO_LINE_CAP_ROUND;
+ break;
+ case SP_STROKE_LINECAP_SQUARE:
+ line_cap = CAIRO_LINE_CAP_SQUARE;
+ break;
+ case SP_STROKE_LINECAP_BUTT:
+ line_cap = CAIRO_LINE_CAP_BUTT;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ switch (style->stroke_linejoin.computed) {
+ case SP_STROKE_LINEJOIN_ROUND:
+ line_join = CAIRO_LINE_JOIN_ROUND;
+ break;
+ case SP_STROKE_LINEJOIN_BEVEL:
+ line_join = CAIRO_LINE_JOIN_BEVEL;
+ break;
+ case SP_STROKE_LINEJOIN_MITER:
+ line_join = CAIRO_LINE_JOIN_MITER;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ miter_limit = style->stroke_miterlimit.value;
+
+ if (dash){
+ delete [] dash;
+ }
+
+ n_dash = style->stroke_dasharray.values.size();
+ if (n_dash != 0) {
+ dash_offset = style->stroke_dashoffset.computed;
+ dash = new double[n_dash];
+ for (unsigned int i = 0; i < n_dash; ++i) {
+ dash[i] = style->stroke_dasharray.values[i].computed;
+ }
+ } else {
+ dash_offset = 0.0;
+ dash = nullptr;
+ }
+
+
+ for( unsigned i = 0; i < PAINT_ORDER_LAYERS; ++i) {
+ switch (style->paint_order.layer[i]) {
+ case SP_CSS_PAINT_ORDER_NORMAL:
+ paint_order_layer[i]=PAINT_ORDER_NORMAL;
+ break;
+ case SP_CSS_PAINT_ORDER_FILL:
+ paint_order_layer[i]=PAINT_ORDER_FILL;
+ break;
+ case SP_CSS_PAINT_ORDER_STROKE:
+ paint_order_layer[i]=PAINT_ORDER_STROKE;
+ break;
+ case SP_CSS_PAINT_ORDER_MARKER:
+ paint_order_layer[i]=PAINT_ORDER_MARKER;
+ break;
+ }
+ }
+
+ text_decoration_line = TEXT_DECORATION_LINE_CLEAR;
+ if(style->text_decoration_line.inherit ){ text_decoration_line |= TEXT_DECORATION_LINE_INHERIT; }
+ if(style->text_decoration_line.underline ){ text_decoration_line |= TEXT_DECORATION_LINE_UNDERLINE + TEXT_DECORATION_LINE_SET; }
+ if(style->text_decoration_line.overline ){ text_decoration_line |= TEXT_DECORATION_LINE_OVERLINE + TEXT_DECORATION_LINE_SET; }
+ if(style->text_decoration_line.line_through){ text_decoration_line |= TEXT_DECORATION_LINE_LINETHROUGH + TEXT_DECORATION_LINE_SET; }
+ if(style->text_decoration_line.blink ){ text_decoration_line |= TEXT_DECORATION_LINE_BLINK + TEXT_DECORATION_LINE_SET; }
+
+ text_decoration_style = TEXT_DECORATION_STYLE_CLEAR;
+ if(style->text_decoration_style.inherit ){ text_decoration_style |= TEXT_DECORATION_STYLE_INHERIT; }
+ if(style->text_decoration_style.solid ){ text_decoration_style |= TEXT_DECORATION_STYLE_SOLID + TEXT_DECORATION_STYLE_SET; }
+ if(style->text_decoration_style.isdouble ){ text_decoration_style |= TEXT_DECORATION_STYLE_ISDOUBLE + TEXT_DECORATION_STYLE_SET; }
+ if(style->text_decoration_style.dotted ){ text_decoration_style |= TEXT_DECORATION_STYLE_DOTTED + TEXT_DECORATION_STYLE_SET; }
+ if(style->text_decoration_style.dashed ){ text_decoration_style |= TEXT_DECORATION_STYLE_DASHED + TEXT_DECORATION_STYLE_SET; }
+ if(style->text_decoration_style.wavy ){ text_decoration_style |= TEXT_DECORATION_STYLE_WAVY + TEXT_DECORATION_STYLE_SET; }
+
+ /* FIXME
+ The meaning of text-decoration-color in CSS3 for SVG is ambiguous (2014-05-06). Set
+ it for fill, for stroke, for both? Both would seem like the obvious choice but what happens
+ is that for text which is just fill (very common) it makes the lines fatter because it
+ enables stroke on the decorations when it wasn't present on the text. That contradicts the
+ usual behavior where the text and decorations by default have the same fill/stroke.
+
+ The behavior here is that if color is defined it is applied to text_decoration_fill/stroke
+ ONLY if the corresponding fill/stroke is also present.
+
+ Hopefully the standard will be clarified to resolve this issue.
+ */
+
+ // Unless explicitly set on an element, text decoration is inherited from
+ // closest ancestor where 'text-decoration' was set. That is, setting
+ // 'text-decoration' on an ancestor fixes the fill and stroke of the
+ // decoration to the fill and stroke values of that ancestor.
+ SPStyle* style_td = style;
+ if ( style->text_decoration.style_td ) style_td = style->text_decoration.style_td;
+ text_decoration_stroke.opacity = SP_SCALE24_TO_FLOAT(style_td->stroke_opacity.value);
+ text_decoration_stroke_width = style_td->stroke_width.computed;
+
+ // Priority is given in order:
+ // * text_decoration_fill
+ // * text_decoration_color (only if fill set)
+ // * fill
+ if (style_td->text_decoration_fill.set) {
+ text_decoration_fill.set(&(style_td->text_decoration_fill));
+ } else if (style_td->text_decoration_color.set) {
+ if(style->fill.isPaintserver() || style->fill.isColor()) {
+ // SVG sets color specifically
+ text_decoration_fill.set(style->text_decoration_color.value.color);
+ } else {
+ // No decoration fill because no text fill
+ text_decoration_fill.clear();
+ }
+ } else {
+ // Pick color/pattern from text
+ text_decoration_fill.set(&(style_td->fill));
+ }
+
+ if (style_td->text_decoration_stroke.set) {
+ text_decoration_stroke.set(&(style_td->text_decoration_stroke));
+ } else if (style_td->text_decoration_color.set) {
+ if(style->stroke.isPaintserver() || style->stroke.isColor()) {
+ // SVG sets color specifically
+ text_decoration_stroke.set(style->text_decoration_color.value.color);
+ } else {
+ // No decoration stroke because no text stroke
+ text_decoration_stroke.clear();
+ }
+ } else {
+ // Pick color/pattern from text
+ text_decoration_stroke.set(&(style_td->stroke));
+ }
+
+ if (text_decoration_line != TEXT_DECORATION_LINE_CLEAR) {
+ phase_length = style->text_decoration_data.phase_length;
+ tspan_line_start = style->text_decoration_data.tspan_line_start;
+ tspan_line_end = style->text_decoration_data.tspan_line_end;
+ tspan_width = style->text_decoration_data.tspan_width;
+ ascender = style->text_decoration_data.ascender;
+ descender = style->text_decoration_data.descender;
+ underline_thickness = style->text_decoration_data.underline_thickness;
+ underline_position = style->text_decoration_data.underline_position;
+ line_through_thickness = style->text_decoration_data.line_through_thickness;
+ line_through_position = style->text_decoration_data.line_through_position;
+ font_size = style->font_size.computed;
+ }
+
+ text_direction = style->direction.computed;
+
+ update();
+}
+
+cairo_pattern_t* NRStyle::preparePaint(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern, Paint& paint)
+{
+ cairo_pattern_t* cpattern = nullptr;
+
+ switch (paint.type) {
+ case PAINT_SERVER:
+ if (pattern) {
+ cpattern = pattern->renderPattern(paint.opacity);
+ } else {
+ cpattern = paint.server->pattern_new(dc.raw(), paintbox, paint.opacity);
+ }
+ break;
+ case PAINT_COLOR: {
+ SPColor const &c = paint.color;
+ cpattern = cairo_pattern_create_rgba(
+ c.v.c[0], c.v.c[1], c.v.c[2], paint.opacity);
+ double red = 0;
+ double green = 0;
+ double blue = 0;
+ double alpha = 0;
+ cairo_pattern_get_rgba(cpattern, &red, &green, &blue, &alpha);
+ }
+ break;
+ default:
+ break;
+ }
+ return cpattern;
+}
+
+bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
+{
+ if (!fill_pattern) fill_pattern = preparePaint(dc, paintbox, pattern, fill);
+ return fill_pattern != nullptr;
+}
+
+bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
+{
+ if (!stroke_pattern) stroke_pattern = preparePaint(dc, paintbox, pattern, stroke);
+ return stroke_pattern != nullptr;
+}
+
+bool NRStyle::prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
+{
+ if (!text_decoration_fill_pattern) text_decoration_fill_pattern = preparePaint(dc, paintbox, pattern, text_decoration_fill);
+ return text_decoration_fill_pattern != nullptr;
+}
+
+bool NRStyle::prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
+{
+ if (!text_decoration_stroke_pattern) text_decoration_stroke_pattern = preparePaint(dc, paintbox, pattern, text_decoration_stroke);
+ return text_decoration_stroke_pattern != nullptr;
+}
+
+void NRStyle::applyFill(Inkscape::DrawingContext &dc)
+{
+ dc.setSource(fill_pattern);
+ dc.setFillRule(fill_rule);
+}
+
+void NRStyle::applyTextDecorationFill(Inkscape::DrawingContext &dc)
+{
+ dc.setSource(text_decoration_fill_pattern);
+ // Fill rule does not matter, no intersections.
+}
+
+void NRStyle::applyStroke(Inkscape::DrawingContext &dc)
+{
+ dc.setSource(stroke_pattern);
+ dc.setLineWidth(stroke_width);
+ dc.setLineCap(line_cap);
+ dc.setLineJoin(line_join);
+ dc.setMiterLimit(miter_limit);
+ cairo_set_dash(dc.raw(), dash, n_dash, dash_offset); // fixme
+}
+
+void NRStyle::applyTextDecorationStroke(Inkscape::DrawingContext &dc)
+{
+ dc.setSource(text_decoration_stroke_pattern);
+ dc.setLineWidth(text_decoration_stroke_width);
+ dc.setLineCap(CAIRO_LINE_CAP_BUTT);
+ dc.setLineJoin(CAIRO_LINE_JOIN_MITER);
+ dc.setMiterLimit(miter_limit);
+ cairo_set_dash(dc.raw(), nullptr, 0, 0.0); // fixme (no dash)
+}
+
+void NRStyle::update()
+{
+ // force pattern update
+ if (fill_pattern) cairo_pattern_destroy(fill_pattern);
+ if (stroke_pattern) cairo_pattern_destroy(stroke_pattern);
+ if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern);
+ if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern);
+ fill_pattern = nullptr;
+ stroke_pattern = nullptr;
+ text_decoration_fill_pattern = nullptr;
+ text_decoration_stroke_pattern = nullptr;
+}
+
+/*
+ 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/display/nr-style.h b/src/display/nr-style.h
new file mode 100644
index 0000000..f9d7fa5
--- /dev/null
+++ b/src/display/nr-style.h
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Style information for rendering.
+ * Only used by classes DrawingShape and DrawingText
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_NR_ARENA_STYLE_H
+#define SEEN_INKSCAPE_DISPLAY_NR_ARENA_STYLE_H
+
+#include <cairo.h>
+#include <2geom/rect.h>
+#include "color.h"
+
+class SPPaintServer;
+class SPStyle;
+class SPIPaint;
+
+namespace Inkscape {
+class DrawingContext;
+class DrawingPattern;
+}
+
+struct NRStyle {
+ NRStyle();
+ ~NRStyle();
+
+ enum PaintType {
+ PAINT_NONE,
+ PAINT_COLOR,
+ PAINT_SERVER
+ };
+
+ class Paint {
+ public:
+ Paint() : type(PAINT_NONE), color(0), server(nullptr), opacity(1.0) {}
+ ~Paint() { clear(); }
+
+ PaintType type;
+ SPColor color;
+ SPPaintServer *server;
+ float opacity;
+
+ void clear();
+ void set(SPColor const &c);
+ void set(SPPaintServer *ps);
+ void set(const SPIPaint* paint);
+ };
+
+ void set(SPStyle *style, SPStyle *context_style = nullptr);
+ cairo_pattern_t* preparePaint(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern, Paint& paint);
+ bool prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ void applyFill(Inkscape::DrawingContext &dc);
+ void applyStroke(Inkscape::DrawingContext &dc);
+ void applyTextDecorationFill(Inkscape::DrawingContext &dc);
+ void applyTextDecorationStroke(Inkscape::DrawingContext &dc);
+ void update();
+
+ Paint fill;
+ Paint stroke;
+ float stroke_width;
+ float miter_limit;
+ unsigned int n_dash;
+ double *dash;
+ float dash_offset;
+ cairo_fill_rule_t fill_rule;
+ cairo_line_cap_t line_cap;
+ cairo_line_join_t line_join;
+
+ cairo_pattern_t *fill_pattern;
+ cairo_pattern_t *stroke_pattern;
+ cairo_pattern_t *text_decoration_fill_pattern;
+ cairo_pattern_t *text_decoration_stroke_pattern;
+
+ enum PaintOrderType {
+ PAINT_ORDER_NORMAL,
+ PAINT_ORDER_FILL,
+ PAINT_ORDER_STROKE,
+ PAINT_ORDER_MARKER
+ };
+
+ static const size_t PAINT_ORDER_LAYERS = 3;
+ PaintOrderType paint_order_layer[PAINT_ORDER_LAYERS];
+
+ enum TextDecorationLine {
+ TEXT_DECORATION_LINE_CLEAR = 0x00,
+ TEXT_DECORATION_LINE_SET = 0x01,
+ TEXT_DECORATION_LINE_INHERIT = 0x02,
+ TEXT_DECORATION_LINE_UNDERLINE = 0x04,
+ TEXT_DECORATION_LINE_OVERLINE = 0x08,
+ TEXT_DECORATION_LINE_LINETHROUGH = 0x10,
+ TEXT_DECORATION_LINE_BLINK = 0x20
+ };
+
+ enum TextDecorationStyle {
+ TEXT_DECORATION_STYLE_CLEAR = 0x00,
+ TEXT_DECORATION_STYLE_SET = 0x01,
+ TEXT_DECORATION_STYLE_INHERIT = 0x02,
+ TEXT_DECORATION_STYLE_SOLID = 0x04,
+ TEXT_DECORATION_STYLE_ISDOUBLE = 0x08,
+ TEXT_DECORATION_STYLE_DOTTED = 0x10,
+ TEXT_DECORATION_STYLE_DASHED = 0x20,
+ TEXT_DECORATION_STYLE_WAVY = 0x40
+ };
+
+ int text_decoration_line;
+ int text_decoration_style;
+ Paint text_decoration_fill;
+ Paint text_decoration_stroke;
+ float text_decoration_stroke_width;
+
+ // These are the same as in style.h
+ float phase_length;
+ bool tspan_line_start;
+ bool tspan_line_end;
+ float tspan_width;
+ float ascender;
+ float descender;
+ float underline_thickness;
+ float underline_position;
+ float line_through_thickness;
+ float line_through_position;
+ float font_size;
+
+ int text_direction;
+};
+
+#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/display/nr-svgfonts.cpp b/src/display/nr-svgfonts.cpp
new file mode 100644
index 0000000..e9679f3
--- /dev/null
+++ b/src/display/nr-svgfonts.cpp
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVGFonts rendering implementation
+ *
+ * Authors:
+ * Felipe C. da S. Sanches <juca@members.fsf.org>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2008 Felipe C. da S. Sanches
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ * Read the file 'COPYING' for more information.
+ */
+
+#include <2geom/pathvector.h>
+#include <2geom/transforms.h>
+#include <cairo.h>
+#include <vector>
+
+#include "svg/svg.h"
+#include "display/cairo-utils.h"
+#include "display/nr-svgfonts.h"
+#include "display/nr-svgfonts.h"
+#include "display/curve.h"
+
+#include "xml/repr.h"
+
+#include "object/sp-path.h"
+#include "object/sp-object-group.h"
+#include "object/sp-use.h"
+#include "object/sp-use-reference.h"
+#include "object/sp-font-face.h"
+#include "object/sp-glyph.h"
+#include "object/sp-missing-glyph.h"
+#include "object/sp-font.h"
+#include "object/sp-glyph-kerning.h"
+
+// ************************//
+// UserFont Implementation //
+// ************************//
+
+// I wrote this binding code because Cairomm does not yet support userfonts. I have moved this code to cairomm and sent them a patch.
+// Once Cairomm incorporate the UserFonts binding, this code should be removed from inkscape and Cairomm API should be used.
+
+static cairo_user_data_key_t key;
+
+static cairo_status_t font_init_cb (cairo_scaled_font_t *scaled_font,
+ cairo_t * /*cairo*/, cairo_font_extents_t *metrics){
+ cairo_font_face_t* face = cairo_scaled_font_get_font_face(scaled_font);
+ SvgFont* instance = static_cast<SvgFont*>(cairo_font_face_get_user_data(face, &key));
+ return instance->scaled_font_init(scaled_font, metrics);
+}
+
+static cairo_status_t font_text_to_glyphs_cb ( cairo_scaled_font_t *scaled_font,
+ const char *utf8,
+ int utf8_len,
+ cairo_glyph_t **glyphs,
+ int *num_glyphs,
+ cairo_text_cluster_t **clusters,
+ int *num_clusters,
+ cairo_text_cluster_flags_t *flags){
+ cairo_font_face_t* face = cairo_scaled_font_get_font_face(scaled_font);
+ SvgFont* instance = static_cast<SvgFont*>(cairo_font_face_get_user_data(face, &key));
+ return instance->scaled_font_text_to_glyphs(scaled_font, utf8, utf8_len, glyphs, num_glyphs, clusters, num_clusters, flags);
+}
+
+static cairo_status_t font_render_glyph_cb (cairo_scaled_font_t *scaled_font,
+ unsigned long glyph,
+ cairo_t *cr,
+ cairo_text_extents_t *metrics){
+ cairo_font_face_t* face = cairo_scaled_font_get_font_face(scaled_font);
+ SvgFont* instance = static_cast<SvgFont*>(cairo_font_face_get_user_data(face, &key));
+ return instance->scaled_font_render_glyph(scaled_font, glyph, cr, metrics);
+}
+
+UserFont::UserFont(SvgFont* instance){
+ this->face = cairo_user_font_face_create ();
+ cairo_user_font_face_set_init_func (this->face, font_init_cb);
+ cairo_user_font_face_set_render_glyph_func (this->face, font_render_glyph_cb);
+ cairo_user_font_face_set_text_to_glyphs_func(this->face, font_text_to_glyphs_cb);
+
+ cairo_font_face_set_user_data (this->face, &key, (void*)instance, (cairo_destroy_func_t) nullptr);
+}
+
+//******************************//
+// SvgFont class Implementation //
+//******************************//
+SvgFont::SvgFont(SPFont* spfont){
+ this->font = spfont;
+ this->missingglyph = nullptr;
+ this->userfont = nullptr;
+}
+
+cairo_status_t
+SvgFont::scaled_font_init (cairo_scaled_font_t */*scaled_font*/,
+ cairo_font_extents_t */*metrics*/)
+{
+//TODO
+// metrics->ascent = .75;
+// metrics->descent = .25;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+unsigned int size_of_substring(const char* substring, gchar* str){
+ const gchar* original_substring = substring;
+
+ while((g_utf8_get_char(substring)==g_utf8_get_char(str)) && g_utf8_get_char(substring) != 0 && g_utf8_get_char(str) != 0){
+ substring = g_utf8_next_char(substring);
+ str = g_utf8_next_char(str);
+ }
+ if (g_utf8_get_char(substring)==0)
+ return substring - original_substring;
+ else
+ return 0;
+}
+
+
+namespace {
+
+//TODO: in these functions, verify what happens when using unicode strings.
+
+bool MatchVKerningRule(SPVkern const *vkern,
+ SPGlyph *glyph,
+ char const *previous_unicode,
+ gchar const *previous_glyph_name)
+{
+ bool value = (vkern->u1->contains(previous_unicode[0])
+ || vkern->g1->contains(previous_glyph_name))
+ && (vkern->u2->contains(glyph->unicode[0])
+ || vkern->g2->contains(glyph->glyph_name.c_str()));
+
+ return value;
+}
+
+bool MatchHKerningRule(SPHkern const *hkern,
+ SPGlyph *glyph,
+ char const *previous_unicode,
+ gchar const *previous_glyph_name)
+{
+ bool value = (hkern->u1->contains(previous_unicode[0])
+ || hkern->g1->contains(previous_glyph_name))
+ && (hkern->u2->contains(glyph->unicode[0])
+ || hkern->g2->contains(glyph->glyph_name.c_str()));
+
+ return value;
+}
+
+} // namespace
+
+cairo_status_t
+SvgFont::scaled_font_text_to_glyphs (cairo_scaled_font_t */*scaled_font*/,
+ const char *utf8,
+ int /*utf8_len*/,
+ cairo_glyph_t **glyphs,
+ int *num_glyphs,
+ cairo_text_cluster_t **/*clusters*/,
+ int */*num_clusters*/,
+ cairo_text_cluster_flags_t */*flags*/)
+{
+ //This function receives a text string to be rendered. It then defines what is the sequence of glyphs that
+ // is used to properly render this string. It also defines the respective coordinates of each glyph. Thus, it
+ // has to read the attributes of the SVGFont hkern and vkern nodes in order to adjust the glyph kerning.
+ //It also determines the usage of the missing-glyph in portions of the string that does not match any of the declared glyphs.
+
+ unsigned long i;
+ int count = 0;
+ gchar* _utf8 = (gchar*) utf8;
+ unsigned int len;
+
+ bool missing;
+ //First we find out what's the number of glyphs needed.
+ while(g_utf8_get_char(_utf8)){
+ missing = true;
+ for (i=0; i < (unsigned long) this->glyphs.size(); i++){
+ if ( (len = size_of_substring(this->glyphs[i]->unicode.c_str(), _utf8)) ){
+ //TODO: store this cluster
+ _utf8+=len;
+ count++;
+ missing=false;
+ break;
+ }
+ }
+ if (missing){
+ //TODO: store this cluster
+ _utf8++;
+ count++;
+ }
+ }
+
+
+ //We use that info to allocate memory for the glyphs
+ *glyphs = (cairo_glyph_t*) malloc(count*sizeof(cairo_glyph_t));
+
+ char* previous_unicode = nullptr; //This is used for kerning
+ gchar* previous_glyph_name = nullptr; //This is used for kerning
+
+ count=0;
+ double x=0, y=0;//These vars store the position of the glyph within the rendered string
+ bool is_horizontal_text = true; //TODO
+ _utf8 = (char*) utf8;
+
+ double font_height = units_per_em();
+ while(g_utf8_get_char(_utf8)){
+ len = 0;
+ for (i=0; i < (unsigned long) this->glyphs.size(); i++){
+ //check whether is there a glyph declared on the SVG document
+ // that matches with the text string in its current position
+ if ( (len = size_of_substring(this->glyphs[i]->unicode.c_str(), _utf8)) ){
+ for(auto& node: font->children) {
+ if (!previous_unicode) {
+ break;
+ }
+ //apply glyph kerning if appropriate
+ SPHkern *hkern = dynamic_cast<SPHkern *>(&node);
+ if (hkern && is_horizontal_text &&
+ MatchHKerningRule(hkern, this->glyphs[i], previous_unicode, previous_glyph_name) ){
+ x -= (hkern->k / font_height);
+ }
+ SPVkern *vkern = dynamic_cast<SPVkern *>(&node);
+ if (vkern && !is_horizontal_text &&
+ MatchVKerningRule(vkern, this->glyphs[i], previous_unicode, previous_glyph_name) ){
+ y -= (vkern->k / font_height);
+ }
+ }
+ previous_unicode = const_cast<char*>(this->glyphs[i]->unicode.c_str());//used for kerning checking
+ previous_glyph_name = const_cast<char*>(this->glyphs[i]->glyph_name.c_str());//used for kerning checking
+ (*glyphs)[count].index = i;
+ (*glyphs)[count].x = x;
+ (*glyphs)[count++].y = y;
+
+ //advance glyph coordinates:
+ if (is_horizontal_text) {
+ if (this->glyphs[i]->horiz_adv_x != 0) {
+ x+=(this->glyphs[i]->horiz_adv_x/font_height);
+ } else {
+ x+=(this->font->horiz_adv_x/font_height);
+ }
+ } else {
+ y+=(this->font->vert_adv_y/font_height);
+ }
+ _utf8+=len; //advance 'len' bytes in our string pointer
+ //continue;
+ goto raptorz;
+ }
+ }
+ raptorz:
+ if (len==0){
+ (*glyphs)[count].index = i;
+ (*glyphs)[count].x = x;
+ (*glyphs)[count++].y = y;
+
+ //advance glyph coordinates:
+ if (is_horizontal_text) x+=(this->font->horiz_adv_x/font_height);//TODO: use here the height of the font
+ else y+=(this->font->vert_adv_y/font_height);//TODO: use here the "height" of the font
+
+ _utf8 = g_utf8_next_char(_utf8); //advance 1 char in our string pointer
+ }
+ }
+ *num_glyphs = count;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+void
+SvgFont::render_glyph_path(cairo_t* cr, Geom::PathVector* pathv){
+ if (!pathv->empty()){
+ //This glyph has a path description on its d attribute, so we render it:
+ cairo_new_path(cr);
+
+ //adjust scale of the glyph
+ Geom::Scale s(1.0/units_per_em());
+ Geom::Rect area( Geom::Point(0,0), Geom::Point(1,1) ); //I need help here! (reaction: note that the 'area' parameter is an *optional* rect, so you can pass an empty Geom::OptRect() )
+
+ feed_pathvector_to_cairo (cr, *pathv, s, area, false, 0);
+ cairo_fill(cr);
+ }
+}
+
+void
+SvgFont::glyph_modified(SPObject* /* blah */, unsigned int /* bleh */){
+ this->refresh();
+ //TODO: update rendering on svgfonts preview widget (in the svg fonts dialog)
+}
+
+Geom::PathVector
+SvgFont::flip_coordinate_system(SPFont* spfont, Geom::PathVector pathv){
+ double units_per_em = 1024;
+ for(auto& obj: spfont->children) {
+ if (dynamic_cast<SPFontFace *>(&obj)) {
+ //XML Tree being directly used here while it shouldn't be.
+ sp_repr_get_double(obj.getRepr(), "units_per_em", &units_per_em);
+ }
+ }
+
+ double baseline_offset = units_per_em - spfont->horiz_origin_y;
+
+ //This matrix flips y-axis and places the origin at baseline
+ Geom::Affine m(Geom::Coord(1),Geom::Coord(0),Geom::Coord(0),Geom::Coord(-1),Geom::Coord(0),Geom::Coord(baseline_offset));
+ return pathv*m;
+}
+
+cairo_status_t
+SvgFont::scaled_font_render_glyph (cairo_scaled_font_t */*scaled_font*/,
+ unsigned long glyph,
+ cairo_t *cr,
+ cairo_text_extents_t */*metrics*/)
+{
+ // This method does the actual rendering of glyphs.
+
+ // We have glyphs.size() glyphs and possibly one missing-glyph declared on this SVG document
+ // The id of the missing-glyph is always equal to glyphs.size()
+ // All the other glyphs have ids ranging from 0 to glyphs.size()-1
+
+ if (glyph > this->glyphs.size()) return CAIRO_STATUS_SUCCESS;//TODO: this is an error!
+
+ SPObject *node = nullptr;
+ if (glyph == glyphs.size()){
+ if (!missingglyph) {
+ return CAIRO_STATUS_SUCCESS;
+ }
+ node = missingglyph;
+ } else {
+ node = glyphs[glyph];
+ }
+
+ if (!dynamic_cast<SPGlyph *>(node) && !dynamic_cast<SPMissingGlyph *>(node)) {
+ return CAIRO_STATUS_SUCCESS; // FIXME: is this the right code to return?
+ }
+
+ SPFont* spfont = dynamic_cast<SPFont *>(node->parent);
+ if (!spfont) {
+ return CAIRO_STATUS_SUCCESS; // FIXME: is this the right code to return?
+ }
+
+ //glyphs can be described by arbitrary SVG declared in the childnodes of a glyph node
+ // or using the d attribute of a glyph node.
+ // pathv stores the path description from the d attribute:
+ Geom::PathVector pathv;
+
+ SPGlyph *glyphNode = dynamic_cast<SPGlyph *>(node);
+ if (glyphNode && glyphNode->d) {
+ pathv = sp_svg_read_pathv(glyphNode->d);
+ pathv = flip_coordinate_system(spfont, pathv);
+ render_glyph_path(cr, &pathv);
+ } else {
+ SPMissingGlyph *missing = dynamic_cast<SPMissingGlyph *>(node);
+ if (missing && missing->d) {
+ pathv = sp_svg_read_pathv(missing->d);
+ pathv = flip_coordinate_system(spfont, pathv);
+ render_glyph_path(cr, &pathv);
+ }
+ }
+
+ if (node->hasChildren()){
+ //render the SVG described on this glyph's child nodes.
+ for(auto& child: node->children) {
+ {
+ SPPath *path = dynamic_cast<SPPath *>(&child);
+ if (path) {
+ pathv = path->_curve->get_pathvector();
+ pathv = flip_coordinate_system(spfont, pathv);
+ render_glyph_path(cr, &pathv);
+ }
+ }
+ if (dynamic_cast<SPObjectGroup *>(&child)) {
+ g_warning("TODO: svgfonts: render OBJECTGROUP");
+ }
+ SPUse *use = dynamic_cast<SPUse *>(&child);
+ if (use) {
+ SPItem* item = use->ref->getObject();
+ SPPath *path = dynamic_cast<SPPath *>(item);
+ if (path) {
+ SPShape *shape = dynamic_cast<SPShape *>(item);
+ g_assert(shape != nullptr);
+ pathv = shape->_curve->get_pathvector();
+ pathv = flip_coordinate_system(spfont, pathv);
+ this->render_glyph_path(cr, &pathv);
+ }
+
+ glyph_modified_connection = item->connectModified(sigc::mem_fun(*this, &SvgFont::glyph_modified));
+ }
+ }
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_font_face_t*
+SvgFont::get_font_face(){
+ if (!this->userfont) {
+ for(auto& node: font->children) {
+ SPGlyph *glyph = dynamic_cast<SPGlyph *>(&node);
+ if (glyph) {
+ glyphs.push_back(glyph);
+ }
+ SPMissingGlyph *missing = dynamic_cast<SPMissingGlyph *>(&node);
+ if (missing) {
+ missingglyph = missing;
+ }
+ }
+ this->userfont = new UserFont(this);
+ }
+ return this->userfont->face;
+}
+
+void SvgFont::refresh(){
+ this->glyphs.clear();
+ delete this->userfont;
+ this->userfont = nullptr;
+}
+
+double SvgFont::units_per_em() {
+ double units_per_em = 1024;
+ for (auto& obj: font->children) {
+ if (dynamic_cast<SPFontFace *>(&obj)) {
+ //XML Tree being directly used here while it shouldn't be.
+ sp_repr_get_double(obj.getRepr(), "units-per-em", &units_per_em);
+ }
+ }
+ if (units_per_em <= 0.0) {
+ units_per_em = 1024;
+ }
+ return units_per_em;
+}
+
+/*
+ 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/display/nr-svgfonts.h b/src/display/nr-svgfonts.h
new file mode 100644
index 0000000..48185b9
--- /dev/null
+++ b/src/display/nr-svgfonts.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef NR_SVGFONTS_H_SEEN
+#define NR_SVGFONTS_H_SEEN
+/*
+ * SVGFonts rendering headear
+ *
+ * Authors:
+ * Felipe C. da S. Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2008 Felipe C. da S. Sanches
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ * Read the file 'COPYING' for more information.
+ */
+
+#include <cairo.h>
+#include <sigc++/connection.h>
+
+class SvgFont;
+class SPFont;
+class SPGlyph;
+class SPMissingGlyph;
+
+extern "C" { typedef struct _GdkEventExpose GdkEventExpose; }
+
+namespace Gtk {
+class Widget;
+}
+
+class UserFont {
+public:
+ UserFont(SvgFont* instance);
+ cairo_font_face_t* face;
+};
+
+class SvgFont {
+public:
+ SvgFont(SPFont* spfont);
+ void refresh();
+ cairo_font_face_t* get_font_face();
+ cairo_status_t scaled_font_init (cairo_scaled_font_t *scaled_font, cairo_font_extents_t *metrics);
+ cairo_status_t scaled_font_text_to_glyphs (cairo_scaled_font_t *scaled_font, const char *utf8, int utf8_len, cairo_glyph_t **glyphs, int *num_glyphs, cairo_text_cluster_t **clusters, int *num_clusters, cairo_text_cluster_flags_t *flags);
+ cairo_status_t scaled_font_render_glyph (cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics);
+
+ Geom::PathVector flip_coordinate_system(SPFont* spfont, Geom::PathVector pathv);
+ void render_glyph_path(cairo_t* cr, Geom::PathVector* pathv);
+ void glyph_modified(SPObject *, unsigned int);
+
+private:
+ SPFont* font;
+ UserFont* userfont;
+ std::vector<SPGlyph*> glyphs;
+ SPMissingGlyph* missingglyph;
+ sigc::connection glyph_modified_connection;
+
+ double units_per_em();
+ //bool drawing_expose_cb (Gtk::Widget *widget, GdkEventExpose *event, void* data);
+};
+
+#endif //#ifndef NR_SVGFONTS_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 :
diff --git a/src/display/rendermode.h b/src/display/rendermode.h
new file mode 100644
index 0000000..66130de
--- /dev/null
+++ b/src/display/rendermode.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * RenderMode enumeration.
+ *
+ * Trivially public domain.
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_RENDERMODE_H
+#define SEEN_INKSCAPE_DISPLAY_RENDERMODE_H
+
+namespace Inkscape {
+
+enum RenderMode {
+ RENDERMODE_NORMAL,
+ RENDERMODE_NO_FILTERS,
+ RENDERMODE_OUTLINE,
+ RENDERMODE_VISIBLE_HAIRLINES
+};
+
+enum ColorMode {
+ COLORMODE_NORMAL,
+ COLORMODE_GRAYSCALE,
+ COLORMODE_PRINT_COLORS_PREVIEW
+};
+
+}
+
+#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/display/snap-indicator.cpp b/src/display/snap-indicator.cpp
new file mode 100644
index 0000000..cb798e6
--- /dev/null
+++ b/src/display/snap-indicator.cpp
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Provides a class that shows a temporary indicator on the canvas of where the snap was, and what kind of snap
+ *
+ * Authors:
+ * Johan Engelen
+ * Diederik van Lierop
+ *
+ * Copyright (C) Johan Engelen 2009 <j.b.c.engelen@utwente.nl>
+ * Copyright (C) Diederik van Lierop 2010 - 2012 <mail@diedenrezi.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/snap-indicator.h"
+
+#include "desktop.h"
+
+#include "display/sodipodi-ctrl.h"
+#include "display/sodipodi-ctrlrect.h"
+#include "display/canvas-text.h"
+#include "display/sp-canvas-util.h"
+#include "knot.h"
+#include "preferences.h"
+#include <glibmm/i18n.h>
+#include "ui/tools-switch.h"
+#include "enums.h"
+
+namespace Inkscape {
+namespace Display {
+
+SnapIndicator::SnapIndicator(SPDesktop * desktop)
+ : _snaptarget(nullptr),
+ _snaptarget_tooltip(nullptr),
+ _snaptarget_bbox(nullptr),
+ _snapsource(nullptr),
+ _snaptarget_is_presnap(false),
+ _desktop(desktop)
+{
+}
+
+SnapIndicator::~SnapIndicator()
+{
+ // remove item that might be present
+ remove_snaptarget();
+ remove_snapsource();
+}
+
+void
+SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap)
+{
+ remove_snaptarget(); //only display one snaptarget at a time
+
+ g_assert(_desktop != nullptr);
+
+ if (!p.getSnapped()) {
+ return; // If we haven't snapped, then it is of no use to draw a snapindicator
+ }
+
+ if (p.getTarget() == SNAPTARGET_CONSTRAINT) {
+ // This is not a real snap, although moving along the constraint did affect the mouse pointer's position.
+ // Maybe we should only show a snap indicator when the user explicitly asked for a constraint by pressing ctrl?
+ // We should not show a snap indicator when stretching a selection box, which is also constrained. That would be
+ // too much information.
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool value = prefs->getBool("/options/snapindicator/value", true);
+
+ if (value) {
+ // TRANSLATORS: undefined target for snapping
+ gchar *target_name = _("UNDEFINED");
+ switch (p.getTarget()) {
+ case SNAPTARGET_UNDEFINED:
+ target_name = _("UNDEFINED");
+ g_warning("Snap target has not been specified");
+ break;
+ case SNAPTARGET_GRID:
+ target_name = _("grid line");
+ break;
+ case SNAPTARGET_GRID_INTERSECTION:
+ target_name = _("grid intersection");
+ break;
+ case SNAPTARGET_GRID_PERPENDICULAR:
+ target_name = _("grid line (perpendicular)");
+ break;
+ case SNAPTARGET_GUIDE:
+ target_name = _("guide");
+ break;
+ case SNAPTARGET_GUIDE_INTERSECTION:
+ target_name = _("guide intersection");
+ break;
+ case SNAPTARGET_GUIDE_ORIGIN:
+ target_name = _("guide origin");
+ break;
+ case SNAPTARGET_GUIDE_PERPENDICULAR:
+ target_name = _("guide (perpendicular)");
+ break;
+ case SNAPTARGET_GRID_GUIDE_INTERSECTION:
+ target_name = _("grid-guide intersection");
+ break;
+ case SNAPTARGET_NODE_CUSP:
+ target_name = _("cusp node");
+ break;
+ case SNAPTARGET_NODE_SMOOTH:
+ target_name = _("smooth node");
+ break;
+ case SNAPTARGET_PATH:
+ target_name = _("path");
+ break;
+ case SNAPTARGET_PATH_PERPENDICULAR:
+ target_name = _("path (perpendicular)");
+ break;
+ case SNAPTARGET_PATH_TANGENTIAL:
+ target_name = _("path (tangential)");
+ break;
+ case SNAPTARGET_PATH_INTERSECTION:
+ target_name = _("path intersection");
+ break;
+ case SNAPTARGET_PATH_GUIDE_INTERSECTION:
+ target_name = _("guide-path intersection");
+ break;
+ case SNAPTARGET_PATH_CLIP:
+ target_name = _("clip-path");
+ break;
+ case SNAPTARGET_PATH_MASK:
+ target_name = _("mask-path");
+ break;
+ case SNAPTARGET_BBOX_CORNER:
+ target_name = _("bounding box corner");
+ break;
+ case SNAPTARGET_BBOX_EDGE:
+ target_name = _("bounding box side");
+ break;
+ case SNAPTARGET_PAGE_BORDER:
+ target_name = _("page border");
+ break;
+ case SNAPTARGET_LINE_MIDPOINT:
+ target_name = _("line midpoint");
+ break;
+ case SNAPTARGET_OBJECT_MIDPOINT:
+ target_name = _("object midpoint");
+ break;
+ case SNAPTARGET_ROTATION_CENTER:
+ target_name = _("object rotation center");
+ break;
+ case SNAPTARGET_BBOX_EDGE_MIDPOINT:
+ target_name = _("bounding box side midpoint");
+ break;
+ case SNAPTARGET_BBOX_MIDPOINT:
+ target_name = _("bounding box midpoint");
+ break;
+ case SNAPTARGET_PAGE_CORNER:
+ target_name = _("page corner");
+ break;
+ case SNAPTARGET_ELLIPSE_QUADRANT_POINT:
+ target_name = _("quadrant point");
+ break;
+ case SNAPTARGET_RECT_CORNER:
+ case SNAPTARGET_IMG_CORNER:
+ target_name = _("corner");
+ break;
+ case SNAPTARGET_TEXT_ANCHOR:
+ target_name = _("text anchor");
+ break;
+ case SNAPTARGET_TEXT_BASELINE:
+ target_name = _("text baseline");
+ break;
+ case SNAPTARGET_CONSTRAINED_ANGLE:
+ target_name = _("constrained angle");
+ break;
+ case SNAPTARGET_CONSTRAINT:
+ target_name = _("constraint");
+ break;
+ default:
+ g_warning("Snap target not in SnapTargetType enum");
+ break;
+ }
+
+ gchar *source_name = _("UNDEFINED");
+ switch (p.getSource()) {
+ case SNAPSOURCE_UNDEFINED:
+ source_name = _("UNDEFINED");
+ g_warning("Snap source has not been specified");
+ break;
+ case SNAPSOURCE_BBOX_CORNER:
+ source_name = _("Bounding box corner");
+ break;
+ case SNAPSOURCE_BBOX_MIDPOINT:
+ source_name = _("Bounding box midpoint");
+ break;
+ case SNAPSOURCE_BBOX_EDGE_MIDPOINT:
+ source_name = _("Bounding box side midpoint");
+ break;
+ case SNAPSOURCE_NODE_SMOOTH:
+ source_name = _("Smooth node");
+ break;
+ case SNAPSOURCE_NODE_CUSP:
+ source_name = _("Cusp node");
+ break;
+ case SNAPSOURCE_LINE_MIDPOINT:
+ source_name = _("Line midpoint");
+ break;
+ case SNAPSOURCE_OBJECT_MIDPOINT:
+ source_name = _("Object midpoint");
+ break;
+ case SNAPSOURCE_ROTATION_CENTER:
+ source_name = _("Object rotation center");
+ break;
+ case SNAPSOURCE_NODE_HANDLE:
+ case SNAPSOURCE_OTHER_HANDLE:
+ source_name = _("Handle");
+ break;
+ case SNAPSOURCE_PATH_INTERSECTION:
+ source_name = _("Path intersection");
+ break;
+ case SNAPSOURCE_GUIDE:
+ source_name = _("Guide");
+ break;
+ case SNAPSOURCE_GUIDE_ORIGIN:
+ source_name = _("Guide origin");
+ break;
+ case SNAPSOURCE_CONVEX_HULL_CORNER:
+ source_name = _("Convex hull corner");
+ break;
+ case SNAPSOURCE_ELLIPSE_QUADRANT_POINT:
+ source_name = _("Quadrant point");
+ break;
+ case SNAPSOURCE_RECT_CORNER:
+ case SNAPSOURCE_IMG_CORNER:
+ source_name = _("Corner");
+ break;
+ case SNAPSOURCE_TEXT_ANCHOR:
+ source_name = _("Text anchor");
+ break;
+ case SNAPSOURCE_GRID_PITCH:
+ source_name = _("Multiple of grid spacing");
+ break;
+ default:
+ g_warning("Snap source not in SnapSourceType enum");
+ break;
+ }
+ //std::cout << "Snapped " << source_name << " to " << target_name << std::endl;
+
+ remove_snapsource(); // Don't set both the source and target indicators, as these will overlap
+
+ // Display the snap indicator (i.e. the cross)
+ SPCanvasItem * canvasitem = nullptr;
+ canvasitem = sp_canvas_item_new(_desktop->getTempGroup(),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 11,
+ "stroked", TRUE,
+ "stroke_color", pre_snap ? 0x7f7f7fff : 0xff0000ff,
+ "mode", SP_KNOT_MODE_XOR,
+ "shape", SP_KNOT_SHAPE_CROSS,
+ NULL );
+
+ double timeout_val = prefs->getDouble("/options/snapindicatorpersistence/value", 2.0);
+ if (timeout_val < 0.1) {
+ timeout_val = 0.1; // a zero value would mean infinite persistence (i.e. until new snap occurs)
+ // Besides, negatives values would ....?
+ }
+
+
+ // The snap indicator will be deleted after some time-out, and sp_canvas_item_dispose
+ // will be called. This will set canvas->current_item to NULL if the snap indicator was
+ // the current item, after which any events will go to the root handler instead of any
+ // item handler. Dragging an object which has just snapped might therefore not be possible
+ // without selecting / repicking it again. To avoid this, we make sure here that the
+ // snap indicator will never be picked, and will therefore never be the current item.
+ // Reported bugs:
+ // - scrolling when hovering above a pre-snap indicator won't work (for example)
+ // (https://bugs.launchpad.net/inkscape/+bug/522335/comments/8)
+ // - dragging doesn't work without repicking
+ // (https://bugs.launchpad.net/inkscape/+bug/1420301/comments/15)
+ SP_CTRL(canvasitem)->pickable = false;
+
+ SP_CTRL(canvasitem)->moveto(p.getPoint());
+ _snaptarget = _desktop->add_temporary_canvasitem(canvasitem, timeout_val*1000.0);
+ _snaptarget_is_presnap = pre_snap;
+
+ // Display the tooltip, which reveals the type of snap source and the type of snap target
+ gchar *tooltip_str = nullptr;
+ if ( (p.getSource() != SNAPSOURCE_GRID_PITCH) && (p.getTarget() != SNAPTARGET_UNDEFINED) ) {
+ tooltip_str = g_strconcat(source_name, _(" to "), target_name, NULL);
+ } else if (p.getSource() != SNAPSOURCE_UNDEFINED) {
+ tooltip_str = g_strdup(source_name);
+ }
+ double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
+
+ if (tooltip_str) {
+ Geom::Point tooltip_pos = p.getPoint();
+ if (tools_isactive(_desktop, TOOLS_MEASURE)) {
+ // Make sure that the snap tooltips do not overlap the ones from the measure tool
+ tooltip_pos += _desktop->w2d(Geom::Point(0, -3*fontsize));
+ } else {
+ tooltip_pos += _desktop->w2d(Geom::Point(0, -2*fontsize));
+ }
+
+ SPCanvasItem *canvas_tooltip = sp_canvastext_new(_desktop->getTempGroup(), _desktop, tooltip_pos, tooltip_str);
+ sp_canvastext_set_fontsize(SP_CANVASTEXT(canvas_tooltip), fontsize);
+ SP_CANVASTEXT(canvas_tooltip)->pickable = false; // See the extensive comment above
+ SP_CANVASTEXT(canvas_tooltip)->rgba = 0xffffffff;
+ SP_CANVASTEXT(canvas_tooltip)->outline = false;
+ SP_CANVASTEXT(canvas_tooltip)->background = true;
+ if (pre_snap) {
+ SP_CANVASTEXT(canvas_tooltip)->rgba_background = 0x33337f40;
+ } else {
+ SP_CANVASTEXT(canvas_tooltip)->rgba_background = 0x33337f7f;
+ }
+ SP_CANVASTEXT(canvas_tooltip)->anchor_position = TEXT_ANCHOR_CENTER;
+ g_free(tooltip_str);
+
+ _snaptarget_tooltip = _desktop->add_temporary_canvasitem(canvas_tooltip, timeout_val*1000.0);
+ }
+
+ // Display the bounding box, if we snapped to one
+ Geom::OptRect const bbox = p.getTargetBBox();
+ if (bbox) {
+ SPCanvasItem* box = sp_canvas_item_new(_desktop->getTempGroup(),
+ SP_TYPE_CTRLRECT,
+ nullptr);
+
+ SP_CTRLRECT(box)->setRectangle(*bbox);
+ SP_CTRLRECT(box)->setColor(pre_snap ? 0x7f7f7fff : 0xff0000ff, false, 0);
+ SP_CTRLRECT(box)->setDashed(true);
+ SP_CTRLRECT(box)->pickable = false; // See the extensive comment above
+ sp_canvas_item_move_to_z(box, 0);
+ _snaptarget_bbox = _desktop->add_temporary_canvasitem(box, timeout_val*1000.0);
+ }
+ }
+}
+
+void
+SnapIndicator::remove_snaptarget(bool only_if_presnap)
+{
+ if (only_if_presnap && !_snaptarget_is_presnap) {
+ return;
+ }
+
+ if (_snaptarget) {
+ _desktop->remove_temporary_canvasitem(_snaptarget);
+ _snaptarget = nullptr;
+ _snaptarget_is_presnap = false;
+ }
+
+ if (_snaptarget_tooltip) {
+ _desktop->remove_temporary_canvasitem(_snaptarget_tooltip);
+ _snaptarget_tooltip = nullptr;
+ }
+
+ if (_snaptarget_bbox) {
+ _desktop->remove_temporary_canvasitem(_snaptarget_bbox);
+ _snaptarget_bbox = nullptr;
+ }
+
+}
+
+void
+SnapIndicator::set_new_snapsource(Inkscape::SnapCandidatePoint const &p)
+{
+ remove_snapsource();
+
+ g_assert(_desktop != nullptr); // If this fails, then likely setup() has not been called on the snap manager (see snap.cpp -> setup())
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool value = prefs->getBool("/options/snapindicator/value", true);
+
+ if (value) {
+ SPCanvasItem * canvasitem = sp_canvas_item_new( _desktop->getTempGroup(),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 7,
+ "stroked", TRUE,
+ "stroke_color", 0xff0000ff,
+ "mode", SP_KNOT_MODE_XOR,
+ "shape", SP_KNOT_SHAPE_CIRCLE,
+ NULL );
+
+ SP_CTRL(canvasitem)->moveto(p.getPoint());
+ _snapsource = _desktop->add_temporary_canvasitem(canvasitem, 1000);
+ }
+}
+
+void
+SnapIndicator::set_new_debugging_point(Geom::Point const &p)
+{
+ g_assert(_desktop != nullptr);
+ SPCanvasItem * canvasitem = sp_canvas_item_new( _desktop->getTempGroup(),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 11,
+ "fill_color", 0x00ff00ff,
+ "stroked", FALSE,
+ "mode", SP_KNOT_MODE_XOR,
+ "shape", SP_KNOT_SHAPE_DIAMOND,
+ NULL );
+
+ SP_CTRL(canvasitem)->moveto(p);
+ _debugging_points.push_back(_desktop->add_temporary_canvasitem(canvasitem, 5000));
+
+}
+
+void
+SnapIndicator::remove_snapsource()
+{
+ if (_snapsource) {
+ _desktop->remove_temporary_canvasitem(_snapsource);
+ _snapsource = nullptr;
+ }
+}
+
+void
+SnapIndicator::remove_debugging_points()
+{
+ for (std::list<TemporaryItem *>::const_iterator i = _debugging_points.begin(); i != _debugging_points.end(); ++i) {
+ _desktop->remove_temporary_canvasitem(*i);
+ }
+ _debugging_points.clear();
+}
+
+
+} //namespace Display
+} /* 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=4:softtabstop=4 :
diff --git a/src/display/snap-indicator.h b/src/display/snap-indicator.h
new file mode 100644
index 0000000..463acaf
--- /dev/null
+++ b/src/display/snap-indicator.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INKSCAPE_DISPLAY_SNAP_INDICATOR_H
+#define INKSCAPE_DISPLAY_SNAP_INDICATOR_H
+
+/**
+ * @file
+ * Provides a class that shows a temporary indicator on the canvas of where the snap was, and what kind of snap
+ */
+/*
+ * Authors:
+ * Johan Engelen
+ * Diederik van Lierop
+ *
+ * Copyright (C) Johan Engelen 2008 <j.b.c.engelen@utwente.nl>
+ * Copyright (C) Diederik van Lierop 2010 <mail@diedenrezi.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "snapped-point.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace Display {
+
+class TemporaryItem;
+
+class SnapIndicator {
+public:
+ SnapIndicator(SPDesktop *desktop);
+ virtual ~SnapIndicator();
+
+ void set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap = false);
+ void remove_snaptarget(bool only_if_presnap = false);
+
+ void set_new_snapsource(Inkscape::SnapCandidatePoint const &p);
+ void remove_snapsource();
+
+ void set_new_debugging_point(Geom::Point const &p);
+ void remove_debugging_points();
+
+protected:
+ TemporaryItem *_snaptarget;
+ TemporaryItem *_snaptarget_tooltip;
+ TemporaryItem *_snaptarget_bbox;
+ TemporaryItem *_snapsource;
+ std::list<TemporaryItem *> _debugging_points;
+ bool _snaptarget_is_presnap;
+ SPDesktop *_desktop;
+
+private:
+ SnapIndicator(const SnapIndicator&) = delete;
+ SnapIndicator& operator=(const SnapIndicator&) = delete;
+};
+
+} //namespace Display
+} //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 :
diff --git a/src/display/sodipodi-ctrl.cpp b/src/display/sodipodi-ctrl.cpp
new file mode 100644
index 0000000..09e34f7
--- /dev/null
+++ b/src/display/sodipodi-ctrl.cpp
@@ -0,0 +1,695 @@
+// 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.
+ */
+/*
+ * SPCtrl
+ *
+ * We render it by hand to reduce allocing/freeing svps & to get clean
+ * (non-aa) images
+ *
+ */
+
+#include <2geom/transforms.h>
+#include <2geom/line.h>
+#include "sp-canvas-util.h"
+#include "sodipodi-ctrl.h"
+#include "display/cairo-utils.h"
+#include "display/sp-canvas.h"
+
+enum {
+ ARG_0,
+ ARG_SHAPE,
+ ARG_MODE,
+ ARG_ANCHOR,
+ ARG_SIZE,
+ ARG_ANGLE,
+ ARG_FILLED,
+ ARG_FILL_COLOR,
+ ARG_STROKED,
+ ARG_STROKE_COLOR,
+ ARG_PIXBUF
+};
+
+static void sp_ctrl_destroy(SPCanvasItem *object);
+static void sp_ctrl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void sp_ctrl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void sp_ctrl_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static double sp_ctrl_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+
+G_DEFINE_TYPE(SPCtrl, sp_ctrl, SP_TYPE_CANVAS_ITEM);
+
+static void
+sp_ctrl_class_init (SPCtrlClass *klass)
+{
+ SPCanvasItemClass *item_class = SP_CANVAS_ITEM_CLASS(klass);
+ GObjectClass *g_object_class = (GObjectClass *) klass;
+
+ g_object_class->set_property = sp_ctrl_set_property;
+ g_object_class->get_property = sp_ctrl_get_property;
+
+ g_object_class_install_property (g_object_class,
+ ARG_SHAPE, g_param_spec_int ("shape", "shape", "Shape", 0, G_MAXINT, SP_CTRL_SHAPE_SQUARE, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_MODE, g_param_spec_int ("mode", "mode", "Mode", 0, G_MAXINT, SP_CTRL_MODE_COLOR, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_ANCHOR, g_param_spec_int ("anchor", "anchor", "Anchor", 0, G_MAXINT, SP_ANCHOR_CENTER, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_SIZE, g_param_spec_uint ("size", "size", "Size", 0, G_MAXUINT, 7, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_ANGLE, g_param_spec_double ("angle", "angle", "Angle", -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_FILLED, g_param_spec_boolean ("filled", "filled", "Filled", TRUE, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_FILL_COLOR, g_param_spec_int ("fill_color", "fill_color", "Fill Color", G_MININT, G_MAXINT, 0x000000ff, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_STROKED, g_param_spec_boolean ("stroked", "stroked", "Stroked", FALSE, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_STROKE_COLOR, g_param_spec_int ("stroke_color", "stroke_color", "Stroke Color", G_MININT, G_MAXINT, 0x000000ff, (GParamFlags) G_PARAM_READWRITE));
+ g_object_class_install_property (g_object_class,
+ ARG_PIXBUF, g_param_spec_pointer ("pixbuf", "pixbuf", "Pixbuf", (GParamFlags) G_PARAM_READWRITE));
+
+ item_class->destroy = sp_ctrl_destroy;
+ item_class->update = sp_ctrl_update;
+ item_class->render = sp_ctrl_render;
+ item_class->point = sp_ctrl_point;
+}
+
+static void
+sp_ctrl_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+
+ SPCanvasItem *item;
+ SPCtrl *ctrl;
+ GdkPixbuf * pixbuf = nullptr;
+
+ item = SP_CANVAS_ITEM (object);
+ ctrl = SP_CTRL (object);
+
+ switch (prop_id) {
+ case ARG_SHAPE:
+ ctrl->shape = (SPCtrlShapeType) g_value_get_int(value);
+ break;
+ case ARG_MODE:
+ ctrl->mode = (SPCtrlModeType) g_value_get_int(value);
+ break;
+ case ARG_ANCHOR:
+ ctrl->anchor = (SPAnchorType) g_value_get_int(value);
+ break;
+ case ARG_SIZE:
+ ctrl->width = g_value_get_uint(value);
+ ctrl->height = ctrl->width;
+ ctrl->defined = (ctrl->width > 0);
+ break;
+ case ARG_ANGLE:
+ ctrl->angle = (double)g_value_get_double(value);
+ break;
+ case ARG_FILLED:
+ ctrl->filled = g_value_get_boolean(value);
+ break;
+ case ARG_FILL_COLOR:
+ ctrl->fill_color = (guint32)g_value_get_int(value);
+ break;
+ case ARG_STROKED:
+ ctrl->stroked = g_value_get_boolean(value);
+ break;
+ case ARG_STROKE_COLOR:
+ ctrl->stroke_color = (guint32)g_value_get_int(value);
+ break;
+ case ARG_PIXBUF:
+ pixbuf = (GdkPixbuf*) g_value_get_pointer(value);
+ // A pixbuf defines it's own size, don't mess about with size.
+ ctrl->width = gdk_pixbuf_get_width(pixbuf);
+ ctrl->height = gdk_pixbuf_get_height(pixbuf);
+ if (gdk_pixbuf_get_has_alpha(pixbuf)) {
+ ctrl->pixbuf = pixbuf;
+ } else {
+ ctrl->pixbuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
+ g_object_unref(pixbuf);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ return; // Do not do an update
+ }
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update(item);
+}
+
+static void
+sp_ctrl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ SPCtrl *ctrl;
+ ctrl = SP_CTRL (object);
+
+ switch (prop_id) {
+
+ case ARG_SHAPE:
+ g_value_set_int(value, ctrl->shape);
+ break;
+
+ case ARG_MODE:
+ g_value_set_int(value, ctrl->mode);
+ break;
+
+ case ARG_ANCHOR:
+ g_value_set_int(value, ctrl->anchor);
+ break;
+
+ case ARG_SIZE:
+ g_value_set_uint(value, ctrl->width);
+ break;
+
+ case ARG_ANGLE:
+ g_value_set_double(value, ctrl->angle);
+ break;
+
+ case ARG_FILLED:
+ g_value_set_boolean(value, ctrl->filled);
+ break;
+
+ case ARG_FILL_COLOR:
+ g_value_set_int(value, ctrl->fill_color);
+ break;
+
+ case ARG_STROKED:
+ g_value_set_boolean(value, ctrl->stroked);
+ break;
+
+ case ARG_STROKE_COLOR:
+ g_value_set_int(value, ctrl->stroke_color);
+ break;
+
+ case ARG_PIXBUF:
+ g_value_set_pointer(value, ctrl->pixbuf);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+
+}
+static void
+sp_ctrl_init (SPCtrl *ctrl)
+{
+ ctrl->shape = SP_CTRL_SHAPE_SQUARE;
+ ctrl->mode = SP_CTRL_MODE_COLOR;
+ ctrl->anchor = SP_ANCHOR_CENTER;
+ ctrl->width = 6;
+ ctrl->height = 6;
+ ctrl->defined = TRUE;
+ ctrl->shown = FALSE;
+ ctrl->build = FALSE;
+ ctrl->filled = 1;
+ ctrl->stroked = 0;
+ ctrl->fill_color = 0x000000ff;
+ ctrl->stroke_color = 0x000000ff;
+ ctrl->angle = 0.0;
+
+ new (&ctrl->box) Geom::IntRect(0,0,0,0);
+ ctrl->cache = nullptr;
+ ctrl->pixbuf = nullptr;
+
+ ctrl->_point = Geom::Point(0,0);
+}
+
+static void sp_ctrl_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CTRL (object));
+
+ SPCtrl *ctrl = SP_CTRL (object);
+
+ if (ctrl->cache) {
+ delete[] ctrl->cache;
+ ctrl->cache = nullptr;
+ }
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrl_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_ctrl_parent_class)->destroy(object);
+}
+
+static void
+sp_ctrl_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCtrl *ctrl;
+ ctrl = SP_CTRL (item);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrl_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(sp_ctrl_parent_class)->update(item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ if (ctrl->shown) {
+ item->canvas->requestRedraw(ctrl->box.left(), ctrl->box.top(), ctrl->box.right() + 1, ctrl->box.bottom() + 1);
+ }
+
+ if (!ctrl->defined) return;
+
+ int w_half = floor(ctrl->width/2.0);
+ int h_half = floor(ctrl->height/2.0);
+
+ int x = floor(affine[4]) - w_half;
+ int y = floor(affine[5]) - h_half;
+
+ switch (ctrl->anchor) {
+ case SP_ANCHOR_N:
+ case SP_ANCHOR_CENTER:
+ case SP_ANCHOR_S:
+ break;
+
+ case SP_ANCHOR_NW:
+ case SP_ANCHOR_W:
+ case SP_ANCHOR_SW:
+ x += w_half;
+ break;
+
+ case SP_ANCHOR_NE:
+ case SP_ANCHOR_E:
+ case SP_ANCHOR_SE:
+ x -= w_half;
+ break;
+ }
+
+ switch (ctrl->anchor) {
+ case SP_ANCHOR_W:
+ case SP_ANCHOR_CENTER:
+ case SP_ANCHOR_E:
+ break;
+
+ case SP_ANCHOR_NW:
+ case SP_ANCHOR_N:
+ case SP_ANCHOR_NE:
+ y += h_half;
+ break;
+
+ case SP_ANCHOR_SW:
+ case SP_ANCHOR_S:
+ case SP_ANCHOR_SE:
+ y -= h_half;
+ break;
+ }
+
+ ctrl->box = Geom::IntRect::from_xywh(x, y, lround(ctrl->width), lround(ctrl->height));
+ sp_canvas_update_bbox (item, ctrl->box.left(), ctrl->box.top(), ctrl->box.right() + 1, ctrl->box.bottom() + 1);
+}
+
+static double
+sp_ctrl_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPCtrl *ctrl = SP_CTRL (item);
+
+ *actual_item = item;
+
+ if (ctrl->box.contains(p.floor())) return 0.0;
+ return 1e18;
+}
+
+bool
+sp_point_inside_line(Geom::Point a, Geom::Point b, Geom::Point c, double tolerance = 0.1) {
+ Geom::LineSegment segment(a, b);
+ return Geom::are_near(c, segment, tolerance);
+}
+
+bool
+sp_point_inside_triangle(Geom::Point p1,Geom::Point p2,Geom::Point p3, Geom::Point point){
+ using Geom::X;
+ using Geom::Y;
+ double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]);
+ double t1 = (point[X]*(p3[Y] - p1[Y]) + point[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator;
+ double t2 = (point[X]*(p2[Y] - p1[Y]) + point[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator;
+ double see = t1 + t2;
+ return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && see <= 1;
+}
+
+static void
+sp_ctrl_build_cache (SPCtrl *ctrl, int device_scale)
+{
+ guint32 stroke_color, fill_color;
+ if (ctrl->filled) {
+ if (ctrl->mode == SP_CTRL_MODE_XOR) {
+ fill_color = ctrl->fill_color;
+ } else {
+ fill_color = argb32_from_rgba(ctrl->fill_color);
+ }
+ } else {
+ fill_color = 0;
+ }
+
+ if (ctrl->stroked) {
+ if (ctrl->mode == SP_CTRL_MODE_XOR) {
+ stroke_color = ctrl->stroke_color;
+ } else {
+ stroke_color = argb32_from_rgba(ctrl->stroke_color);
+ }
+ } else {
+ stroke_color = fill_color;
+ }
+
+ gint width = ctrl->width * device_scale;
+ gint height = ctrl->height * device_scale;
+
+ if (width < 2) return;
+ gint size = width * height;
+
+ if (ctrl->cache) delete[] ctrl->cache;
+ ctrl->cache = new guint32[size];
+
+ switch (ctrl->shape) {
+ case SP_CTRL_SHAPE_SQUARE:
+ {
+ guint32* p = ctrl->cache;
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ if ( i > device_scale - 1 &&
+ j > device_scale - 1 &&
+ width - i > device_scale &&
+ height -j > device_scale) {
+ *p++ = fill_color;
+ } else {
+ *p++ = stroke_color;
+ }
+ }
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_DIAMOND:
+ {
+ // width == height
+ guint32* p = ctrl->cache;
+ int m = (width+1)/2;
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ if ( i + j > m-1+device_scale &&
+ (width-1-i) + j > m-1+device_scale &&
+ (width-1-i) + (height-1-j) > m-1+device_scale &&
+ i + (height-1-j) > m-1+device_scale ) {
+ *p++ = fill_color;
+ } else
+ if ( i + j > m-2 &&
+ (width-1-i) + j > m-2 &&
+ (width-1-i) + (height-1-j) > m-2 &&
+ i + (height-1-j) > m-2 ) {
+ *p++ = stroke_color;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_CIRCLE:
+ {
+ guint32* p = ctrl->cache;
+
+ double rs = width/2.0;
+ double rs2 = rs*rs;
+ double rf = rs-device_scale;
+ double rf2 = rf*rf;
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+
+ double rx = i - (width /2.0) + 0.5;
+ double ry = j - (height/2.0) + 0.5;
+ double r2 = rx*rx + ry*ry;
+
+ if (r2 < rf2) {
+ *p++ = fill_color;
+ } else if (r2 < rs2) {
+ *p++ = stroke_color;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_TRIANGLE:
+ {
+ guint* p = ctrl->cache;
+
+ Geom::Affine m = Geom::Translate(Geom::Point(-width/2.0,-height/2.0));
+ m *= Geom::Rotate(-ctrl->angle);
+ m *= Geom::Translate(Geom::Point(width/2.0, height/2.0));
+
+ // Construct an arrowhead (triangle) of maximum size that won't leak out of rectangle
+ // defined by width and height, assuming width == height.
+ double w2 = width/2.0;
+ double h2 = height/2.0;
+ double w2cos = w2 * cos( M_PI/6 );
+ double h2sin = h2 * sin( M_PI/6 );
+ Geom::Point p1s(0, h2);
+ Geom::Point p2s(w2 + w2cos, h2 + h2sin);
+ Geom::Point p3s(w2 + w2cos, h2 - h2sin);
+ // Needed for constructing smaller arrowhead below.
+ double theta = atan2( Geom::Point( p2s - p1s ) );
+ p1s *= m;
+ p2s *= m;
+ p3s *= m;
+
+ // Construct a smaller arrow head for fill.
+ Geom::Point p1f(device_scale/sin(theta), h2);
+ Geom::Point p2f(w2 + w2cos, h2 - h2sin + device_scale/cos(theta));
+ Geom::Point p3f(w2 + w2cos, h2 + h2sin - device_scale/cos(theta));
+ p1f *= m;
+ p2f *= m;
+ p3f *= m;
+
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ Geom::Point point = Geom::Point(x+0.5,y+0.5);
+ if (sp_point_inside_triangle(p1f, p2f, p3f, point)) {
+ p[(y*width)+x] = fill_color;
+ } else
+ if (sp_point_inside_triangle(p1s, p2s, p3s, point)) {
+ p[(y*width)+x] = stroke_color;
+ } else {
+ p[(y*width)+x] = 0;
+ }
+ }
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_CROSS:
+ {
+ guint* p = ctrl->cache;
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ if ( abs(x - y) < device_scale ||
+ abs(width - 1 - x - y) < device_scale ) {
+ *p++ = stroke_color;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_BITMAP:
+ {
+ if (ctrl->pixbuf) {
+ unsigned char* px = gdk_pixbuf_get_pixels (ctrl->pixbuf);
+ unsigned int rs = gdk_pixbuf_get_rowstride (ctrl->pixbuf);
+ for (int y = 0; y < height/device_scale; y++){
+ for (int x = 0; x < width/device_scale; x++) {
+ unsigned char *s = px + rs*y + 4*x;
+ guint32 color;
+ if (s[3] < 0x80) {
+ color = 0;
+ } else if (s[0] < 0x80) {
+ color = stroke_color;
+ } else {
+ color = fill_color;
+ }
+
+ // Fill in device_scale x device_scale block
+ for (int i = 0; i < device_scale; ++i) {
+ for (int j = 0; j < device_scale; ++j) {
+ guint* p = ctrl->cache +
+ (x * device_scale + i) + // Column
+ (y * device_scale + j) * width; // Row
+ *p = color;
+ }
+ }
+ }
+ }
+ } else {
+ g_print ("control has no pixbuf\n");
+ }
+
+ ctrl->build = TRUE;
+ break;
+ }
+
+ case SP_CTRL_SHAPE_IMAGE:
+ {
+ if (ctrl->pixbuf) {
+ guint rs = gdk_pixbuf_get_rowstride (ctrl->pixbuf);
+ guchar *data = gdk_pixbuf_get_pixels (ctrl->pixbuf);
+
+ for (int y = 0; y < height/device_scale; y++){
+ for (int x = 0; x < width/device_scale; x++) {
+ guint32 *px = reinterpret_cast<guint32*>(data + rs*y + 4*x);
+
+ // Fill in device_scale x device_scale block
+ for (int i = 0; i < device_scale; ++i) {
+ for (int j = 0; j < device_scale; ++j) {
+ guint* p = ctrl->cache +
+ (x * device_scale + i) + // Column
+ (y * device_scale + j) * width; // Row
+ *p = *px;
+ }
+ }
+ }
+ }
+ } else {
+ g_print ("control has no pixbuf\n");
+ }
+ ctrl->build = TRUE;
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static inline guint32 compose_xor(guint32 bg, guint32 fg, guint32 a)
+{
+ guint32 c = bg * (255-a) + (((bg ^ ~fg) + (bg >> 2) - (bg > 127 ? 63 : 0)) & 255) * a;
+ return (c + 127) / 255;
+}
+
+static void
+sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrl *ctrl = SP_CTRL (item);
+
+ if (!ctrl->defined) return;
+ if ((!ctrl->filled) && (!ctrl->stroked)) return;
+
+ // the control-image is rendered into ctrl->cache
+ if (!ctrl->build) {
+ sp_ctrl_build_cache (ctrl, buf->device_scale);
+ }
+
+ // Must match width/height sp_ctrl_build_cache.
+ int w = ctrl->width * buf->device_scale;
+ int h = ctrl->height * buf->device_scale;
+
+ double x = ctrl->box.left() - buf->rect.left();
+ double y = ctrl->box.top() - buf->rect.top();
+
+ // The code below works even when the target is not an image surface
+ if (ctrl->mode == SP_CTRL_MODE_XOR) {
+
+ // 1. Copy the affected part of output to a temporary surface
+
+ // Size in device pixels. Does not set device scale.
+ cairo_surface_t *work = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
+ cairo_surface_set_device_scale(work, buf->device_scale, buf->device_scale);
+
+ cairo_t *cr = cairo_create(work);
+ cairo_translate(cr, -ctrl->box.left(), -ctrl->box.top());
+ cairo_set_source_surface(cr, cairo_get_target(buf->ct), buf->rect.left(), buf->rect.top());
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ // cairo_surface_write_to_png( work, "ctrl0.png" );
+
+ // 2. Composite the control on a temporary surface
+ cairo_surface_flush(work);
+ int strideb = cairo_image_surface_get_stride(work);
+ unsigned char *pxb = cairo_image_surface_get_data(work);
+
+ guint32 *p = ctrl->cache;
+ for (int i=0; i<h; ++i) {
+ guint32 *pb = reinterpret_cast<guint32*>(pxb + i*strideb);
+ for (int j=0; j<w; ++j) {
+ guint32 cc = *p++;
+ guint32 ac = cc & 0xff;
+ if (ac == 0 && cc != 0) {
+ *pb++ = argb32_from_rgba(cc | 0x000000ff);
+ } else {
+ EXTRACT_ARGB32(*pb, ab,rb,gb,bb)
+ guint32 ro = compose_xor(rb, (cc & 0xff000000) >> 24, ac);
+ guint32 go = compose_xor(gb, (cc & 0x00ff0000) >> 16, ac);
+ guint32 bo = compose_xor(bb, (cc & 0x0000ff00) >> 8, ac);
+ ASSEMBLE_ARGB32(px, ab,ro,go,bo)
+ *pb++ = px;
+ }
+ }
+ }
+ cairo_surface_mark_dirty(work);
+ // cairo_surface_write_to_png( work, "ctrl1.png" );
+
+ // 3. Replace the affected part of output with contents of temporary surface
+ cairo_save(buf->ct);
+ cairo_set_source_surface(buf->ct, work, x, y);
+ cairo_rectangle(buf->ct, x, y, w/buf->device_scale, h/buf->device_scale);
+ cairo_clip(buf->ct);
+ cairo_set_operator(buf->ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(buf->ct);
+ cairo_restore(buf->ct);
+ cairo_surface_destroy(work);
+ } else {
+ cairo_surface_t *cache = cairo_image_surface_create_for_data(
+ reinterpret_cast<unsigned char*>(ctrl->cache), CAIRO_FORMAT_ARGB32, w, h, w*4);
+ cairo_surface_set_device_scale(cache, buf->device_scale, buf->device_scale);
+ cairo_surface_mark_dirty(cache);
+ cairo_save(buf->ct);
+ cairo_set_source_surface(buf->ct, cache, x, y);
+ cairo_rectangle(buf->ct, x, y, w/buf->device_scale, h/buf->device_scale);
+ cairo_clip(buf->ct);
+ cairo_paint(buf->ct);
+ // cairo_surface_write_to_png(cache, "ctrl_cache.png" );
+ cairo_restore(buf->ct);
+ cairo_surface_destroy(cache);
+
+ }
+ ctrl->shown = TRUE;
+}
+
+void SPCtrl::moveto (Geom::Point const p) {
+ if (p != _point) {
+ sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (this), Geom::Affine(Geom::Translate (p)));
+ }
+ _point = p;
+}
+
+
+/*
+ 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/display/sodipodi-ctrl.h b/src/display/sodipodi-ctrl.h
new file mode 100644
index 0000000..184c77e
--- /dev/null
+++ b/src/display/sodipodi-ctrl.h
@@ -0,0 +1,90 @@
+// 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_CTRL_H
+#define INKSCAPE_CTRL_H
+
+/* sodipodi-ctrl
+ *
+ * It is simply small square, which does not scale nor rotate
+ *
+ */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "sp-canvas-item.h"
+#include "enums.h"
+
+
+#define SP_TYPE_CTRL (sp_ctrl_get_type ())
+#define SP_CTRL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CTRL, SPCtrl))
+#define SP_CTRL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CTRL, SPCtrlClass))
+#define SP_IS_CTRL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CTRL))
+#define SP_IS_CTRL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRL))
+
+enum SPCtrlShapeType {
+ SP_CTRL_SHAPE_SQUARE,
+ SP_CTRL_SHAPE_DIAMOND,
+ SP_CTRL_SHAPE_CIRCLE,
+ SP_CTRL_SHAPE_TRIANGLE,
+ SP_CTRL_SHAPE_CROSS,
+ SP_CTRL_SHAPE_BITMAP,
+ SP_CTRL_SHAPE_IMAGE
+};
+
+
+enum SPCtrlModeType {
+ SP_CTRL_MODE_COLOR,
+ SP_CTRL_MODE_XOR
+};
+
+struct SPCtrl : public SPCanvasItem {
+ SPCtrlShapeType shape;
+ SPCtrlModeType mode;
+ SPAnchorType anchor;
+ unsigned int width;
+ unsigned int height;
+
+ guint defined : 1;
+ guint shown : 1;
+ guint build : 1;
+ guint filled : 1;
+ guint stroked : 1;
+ guint32 fill_color;
+ guint32 stroke_color;
+ gdouble angle;
+
+ Geom::IntRect box; /* NB! x1 & y1 are included */
+ guint32 *cache;
+ GdkPixbuf * pixbuf;
+
+ void moveto(Geom::Point const p);
+ Geom::Point _point;
+};
+
+struct SPCtrlClass : public SPCanvasItemClass{
+};
+
+
+
+/* Standard Gtk function */
+GType sp_ctrl_get_type ();
+
+
+#endif /* !INKSCAPE_CTRL_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/display/sodipodi-ctrlrect.cpp b/src/display/sodipodi-ctrlrect.cpp
new file mode 100644
index 0000000..b47f676
--- /dev/null
+++ b/src/display/sodipodi-ctrlrect.cpp
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Simple non-transformed rectangle, usable for rubberband
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include "inkscape.h"
+#include "sodipodi-ctrlrect.h"
+#include "sp-canvas-util.h"
+#include "display/cairo-utils.h"
+#include "display/sp-canvas.h"
+#include <2geom/transforms.h>
+
+/*
+ * Currently we do not have point method, as it should always be painted
+ * during some transformation, which takes care of events...
+ *
+ * Corner coords can be in any order - i.e. x1 < x0 is allowed
+ */
+
+static void sp_ctrlrect_destroy(SPCanvasItem *object);
+
+static void sp_ctrlrect_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+G_DEFINE_TYPE(CtrlRect, sp_ctrlrect, SP_TYPE_CANVAS_ITEM);
+
+static void sp_ctrlrect_class_init(CtrlRectClass *c)
+{
+ SPCanvasItemClass *item_class = SP_CANVAS_ITEM_CLASS(c);
+
+ item_class->destroy = sp_ctrlrect_destroy;
+ item_class->update = sp_ctrlrect_update;
+ item_class->render = sp_ctrlrect_render;
+}
+
+static void sp_ctrlrect_init(CtrlRect *cr)
+{
+ cr->init();
+}
+
+static void sp_ctrlrect_destroy(SPCanvasItem *object)
+{
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlrect_parent_class)->destroy) {
+ (* SP_CANVAS_ITEM_CLASS(sp_ctrlrect_parent_class)->destroy)(object);
+ }
+}
+
+
+static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SP_CTRLRECT(item)->render(buf);
+}
+
+
+static void sp_ctrlrect_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SP_CTRLRECT(item)->update(affine, flags);
+}
+
+
+
+void CtrlRect::init()
+{
+ _has_fill = false;
+ _dashed = false;
+ _checkerboard = false;
+
+ _shadow_width = 0;
+
+ _area = Geom::OptIntRect();
+
+ _rect = Geom::Rect(Geom::Point(0,0),Geom::Point(0,0));
+
+ _border_color = 0x000000ff;
+ _fill_color = 0xffffffff;
+ _shadow_color = 0x000000ff;
+ _inverted = false;
+}
+
+
+void CtrlRect::render(SPCanvasBuf *buf)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ if (!_area) {
+ return;
+ }
+
+ Geom::IntRect area = *_area; // _area already includes space for shadow.
+ if ( area.intersects(buf->rect) )
+ {
+
+ cairo_save(buf->ct);
+ cairo_translate(buf->ct, -buf->rect.left(), -buf->rect.top());
+
+ // Get canvas rotation (scale is isotropic).
+ double rotation = atan2( _affine[1], _affine[0] );
+
+ // Are we axis aligned?
+ double mod_rot = fmod(rotation * M_2_PI, 1);
+ bool axis_aligned = Geom::are_near( mod_rot, 0 ) || Geom::are_near( mod_rot, 1.0 );
+
+ // Get the points we need transformed to window coordinates.
+ Geom::Point rect_transformed[4];
+ for (unsigned i = 0; i < 4; ++i) {
+ rect_transformed[i] = _rect.corner(i) * _affine;
+ }
+
+ if(_inverted) {
+ cairo_set_operator(buf->ct, CAIRO_OPERATOR_DIFFERENCE);
+ }
+
+ // Draw shadow first. Shadow extends under rectangle to reduce aliasing effects.
+ if (_shadow_width > 0 && !_dashed) {
+ Geom::Point const * corners = rect_transformed;
+ double shadowydir = _affine.det() > 0 ? -1 : 1;
+
+ // is the desktop y-axis downwards?
+ if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->is_yaxisdown()) {
+ ++corners; // need corners 1/2/3 instead of 0/1/2
+ shadowydir *= -1;
+ }
+
+ // Offset by half stroke width (_shadow_width is in window coordinates).
+ // Need to handle change in handedness with flips.
+ Geom::Point shadow( _shadow_width/2.0, shadowydir * _shadow_width/2.0 );
+ shadow *= Geom::Rotate( rotation );
+
+ if (axis_aligned) {
+ // Snap to pixel grid (add 0.5 to center on pixel).
+ cairo_move_to( buf->ct,
+ floor(corners[0][X] + shadow[X]+0.5) + 0.5,
+ floor(corners[0][Y] + shadow[Y]+0.5) + 0.5 );
+ cairo_line_to( buf->ct,
+ floor(corners[1][X] + shadow[X]+0.5) + 0.5,
+ floor(corners[1][Y] + shadow[Y]+0.5) + 0.5 );
+ cairo_line_to( buf->ct,
+ floor(corners[2][X] + shadow[X]+0.5) + 0.5,
+ floor(corners[2][Y] + shadow[Y]+0.5) + 0.5 );
+ } else {
+ cairo_move_to( buf->ct,
+ corners[0][X] + shadow[X],
+ corners[0][Y] + shadow[Y] );
+ cairo_line_to( buf->ct,
+ corners[1][X] + shadow[X],
+ corners[1][Y] + shadow[Y] );
+ cairo_line_to( buf->ct,
+ corners[2][X] + shadow[X],
+ corners[2][Y] + shadow[Y] );
+ }
+
+ ink_cairo_set_source_rgba32( buf->ct, _shadow_color );
+ cairo_set_line_width( buf->ct, _shadow_width + 1 );
+ cairo_stroke( buf->ct );
+ }
+
+
+ // Setup rectangle path
+ if (axis_aligned) {
+
+ // Snap to pixel grid
+ Geom::Rect outline( _rect.min() * _affine, _rect.max() * _affine);
+ cairo_rectangle (buf->ct,
+ floor(outline.min()[X])+0.5,
+ floor(outline.min()[Y])+0.5,
+ floor(outline.max()[X]) - floor(outline.min()[X]),
+ floor(outline.max()[Y]) - floor(outline.min()[Y]));
+ } else {
+
+ // Angled
+ cairo_move_to( buf->ct, rect_transformed[0][X], rect_transformed[0][Y] );
+ cairo_line_to( buf->ct, rect_transformed[1][X], rect_transformed[1][Y] );
+ cairo_line_to( buf->ct, rect_transformed[2][X], rect_transformed[2][Y] );
+ cairo_line_to( buf->ct, rect_transformed[3][X], rect_transformed[3][Y] );
+ cairo_close_path( buf->ct );
+ }
+
+ // This doesn't seem to be used anywhere. If it is, then we should
+ // probably rotate the coordinate system and fill using a cairo_rectangle().
+ if (_checkerboard) {
+ cairo_pattern_t *cb = ink_cairo_pattern_create_checkerboard();
+ cairo_set_source(buf->ct, cb);
+ cairo_pattern_destroy(cb);
+ cairo_fill_preserve(buf->ct);
+ }
+
+ if (_has_fill) {
+ ink_cairo_set_source_rgba32(buf->ct, _fill_color);
+ cairo_fill_preserve(buf->ct);
+ }
+
+ // Set up stroke.
+ ink_cairo_set_source_rgba32(buf->ct, _border_color);
+ cairo_set_line_width(buf->ct, 1);
+ static double const dashes[2] = {4.0, 4.0};
+ if (_dashed) cairo_set_dash(buf->ct, dashes, 2, 0);
+
+ // Stroke rectangle.
+ cairo_stroke_preserve(buf->ct);
+
+ // Highlight the border by drawing it in _shadow_color.
+ if (_shadow_width == 1 && _dashed) {
+ ink_cairo_set_source_rgba32(buf->ct, _shadow_color);
+ cairo_set_dash(buf->ct, dashes, 2, 4); // Dash offset by dash length.
+ cairo_stroke_preserve(buf->ct);
+ }
+
+ cairo_new_path(buf->ct); // Clear path or get weird artifacts.
+ cairo_restore(buf->ct);
+ }
+}
+
+
+void CtrlRect::update(Geom::Affine const &affine, unsigned int flags)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ if ((SP_CANVAS_ITEM_CLASS(sp_ctrlrect_parent_class))->update) {
+ (SP_CANVAS_ITEM_CLASS(sp_ctrlrect_parent_class))->update(this, affine, flags);
+ }
+
+ // Note: There is no harm if 'area' is too large other than a possible small slow down in
+ // rendering speed.
+
+ // Calculate an axis-aligned bounding box that include all of transformed _rect.
+ Geom::Rect bbox = _rect;
+ bbox *= affine;
+
+ // Enlarge bbox by twice shadow size (to allow for shadow on any side with a 45deg rotation).
+ bbox.expandBy( 2.0 *_shadow_width );
+
+ // Generate an integer rectangle that includes bbox.
+ Geom::OptIntRect _area_old = _area;
+ _area = bbox.roundOutwards();
+
+ // std::cout << " _rect: " << _rect << std::endl;
+ // std::cout << " bbox: " << bbox << std::endl;
+ // std::cout << " area: " << *_area << std::endl;
+
+ if (_area) {
+ Geom::IntRect area = *_area;
+ // Windows glitches sometimes in cairo, possibly due to the way the surface is cleared.
+ // Increasing '_area' won't work as the box must be drawn 'inside' the updated area.
+ sp_canvas_update_bbox(this, area.left(), area.top(), area.right() + 1, area.bottom() + 1);
+
+ } else {
+ std::cerr << "CtrlRect::update: No area!!" << std::endl;
+ }
+
+ // At rendering stage, we need to know affine:
+ _affine = affine;
+}
+
+
+void CtrlRect::setColor(guint32 b, bool h, guint f)
+{
+ _border_color = b;
+ _has_fill = h;
+ _fill_color = f;
+ _requestUpdate();
+}
+
+void CtrlRect::setShadow(int s, guint c)
+{
+ _shadow_width = s;
+ _shadow_color = c;
+ _requestUpdate();
+}
+
+void CtrlRect::setInvert(bool invert) {
+ _inverted = invert;
+ _requestUpdate();
+}
+
+void CtrlRect::setRectangle(Geom::Rect const &r)
+{
+ _rect = r;
+ _requestUpdate();
+}
+
+void CtrlRect::setDashed(bool d)
+{
+ _dashed = d;
+ _requestUpdate();
+}
+
+void CtrlRect::setCheckerboard(bool d)
+{
+ _checkerboard = d;
+ _requestUpdate();
+}
+
+void CtrlRect::_requestUpdate()
+{
+ sp_canvas_item_request_update(SP_CANVAS_ITEM(this));
+}
+
+/*
+ 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/display/sodipodi-ctrlrect.h b/src/display/sodipodi-ctrlrect.h
new file mode 100644
index 0000000..cdc4a3d
--- /dev/null
+++ b/src/display/sodipodi-ctrlrect.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_CTRLRECT_H
+#define SEEN_INKSCAPE_CTRLRECT_H
+
+/**
+ * @file
+ * Simple non-transformed rectangle, usable for rubberband.
+ * Modified to work with rotated canvas.
+ */
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Tavmjong Bah <tavjong@free.fr>
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include <glib.h>
+#include "sp-canvas-item.h"
+#include <2geom/rect.h>
+#include <2geom/int-rect.h>
+#include <2geom/transforms.h>
+
+struct SPCanvasBuf;
+
+#define SP_TYPE_CTRLRECT (sp_ctrlrect_get_type ())
+#define SP_CTRLRECT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_CTRLRECT, CtrlRect))
+#define SP_CTRLRECT_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), SP_TYPE_CTRLRECT, CtrlRectClass))
+#define SP_IS_CTRLRECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CTRLRECT))
+#define SP_IS_CTRLRECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRLRECT))
+
+class CtrlRect : public SPCanvasItem
+{
+public:
+
+ void init();
+ void setColor(guint32 b, bool h, guint f);
+ void setShadow(int s, guint c);
+ void setInvert(bool invert);
+ void setRectangle(Geom::Rect const &r);
+ void setDashed(bool d);
+ void setCheckerboard(bool d);
+
+ void render(SPCanvasBuf *buf);
+ void update(Geom::Affine const &affine, unsigned int flags);
+
+private:
+ void _requestUpdate();
+
+ Geom::Rect _rect;
+ Geom::Affine _affine;
+
+ bool _has_fill;
+ bool _dashed;
+ bool _inverted;
+ bool _checkerboard;
+
+ Geom::OptIntRect _area;
+ gint _shadow_width;
+ guint32 _border_color;
+ guint32 _fill_color;
+ guint32 _shadow_color;
+};
+
+struct CtrlRectClass : public SPCanvasItemClass {};
+
+GType sp_ctrlrect_get_type();
+
+#endif // SEEN_CTRLRECT_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/display/sp-canvas-group.h b/src/display/sp-canvas-group.h
new file mode 100644
index 0000000..ff85b4e
--- /dev/null
+++ b/src/display/sp-canvas-group.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_GROUP_H
+#define SEEN_SP_CANVAS_GROUP_H
+
+/**
+ * @file
+ * SPCanvasGroup.
+ */
+/*
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2010 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib-object.h>
+
+#define SP_TYPE_CANVAS_GROUP (sp_canvas_group_get_type())
+#define SP_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_CANVAS_GROUP, SPCanvasGroup))
+#define SP_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_CANVAS_GROUP))
+
+GType sp_canvas_group_get_type();
+
+
+
+#endif // SEEN_SP_CANVAS_GROUP_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/display/sp-canvas-item.h b/src/display/sp-canvas-item.h
new file mode 100644
index 0000000..9856906
--- /dev/null
+++ b/src/display/sp-canvas-item.h
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_ITEM_H
+#define SEEN_SP_CANVAS_ITEM_H
+
+/**
+ * @file
+ * SPCanvasItem.
+ */
+/*
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2010 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/rect.h>
+#include <boost/intrusive/list.hpp>
+#include <glib-object.h>
+
+#include "ui/control-types.h"
+
+G_BEGIN_DECLS
+
+struct SPCanvas;
+struct SPCanvasBuf;
+struct SPCanvasGroup;
+
+typedef struct _SPCanvasItemClass SPCanvasItemClass;
+typedef union _GdkEvent GdkEvent;
+typedef struct _GdkCursor GdkCursor;
+
+#define SP_TYPE_CANVAS_ITEM (sp_canvas_item_get_type())
+#define SP_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_CANVAS_ITEM, SPCanvasItem))
+#define SP_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_ITEM, SPCanvasItemClass))
+#define SP_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_CANVAS_ITEM))
+#define SP_CANVAS_ITEM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), SP_TYPE_CANVAS_ITEM, SPCanvasItemClass))
+
+
+/**
+ * An SPCanvasItem refers to a SPCanvas and to its parent item; it has
+ * four coordinates, a bounding rectangle, and a transformation matrix.
+ */
+struct SPCanvasItem {
+ GInitiallyUnowned parent_instance;
+
+ // boost linked list member hook
+ boost::intrusive::list_member_hook<> member_hook_;
+
+ SPCanvas *canvas;
+ SPCanvasItem *parent;
+
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+ Geom::Rect bounds;
+ Geom::Affine xform;
+
+ int ctrlResize;
+ Inkscape::ControlType ctrlType;
+ Inkscape::ControlFlags ctrlFlags;
+
+ // Replacement for custom GtkObject flag enumeration
+ gboolean visible;
+ gboolean need_update;
+ gboolean need_affine;
+
+ // If true, then SPCanvasGroup::point() and sp_canvas_item_invoke_point() will calculate
+ // the distance to the pointer, such that this item can be picked in pickCurrentItem()
+ // Only if an item can be picked, then it can be set as current_item and receive events!
+ bool pickable;
+
+ bool in_destruction;
+};
+
+GType sp_canvas_item_get_type();
+
+//Define type for linked list storing SPCanvasItems
+typedef boost::intrusive::list<
+ SPCanvasItem,
+ boost::intrusive::member_hook<SPCanvasItem, boost::intrusive::list_member_hook<>, &SPCanvasItem::member_hook_> >
+ SPCanvasItemList;
+
+/**
+ * The vtable of an SPCanvasItem.
+ */
+struct _SPCanvasItemClass {
+ GInitiallyUnownedClass parent_class;
+
+ void (* update) (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+
+ void (* render) (SPCanvasItem *item, SPCanvasBuf *buf);
+ double (* point) (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+
+ int (* event) (SPCanvasItem *item, GdkEvent *event);
+ void (* viewbox_changed) (SPCanvasItem *item, Geom::IntRect const &new_area);
+
+ /* Default signal handler for the ::destroy signal, which is
+ * invoked to request that references to the widget be dropped.
+ * If an object class overrides destroy() in order to perform class
+ * specific destruction then it must still invoke its superclass'
+ * implementation of the method after it is finished with its
+ * own cleanup. (See gtk_widget_real_destroy() for an example of
+ * how to do this).
+ */
+ void (*destroy) (SPCanvasItem *object);
+};
+
+/**
+ * Constructs new SPCanvasItem on SPCanvasGroup.
+ */
+SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GType type, const gchar *first_arg_name, ...);
+
+G_END_DECLS
+
+
+void sp_canvas_item_affine_absolute(SPCanvasItem *item, Geom::Affine const &aff);
+
+void sp_canvas_item_raise(SPCanvasItem *item, int positions);
+void sp_canvas_item_raise_to_top(SPCanvasItem *item);
+void sp_canvas_item_lower(SPCanvasItem *item, int positions);
+void sp_canvas_item_lower_to_bottom(SPCanvasItem *item);
+bool sp_canvas_item_is_visible(SPCanvasItem *item);
+void sp_canvas_item_show(SPCanvasItem *item);
+void sp_canvas_item_hide(SPCanvasItem *item);
+void sp_canvas_item_destroy(SPCanvasItem *item);
+int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime);
+void sp_canvas_item_ungrab(SPCanvasItem *item);
+
+Geom::Affine sp_canvas_item_i2w_affine(SPCanvasItem const *item);
+
+void sp_canvas_item_request_update(SPCanvasItem *item);
+
+/* get item z-order in parent group */
+
+gint sp_canvas_item_order(SPCanvasItem * item);
+
+
+
+#endif // SEEN_SP_CANVAS_ITEM_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/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp
new file mode 100644
index 0000000..dee7d8d
--- /dev/null
+++ b/src/display/sp-canvas-util.cpp
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Helper stuff for SPCanvas
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <2geom/affine.h>
+#include "sp-canvas-util.h"
+#include "sp-canvas-item.h"
+#include "sp-canvas.h"
+
+void sp_canvas_update_bbox(SPCanvasItem *item, int x1, int y1, int x2, int y2)
+{
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+ item->x1 = x1;
+ item->y1 = y1;
+ item->x2 = x2;
+ item->y2 = y2;
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+}
+
+void
+sp_canvas_item_reset_bounds (SPCanvasItem *item)
+{
+ item->x1 = 0.0;
+ item->y1 = 0.0;
+ item->x2 = 0.0;
+ item->y2 = 0.0;
+}
+
+void sp_canvas_prepare_buffer(SPCanvasBuf *)
+{
+}
+
+Geom::Affine sp_canvas_item_i2p_affine (SPCanvasItem * item)
+{
+ g_assert (item != nullptr); /* this may be overly zealous - it is
+ * plausible that this gets called
+ * with item == 0. */
+
+ return item->xform;
+}
+
+Geom::Affine sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to)
+{
+ g_assert (from != nullptr);
+ g_assert (to != nullptr);
+
+ return sp_canvas_item_i2w_affine(from) * sp_canvas_item_i2w_affine(to).inverse();
+}
+
+void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, Geom::Affine const &i2w)
+{
+ g_assert (item != nullptr);
+
+ sp_canvas_item_affine_absolute(item, i2w * sp_canvas_item_i2w_affine(item->parent).inverse());
+}
+
+void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z)
+{
+ g_assert (item != nullptr);
+
+ if (z == 0)
+ return sp_canvas_item_lower_to_bottom(item);
+
+ gint current_z = sp_canvas_item_order (item);
+
+ if (current_z == -1) // not found in its parent
+ return;
+
+ if (z == current_z)
+ return;
+
+ if (z > current_z) {
+ sp_canvas_item_raise (item, z - current_z);
+ } else {
+ sp_canvas_item_lower (item, current_z - z);
+ }
+}
+
+gint
+sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b)
+{
+ gint const o_a = sp_canvas_item_order (a);
+ gint const o_b = sp_canvas_item_order (b);
+
+ if (o_a > o_b) return -1;
+ if (o_a < o_b) return 1;
+
+ return 0;
+}
+
+/*
+ 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/display/sp-canvas-util.h b/src/display/sp-canvas-util.h
new file mode 100644
index 0000000..f58deb1
--- /dev/null
+++ b/src/display/sp-canvas-util.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_UTILS_H
+#define SEEN_SP_CANVAS_UTILS_H
+
+/*
+ * Helper stuff for SPCanvas
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/value.h>
+
+struct SPCanvasItem;
+struct SPCanvasBuf;
+
+namespace Geom {
+ class Affine;
+}
+
+/* Miscellaneous utility & convenience functions for general canvas objects */
+
+void sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2);
+void sp_canvas_item_reset_bounds (SPCanvasItem *item);
+void sp_canvas_prepare_buffer (SPCanvasBuf *buf);
+
+/* get i2p (item to parent) affine transformation as general 6-element array */
+
+Geom::Affine sp_canvas_item_i2p_affine (SPCanvasItem * item);
+
+/* get i2i (item to item) affine transformation as general 6-element array */
+
+Geom::Affine sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to);
+
+/* set item affine matrix to achieve given i2w matrix */
+
+void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, Geom::Affine const & aff);
+
+void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z);
+
+gint sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b);
+
+#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/display/sp-canvas.cpp b/src/display/sp-canvas.cpp
new file mode 100644
index 0000000..87a9722
--- /dev/null
+++ b/src/display/sp-canvas.cpp
@@ -0,0 +1,2959 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Port of GnomeCanvas for Inkscape needs
+ *
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * fred
+ * bbyak
+ * Jon A. Cruz <jon@joncruz.org>
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002-2006 authors
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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 <gdkmm/devicemanager.h>
+#include <gdkmm/display.h>
+#include <gdkmm/rectangle.h>
+#include <gdkmm/seat.h>
+
+#include <cairomm/region.h>
+
+#include "cms-system.h"
+#include "color.h"
+#include "debug/gdk-event-latency-tracker.h"
+#include "desktop.h"
+#include "display/cairo-utils.h"
+#include "display/canvas-arena.h"
+#include "display/rendermode.h"
+#include "display/sp-canvas-group.h"
+#include "display/sp-canvas.h"
+#include "helper/sp-marshal.h"
+#include "inkscape-window.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "sodipodi-ctrlrect.h"
+#include "ui/tools/node-tool.h"
+#include "ui/tools/tool-base.h"
+#include "widgets/desktop-widget.h"
+#include <2geom/affine.h>
+#include <2geom/rect.h>
+
+using Inkscape::Debug::GdkEventLatencyTracker;
+
+
+// Disabled by Mentalguy, many years ago in commit 427a81
+static bool const HAS_BROKEN_MOTION_HINTS = true;
+
+// Define this to visualize the regions to be redrawn
+//#define DEBUG_REDRAW 1;
+
+// Define this to output the time spent in a full idle loop and the number of "tiles" painted
+//#define DEBUG_PERFORMANCE 1;
+
+// Tiles are a way to minimize the number of redraws, eliminating too small redraws.
+// The canvas stores a 2D array of ints, each representing a TILE_SIZExTILE_SIZE pixels tile.
+// If any part of it is dirtied, the entire tile is dirtied (its int is nonzero) and repainted.
+#define TILE_SIZE 16
+
+/**
+ * The SPCanvasGroup vtable.
+ */
+struct SPCanvasGroupClass {
+ SPCanvasItemClass parent_class;
+};
+
+static void ungrab_default_client_pointer()
+{
+ auto const display = Gdk::Display::get_default();
+ auto const seat = display->get_default_seat();
+ seat->ungrab();
+}
+
+/**
+ * A group of items.
+ */
+struct SPCanvasGroup {
+ /**
+ * Adds an item to a canvas group.
+ */
+ void add(SPCanvasItem *item);
+
+ /**
+ * Removes an item from a canvas group.
+ */
+ void remove(SPCanvasItem *item);
+
+ /**
+ * Class initialization function for SPCanvasGroupClass.
+ */
+ static void classInit(SPCanvasGroupClass *klass);
+
+ /**
+ * Callback. Empty.
+ */
+ static void init(SPCanvasGroup *group);
+
+ /**
+ * Callback that destroys all items in group and calls group's virtual
+ * destroy() function.
+ */
+ static void destroy(SPCanvasItem *object);
+
+ /**
+ * Update handler for canvas groups.
+ */
+ static void update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+
+ /**
+ * Point handler for canvas groups.
+ */
+ static double point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+
+ /**
+ * Renders all visible canvas group items in buf rectangle.
+ */
+ static void render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+ static void viewboxChanged(SPCanvasItem *item, Geom::IntRect const &new_area);
+
+
+ // Data members: ----------------------------------------------------------
+
+ SPCanvasItem item;
+
+ SPCanvasItemList items;
+};
+
+/**
+ * The SPCanvas vtable.
+ */
+struct SPCanvasClass {
+ GtkWidgetClass parent_class;
+};
+
+namespace {
+
+GdkWindow *getWindow(SPCanvas *canvas)
+{
+ return gtk_widget_get_window(reinterpret_cast<GtkWidget *>(canvas));
+}
+
+
+// SPCanvasItem
+
+enum {
+ ITEM_EVENT,
+ ITEM_LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_VISIBLE
+};
+
+
+void trackLatency(GdkEvent const *event);
+
+enum {
+ DESTROY,
+ LAST_SIGNAL
+};
+
+/**
+ * Callback that removes item from all referrers and destroys it.
+ */
+void sp_canvas_item_dispose(GObject *object);
+void sp_canvas_item_finalize(GObject *object);
+void sp_canvas_item_real_destroy(SPCanvasItem *object);
+
+static guint object_signals[LAST_SIGNAL] = { 0 };
+
+/**
+ * Sets up the newly created SPCanvasItem.
+ *
+ * We make it static for encapsulation reasons since it was nowhere used.
+ */
+void sp_canvas_item_construct(SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args);
+
+/**
+ * Helper that returns true iff item is descendant of parent.
+ */
+bool is_descendant(SPCanvasItem const *item, SPCanvasItem const *parent);
+
+guint item_signals[ITEM_LAST_SIGNAL] = { 0 };
+
+} // namespace
+
+G_DEFINE_TYPE(SPCanvasItem, sp_canvas_item, G_TYPE_INITIALLY_UNOWNED);
+
+static void
+sp_canvas_item_class_init(SPCanvasItemClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ item_signals[ITEM_EVENT] = g_signal_new ("event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ ((glong)((guint8*)&(klass->event) - (guint8*)klass)),
+ nullptr, nullptr,
+ sp_marshal_BOOLEAN__POINTER,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT);
+
+ gobject_class->dispose = sp_canvas_item_dispose;
+ gobject_class->finalize = sp_canvas_item_finalize;
+ klass->destroy = sp_canvas_item_real_destroy;
+
+ object_signals[DESTROY] =
+ g_signal_new ("destroy",
+ G_TYPE_FROM_CLASS (gobject_class),
+ (GSignalFlags)(G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+ G_STRUCT_OFFSET (SPCanvasItemClass, destroy),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+sp_canvas_item_init(SPCanvasItem *item)
+{
+ item->xform = Geom::Affine(Geom::identity());
+ item->ctrlResize = 0;
+ item->ctrlType = Inkscape::CTRL_TYPE_UNKNOWN;
+ item->ctrlFlags = Inkscape::CTRL_FLAG_NORMAL;
+
+ // TODO items should not be visible on creation - this causes kludges with items
+ // that should be initially invisible; examples of such items: node handles, the CtrlRect
+ // used for rubberbanding, path outline, etc.
+ item->visible = TRUE;
+ item->in_destruction = false;
+ item->pickable = true;
+}
+
+SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GType type, gchar const *first_arg_name, ...)
+{
+ va_list args;
+
+ g_return_val_if_fail(parent != nullptr, NULL);
+ g_return_val_if_fail(SP_IS_CANVAS_GROUP(parent), NULL);
+ g_return_val_if_fail(g_type_is_a(type, SP_TYPE_CANVAS_ITEM), NULL);
+
+ SPCanvasItem *item = SP_CANVAS_ITEM(g_object_new(type, nullptr));
+
+ va_start(args, first_arg_name);
+ sp_canvas_item_construct(item, parent, first_arg_name, args);
+ va_end(args);
+
+ return item;
+}
+
+namespace {
+
+void sp_canvas_item_construct(SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args)
+{
+ g_return_if_fail(SP_IS_CANVAS_GROUP(parent));
+ g_return_if_fail(SP_IS_CANVAS_ITEM(item));
+
+ // manually invoke list_member_hook constructor
+ new (&(item->member_hook_)) boost::intrusive::list_member_hook<>();
+
+ item->parent = SP_CANVAS_ITEM(parent);
+ item->canvas = item->parent->canvas;
+
+ g_object_set_valist(G_OBJECT(item), first_arg_name, args);
+
+ SP_CANVAS_GROUP(item->parent)->add(item);
+
+ sp_canvas_item_request_update(item);
+}
+
+} // namespace
+
+/**
+ * Helper function that requests redraw only if item's visible flag is set.
+ */
+static void redraw_if_visible(SPCanvasItem *item)
+{
+ if (item->visible) {
+ int x0 = (int)(item->x1);
+ int x1 = (int)(item->x2);
+ int y0 = (int)(item->y1);
+ int y1 = (int)(item->y2);
+
+ if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+ item->canvas->requestRedraw((int)(item->x1 - 1), (int)(item->y1 -1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+ }
+ }
+}
+
+void sp_canvas_item_destroy(SPCanvasItem *item)
+{
+ g_return_if_fail(SP_IS_CANVAS_ITEM(item));
+
+ if (!item->in_destruction)
+ g_object_run_dispose(G_OBJECT(item));
+}
+
+namespace {
+void sp_canvas_item_dispose(GObject *object)
+{
+ SPCanvasItem *item = SP_CANVAS_ITEM (object);
+
+ /* guard against reinvocations during
+ * destruction with the in_destruction flag.
+ */
+ if (!item->in_destruction)
+ {
+ item->in_destruction=true;
+
+ // Hack: if this is a ctrlrect, move it to 0,0;
+ // this redraws only the stroke of the rect to be deleted,
+ // avoiding redraw of the entire area
+ if (SP_IS_CTRLRECT(item)) {
+ SP_CTRLRECT(object)->setRectangle(Geom::Rect(Geom::Point(0,0),Geom::Point(0,0)));
+ SP_CTRLRECT(object)->update(item->xform, 0);
+ } else {
+ redraw_if_visible (item);
+ }
+ item->visible = FALSE;
+
+ if (item == item->canvas->_current_item) {
+ item->canvas->_current_item = nullptr;
+ item->canvas->_need_repick = TRUE;
+ }
+
+ if (item == item->canvas->_new_current_item) {
+ item->canvas->_new_current_item = nullptr;
+ item->canvas->_need_repick = TRUE;
+ }
+
+ if (item == item->canvas->_grabbed_item) {
+ item->canvas->_grabbed_item = nullptr;
+ ungrab_default_client_pointer();
+ }
+
+ if (item == item->canvas->_focused_item) {
+ item->canvas->_focused_item = nullptr;
+ }
+
+ if (item->parent) {
+ SP_CANVAS_GROUP(item->parent)->remove(item);
+ }
+
+ g_signal_emit (object, object_signals[DESTROY], 0);
+ item->in_destruction = false;
+ }
+
+ G_OBJECT_CLASS(sp_canvas_item_parent_class)->dispose(object);
+}
+
+void sp_reset_spliter(SPCanvas *canvas)
+{
+ canvas->_spliter = Geom::OptIntRect();
+ canvas->_spliter_area = Geom::OptIntRect();
+ canvas->_spliter_control = Geom::OptIntRect();
+ canvas->_spliter_top = Geom::OptIntRect();
+ canvas->_spliter_bottom = Geom::OptIntRect();
+ canvas->_spliter_left = Geom::OptIntRect();
+ canvas->_spliter_right = Geom::OptIntRect();
+ canvas->_spliter_control_pos = Geom::Point();
+ canvas->_spliter_in_control_pos = Geom::Point();
+ canvas->_split_value = 0.5;
+ canvas->_split_vertical = true;
+ canvas->_split_inverse = false;
+ canvas->_split_hover_vertical = false;
+ canvas->_split_hover_horizontal = false;
+ canvas->_split_hover = false;
+ canvas->_split_pressed = false;
+ canvas->_split_control_pressed = false;
+ canvas->_split_dragging = false;
+}
+
+void sp_canvas_item_real_destroy(SPCanvasItem *object)
+{
+ g_signal_handlers_destroy(object);
+}
+
+void sp_canvas_item_finalize(GObject *gobject)
+{
+ SPCanvasItem *object = SP_CANVAS_ITEM(gobject);
+
+ if (g_object_is_floating (object))
+ {
+ g_warning ("A floating object was finalized. This means that someone\n"
+ "called g_object_unref() on an object that had only a floating\n"
+ "reference; the initial floating reference is not owned by anyone\n"
+ "and must be removed with g_object_ref_sink().");
+ }
+
+ G_OBJECT_CLASS (sp_canvas_item_parent_class)->finalize (gobject);
+}
+} // namespace
+
+/**
+ * Helper function to update item and its children.
+ *
+ * NB! affine is parent2canvas.
+ */
+static void sp_canvas_item_invoke_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ // Apply the child item's transform
+ Geom::Affine child_affine = item->xform * affine;
+
+ // apply object flags to child flags
+ int child_flags = flags & ~SP_CANVAS_UPDATE_REQUESTED;
+
+ if (item->need_update) {
+ child_flags |= SP_CANVAS_UPDATE_REQUESTED;
+ }
+
+ if (item->need_affine) {
+ child_flags |= SP_CANVAS_UPDATE_AFFINE;
+ }
+
+ if (child_flags & (SP_CANVAS_UPDATE_REQUESTED | SP_CANVAS_UPDATE_AFFINE)) {
+ if (SP_CANVAS_ITEM_GET_CLASS (item)->update) {
+ SP_CANVAS_ITEM_GET_CLASS (item)->update(item, child_affine, child_flags);
+ }
+ }
+
+ item->need_update = FALSE;
+ item->need_affine = FALSE;
+}
+
+/**
+ * Helper function to invoke the point method of the item.
+ *
+ * The argument x, y should be in the parent's item-relative coordinate
+ * system. This routine applies the _split_inverse of the item's transform,
+ * maintaining the affine invariant.
+ */
+static double sp_canvas_item_invoke_point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ if (SP_CANVAS_ITEM_GET_CLASS(item)->point) {
+ return SP_CANVAS_ITEM_GET_CLASS (item)->point (item, p, actual_item);
+ }
+
+ return Geom::infinity();
+}
+
+/**
+ * Makes the item's affine transformation matrix be equal to the specified
+ * matrix.
+ *
+ * @item: A canvas item.
+ * @affine: An affine transformation matrix.
+ */
+void sp_canvas_item_affine_absolute(SPCanvasItem *item, Geom::Affine const &affine)
+{
+ item->xform = affine;
+
+ if (!item->need_affine) {
+ item->need_affine = TRUE;
+ if (item->parent != nullptr) {
+ sp_canvas_item_request_update (item->parent);
+ } else {
+ item->canvas->requestUpdate();
+ }
+ }
+
+ item->canvas->_need_repick = TRUE;
+}
+
+/**
+ * Raises the item in its parent's stack by the specified number of positions.
+ *
+ * @param item A canvas item.
+ * @param positions Number of steps to raise the item.
+ *
+ * If the number of positions is greater than the distance to the top of the
+ * stack, then the item is put at the top.
+ */
+void sp_canvas_item_raise(SPCanvasItem *item, int positions)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (positions >= 0);
+
+ if (!item->parent || positions == 0) {
+ return;
+ }
+
+ SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent);
+ auto from = parent->items.iterator_to(*item);
+ auto to = from;
+
+ for (int i = 0; i <= positions && to != parent->items.end(); ++i) {
+ ++to;
+ }
+
+ parent->items.erase(from);
+ parent->items.insert(to, *item);
+
+ redraw_if_visible (item);
+ item->canvas->_need_repick = TRUE;
+}
+
+void sp_canvas_item_raise_to_top(SPCanvasItem *item)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ if (!item->parent)
+ return;
+ SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent);
+ parent->items.erase(parent->items.iterator_to(*item));
+ parent->items.push_back(*item);
+ redraw_if_visible (item);
+ item->canvas->_need_repick = TRUE;
+}
+
+
+
+/**
+ * Lowers the item in its parent's stack by the specified number of positions.
+ *
+ * @param item A canvas item.
+ * @param positions Number of steps to lower the item.
+ *
+ * If the number of positions is greater than the distance to the bottom of the
+ * stack, then the item is put at the bottom.
+ */
+void sp_canvas_item_lower(SPCanvasItem *item, int positions)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (positions >= 1);
+
+ SPCanvasGroup *parent = SP_CANVAS_GROUP(item->parent);
+
+ if (!parent || positions == 0 || item == &parent->items.front()) {
+ return;
+ }
+
+ auto from = parent->items.iterator_to(*item);
+ auto to = from;
+ g_assert(from != parent->items.end());
+
+ for (int i = 0; i < positions && to != parent->items.begin(); ++i) {
+ --to;
+ }
+
+ parent->items.erase(from);
+ parent->items.insert(to, *item);
+
+ redraw_if_visible (item);
+ item->canvas->_need_repick = TRUE;
+}
+
+void sp_canvas_item_lower_to_bottom(SPCanvasItem *item)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ if (!item->parent)
+ return;
+ SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent);
+ parent->items.erase(parent->items.iterator_to(*item));
+ parent->items.push_front(*item);
+ redraw_if_visible (item);
+ item->canvas->_need_repick = TRUE;
+}
+
+bool sp_canvas_item_is_visible(SPCanvasItem *item)
+{
+ return item->visible;
+}
+
+/**
+ * Sets visible flag on item and requests a redraw.
+ */
+void sp_canvas_item_show(SPCanvasItem *item)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (item->visible) {
+ return;
+ }
+
+ item->visible = TRUE;
+
+ int x0 = (int)(item->x1);
+ int x1 = (int)(item->x2);
+ int y0 = (int)(item->y1);
+ int y1 = (int)(item->y2);
+
+ if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+ item->canvas->requestRedraw((int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+ item->canvas->_need_repick = TRUE;
+ }
+}
+
+/**
+ * Clears visible flag on item and requests a redraw.
+ */
+void sp_canvas_item_hide(SPCanvasItem *item)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (!item->visible) {
+ return;
+ }
+
+ item->visible = FALSE;
+
+ int x0 = (int)(item->x1);
+ int x1 = (int)(item->x2);
+ int y0 = (int)(item->y1);
+ int y1 = (int)(item->y2);
+
+ if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1));
+ item->canvas->_need_repick = TRUE;
+ }
+}
+
+/**
+ * Grab item under cursor.
+ *
+ * \pre !canvas->grabbed_item && item->flags & SP_CANVAS_ITEM_VISIBLE
+ */
+int sp_canvas_item_grab(SPCanvasItem *item, guint event_mask, GdkCursor *cursor, guint32 etime)
+{
+ g_return_val_if_fail (item != nullptr, -1);
+ g_return_val_if_fail (SP_IS_CANVAS_ITEM (item), -1);
+ g_return_val_if_fail (gtk_widget_get_mapped (GTK_WIDGET (item->canvas)), -1);
+
+ if (item->canvas->_grabbed_item) {
+ return -1;
+ }
+
+ // This test disallows grabbing events by an invisible item, which may be useful
+ // sometimes. An example is the hidden control point used for the selector component,
+ // where it is used for object selection and rubberbanding. There seems to be nothing
+ // preventing this except this test, so I removed it.
+ // -- Krzysztof KosiƄski, 2009.08.12
+ //if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
+ // return -1;
+
+ if (HAS_BROKEN_MOTION_HINTS) {
+ event_mask &= ~GDK_POINTER_MOTION_HINT_MASK;
+ }
+
+ // fixme: Top hack (Lauris)
+ // fixme: If we add key masks to event mask, Gdk will abort (Lauris)
+ // fixme: But Canvas actually does get key events, so all we need is routing these here
+ auto display = gdk_display_get_default();
+ auto seat = gdk_display_get_default_seat(display);
+ gdk_seat_grab(seat,
+ getWindow(item->canvas),
+ GDK_SEAT_CAPABILITY_ALL_POINTING,
+ FALSE,
+ cursor,
+ nullptr,
+ nullptr,
+ nullptr);
+
+ item->canvas->_grabbed_item = item;
+ item->canvas->_grabbed_event_mask = event_mask;
+ item->canvas->_current_item = item; // So that events go to the grabbed item
+
+ return 0;
+}
+
+/**
+ * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
+ * mouse.
+ *
+ * @param item A canvas item that holds a grab.
+ */
+void sp_canvas_item_ungrab(SPCanvasItem *item)
+{
+ g_return_if_fail (item != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (item->canvas->_grabbed_item != item) {
+ return;
+ }
+
+ item->canvas->_grabbed_item = nullptr;
+ ungrab_default_client_pointer();
+}
+
+/**
+ * Returns the product of all transformation matrices from the root item down
+ * to the item.
+ */
+Geom::Affine sp_canvas_item_i2w_affine(SPCanvasItem const *item)
+{
+ g_assert (SP_IS_CANVAS_ITEM (item)); // should we get this?
+
+ Geom::Affine affine = Geom::identity();
+
+ while (item) {
+ affine *= item->xform;
+ item = item->parent;
+ }
+ return affine;
+}
+
+namespace {
+
+bool is_descendant(SPCanvasItem const *item, SPCanvasItem const *parent)
+{
+ while (item) {
+ if (item == parent) {
+ return true;
+ }
+ item = item->parent;
+ }
+
+ return false;
+}
+
+} // namespace
+
+/**
+ * Requests that the canvas queue an update for the specified item.
+ *
+ * To be used only by item implementations.
+ */
+void sp_canvas_item_request_update(SPCanvasItem *item)
+{
+ if (item->need_update) {
+ return;
+ }
+
+ item->need_update = TRUE;
+
+ if (item->parent != nullptr) {
+ // Recurse up the tree
+ sp_canvas_item_request_update (item->parent);
+ } else {
+ // Have reached the top of the tree, make sure the update call gets scheduled.
+ item->canvas->requestUpdate();
+ }
+}
+
+/**
+ * Returns position of item in group.
+ */
+gint sp_canvas_item_order (SPCanvasItem * item)
+{
+ SPCanvasGroup * p = SP_CANVAS_GROUP(item->parent);
+ size_t index = 0;
+ for (auto it = p->items.begin(); it != p->items.end(); ++it, ++index) {
+ if (item == &(*it)) {
+ return index;
+ }
+ }
+
+ return -1;
+}
+
+// SPCanvasGroup
+G_DEFINE_TYPE(SPCanvasGroup, sp_canvas_group, SP_TYPE_CANVAS_ITEM);
+
+static void sp_canvas_group_class_init(SPCanvasGroupClass *klass)
+{
+ SPCanvasItemClass *item_class = reinterpret_cast<SPCanvasItemClass *>(klass);
+
+ item_class->destroy = SPCanvasGroup::destroy;
+ item_class->update = SPCanvasGroup::update;
+ item_class->render = SPCanvasGroup::render;
+ item_class->point = SPCanvasGroup::point;
+ item_class->viewbox_changed = SPCanvasGroup::viewboxChanged;
+}
+
+static void sp_canvas_group_init(SPCanvasGroup * group)
+{
+ new (&group->items) SPCanvasItemList;
+}
+
+void SPCanvasGroup::destroy(SPCanvasItem *object)
+{
+ g_return_if_fail(object != nullptr);
+ g_return_if_fail(SP_IS_CANVAS_GROUP(object));
+
+ SPCanvasGroup *group = SP_CANVAS_GROUP(object);
+
+ for (auto it = group->items.begin(); it != group->items.end();) {
+ SPCanvasItem *item = &(*it);
+ it++;
+ sp_canvas_item_destroy(item);
+ }
+
+ group->items.clear();
+ group->items.~SPCanvasItemList(); // invoke manually
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_group_parent_class)->destroy) {
+ (* SP_CANVAS_ITEM_CLASS(sp_canvas_group_parent_class)->destroy)(object);
+ }
+}
+
+void SPCanvasGroup::update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCanvasGroup *group = SP_CANVAS_GROUP(item);
+ Geom::OptRect bounds;
+
+ for (auto & item : group->items) {
+ SPCanvasItem *i = &item;
+
+ sp_canvas_item_invoke_update (i, affine, flags);
+
+ if ( (i->x2 > i->x1) && (i->y2 > i->y1) ) {
+ bounds.expandTo(Geom::Point(i->x1, i->y1));
+ bounds.expandTo(Geom::Point(i->x2, i->y2));
+ }
+ }
+
+ if (bounds) {
+ item->x1 = bounds->min()[Geom::X];
+ item->y1 = bounds->min()[Geom::Y];
+ item->x2 = bounds->max()[Geom::X];
+ item->y2 = bounds->max()[Geom::Y];
+ } else {
+ // FIXME ?
+ item->x1 = item->x2 = item->y1 = item->y2 = 0;
+ }
+}
+
+double SPCanvasGroup::point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasGroup *group = SP_CANVAS_GROUP(item);
+ double const x = p[Geom::X];
+ double const y = p[Geom::Y];
+ int x1 = (int)(x - item->canvas->_close_enough);
+ int y1 = (int)(y - item->canvas->_close_enough);
+ int x2 = (int)(x + item->canvas->_close_enough);
+ int y2 = (int)(y + item->canvas->_close_enough);
+
+ double best = 0.0;
+ *actual_item = nullptr;
+
+ double dist = 0.0;
+ for (auto & it : group->items) {
+ SPCanvasItem *child = &it;
+
+ if ((child->x1 <= x2) && (child->y1 <= y2) && (child->x2 >= x1) && (child->y2 >= y1)) {
+ SPCanvasItem *point_item = nullptr; // cater for incomplete item implementations
+
+ int pickable;
+ if (child->visible && child->pickable && SP_CANVAS_ITEM_GET_CLASS(child)->point) {
+ dist = sp_canvas_item_invoke_point(child, p, &point_item);
+ pickable = TRUE;
+ } else {
+ pickable = FALSE;
+ }
+
+ // TODO: This metric should be improved, because in case of (partly) overlapping items we will now
+ // always select the last one that has been added to the group. We could instead select the one
+ // of which the center is the closest, for example. One can then move to the center
+ // of the item to be focused, and have that one selected. Of course this will only work if the
+ // centers are not coincident, but at least it's better than what we have now.
+ // See the extensive comment in Inkscape::SelTrans::_updateHandles()
+ if (pickable && point_item && ((int) (dist + 0.5) <= item->canvas->_close_enough)) {
+ best = dist;
+ *actual_item = point_item;
+ }
+ }
+ }
+
+ return best;
+}
+
+void SPCanvasGroup::render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCanvasGroup *group = SP_CANVAS_GROUP(item);
+
+ for (auto & item : group->items) {
+ SPCanvasItem *child = &item;
+ if (child->visible) {
+ if ((child->x1 < buf->rect.right()) &&
+ (child->y1 < buf->rect.bottom()) &&
+ (child->x2 > buf->rect.left()) &&
+ (child->y2 > buf->rect.top())) {
+ if (SP_CANVAS_ITEM_GET_CLASS(child)->render) {
+ SP_CANVAS_ITEM_GET_CLASS(child)->render(child, buf);
+ }
+ }
+ }
+ }
+}
+
+void SPCanvasGroup::viewboxChanged(SPCanvasItem *item, Geom::IntRect const &new_area)
+{
+ SPCanvasGroup *group = SP_CANVAS_GROUP(item);
+ for (auto & item : group->items) {
+ SPCanvasItem *child = &item;
+ if (child->visible) {
+ if (SP_CANVAS_ITEM_GET_CLASS(child)->viewbox_changed) {
+ SP_CANVAS_ITEM_GET_CLASS(child)->viewbox_changed(child, new_area);
+ }
+ }
+ }
+}
+
+void SPCanvasGroup::add(SPCanvasItem *item)
+{
+ g_object_ref(item);
+ g_object_ref_sink(item);
+
+ items.push_back(*item);
+
+ sp_canvas_item_request_update(item);
+}
+
+void SPCanvasGroup::remove(SPCanvasItem *item)
+{
+ g_return_if_fail(item != nullptr);
+
+ auto position = items.iterator_to(*item);
+ if (position != items.end()) {
+ items.erase(position);
+ }
+
+ // Unparent the child
+ item->parent = nullptr;
+ g_object_unref(item);
+
+}
+
+G_DEFINE_TYPE(SPCanvas, sp_canvas, GTK_TYPE_WIDGET);
+
+static void sp_canvas_finalize(GObject *object)
+{
+ SPCanvas *canvas = SP_CANVAS(object);
+
+#if defined(HAVE_LIBLCMS2)
+ using S = decltype(canvas->_cms_key);
+ canvas->_cms_key.~S();
+#endif
+
+ G_OBJECT_CLASS(sp_canvas_parent_class)->finalize(object);
+}
+
+void sp_canvas_class_init(SPCanvasClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ object_class->dispose = SPCanvas::dispose;
+ object_class->finalize = sp_canvas_finalize;
+
+ widget_class->realize = SPCanvas::handle_realize;
+ widget_class->unrealize = SPCanvas::handle_unrealize;
+ widget_class->get_preferred_width = SPCanvas::handle_get_preferred_width;
+ widget_class->get_preferred_height = SPCanvas::handle_get_preferred_height;
+ widget_class->draw = SPCanvas::handle_draw;
+ widget_class->size_allocate = SPCanvas::handle_size_allocate;
+ widget_class->button_press_event = SPCanvas::handle_button;
+ widget_class->button_release_event = SPCanvas::handle_button;
+ widget_class->motion_notify_event = SPCanvas::handle_motion;
+ widget_class->scroll_event = SPCanvas::handle_scroll;
+ widget_class->key_press_event = SPCanvas::handle_key_event;
+ widget_class->key_release_event = SPCanvas::handle_key_event;
+ widget_class->enter_notify_event = SPCanvas::handle_crossing;
+ widget_class->leave_notify_event = SPCanvas::handle_crossing;
+ widget_class->focus_in_event = SPCanvas::handle_focus_in;
+ widget_class->focus_out_event = SPCanvas::handle_focus_out;
+}
+
+static void sp_canvas_init(SPCanvas *canvas)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (canvas), TRUE);
+ gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE);
+
+ canvas->_pick_event.type = GDK_LEAVE_NOTIFY;
+ canvas->_pick_event.crossing.x = 0;
+ canvas->_pick_event.crossing.y = 0;
+
+ // Create the root item as a special case
+ canvas->_root = SP_CANVAS_ITEM(g_object_new(SP_TYPE_CANVAS_GROUP, nullptr));
+ canvas->_root->canvas = canvas;
+
+ g_object_ref (canvas->_root);
+ g_object_ref_sink (canvas->_root);
+
+ canvas->_need_repick = TRUE;
+
+ // See comment at in sp-canvas.h.
+ canvas->_gen_all_enter_events = false;
+
+ canvas->_drawing_disabled = false;
+
+ canvas->_backing_store = nullptr;
+ canvas->_surface_for_similar = nullptr;
+ canvas->_clean_region = cairo_region_create();
+ canvas->_background = cairo_pattern_create_rgb(1, 1, 1);
+ canvas->_background_is_checkerboard = false;
+
+ canvas->_forced_redraw_count = 0;
+ canvas->_forced_redraw_limit = -1;
+ canvas->_in_full_redraw = false;
+ // Split view controls
+ canvas->_spliter = Geom::OptIntRect();
+ canvas->_spliter_area = Geom::OptIntRect();
+ canvas->_spliter_control = Geom::OptIntRect();
+ canvas->_spliter_top = Geom::OptIntRect();
+ canvas->_spliter_bottom = Geom::OptIntRect();
+ canvas->_spliter_left = Geom::OptIntRect();
+ canvas->_spliter_right = Geom::OptIntRect();
+ canvas->_spliter_control_pos = Geom::Point();
+ canvas->_spliter_in_control_pos = Geom::Point();
+ canvas->_xray_rect = Geom::OptIntRect();
+ canvas->_split_value = 0.5;
+ canvas->_split_vertical = true;
+ canvas->_split_inverse = false;
+ canvas->_split_hover_vertical = false;
+ canvas->_split_hover_horizontal = false;
+ canvas->_split_hover = false;
+ canvas->_split_pressed = false;
+ canvas->_split_control_pressed = false;
+ canvas->_split_dragging = false;
+ canvas->_xray_radius = 100;
+ canvas->_xray = false;
+ canvas->_xray_orig = Geom::Point();
+ canvas->_changecursor = 0;
+ canvas->_splits = 0;
+ canvas->_totalelapsed = 0;
+ canvas->_idle_time = g_get_monotonic_time();
+ canvas->_is_dragging = false;
+
+#if defined(HAVE_LIBLCMS2)
+ canvas->_enable_cms_display_adj = false;
+ new (&canvas->_cms_key) Glib::ustring("");
+#endif // defined(HAVE_LIBLCMS2)
+}
+
+void SPCanvas::shutdownTransients()
+{
+ // Reset the clean region
+ dirtyAll();
+
+ if (_grabbed_item) {
+ _grabbed_item = nullptr;
+ ungrab_default_client_pointer();
+ }
+ removeIdle();
+}
+
+void SPCanvas::dispose(GObject *object)
+{
+ SPCanvas *canvas = SP_CANVAS(object);
+
+ if (canvas->_root) {
+ g_object_unref (canvas->_root);
+ canvas->_root = nullptr;
+ }
+ if (canvas->_backing_store) {
+ cairo_surface_destroy(canvas->_backing_store);
+ canvas->_backing_store = nullptr;
+ }
+ if (canvas->_surface_for_similar) {
+ cairo_surface_destroy(canvas->_surface_for_similar);
+ canvas->_surface_for_similar = nullptr;
+ }
+ if (canvas->_clean_region) {
+ cairo_region_destroy(canvas->_clean_region);
+ canvas->_clean_region = nullptr;
+ }
+ if (canvas->_background) {
+ cairo_pattern_destroy(canvas->_background);
+ canvas->_background = nullptr;
+ }
+
+ canvas->shutdownTransients();
+ if (G_OBJECT_CLASS(sp_canvas_parent_class)->dispose) {
+ (* G_OBJECT_CLASS(sp_canvas_parent_class)->dispose)(object);
+ }
+}
+
+namespace {
+
+void trackLatency(GdkEvent const *event)
+{
+ GdkEventLatencyTracker &tracker = GdkEventLatencyTracker::default_tracker();
+ boost::optional<double> latency = tracker.process(event);
+ if (latency && *latency > 2.0) {
+ //g_warning("Event latency reached %f sec (%1.4f)", *latency, tracker.getSkew());
+ }
+}
+
+} // namespace
+
+GtkWidget *SPCanvas::createAA()
+{
+ SPCanvas *canvas = SP_CANVAS(g_object_new(SP_TYPE_CANVAS, nullptr));
+ return GTK_WIDGET(canvas);
+}
+
+void SPCanvas::handle_realize(GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+ GtkAllocation allocation;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ gtk_widget_get_allocation (widget, &allocation);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+
+ attributes.event_mask = (gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ ( HAS_BROKEN_MOTION_HINTS ?
+ 0 : GDK_POINTER_MOTION_HINT_MASK ) |
+ GDK_PROXIMITY_IN_MASK |
+ GDK_PROXIMITY_OUT_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_SCROLL_MASK |
+ GDK_SMOOTH_SCROLL_MASK |
+ GDK_FOCUS_CHANGE_MASK);
+
+ gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ GdkWindow *window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+ gtk_widget_set_window (widget, window);
+ gdk_window_set_user_data (window, widget);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/options/useextinput/value", true)) {
+ gtk_widget_set_events(widget, attributes.event_mask);
+ }
+
+ gtk_widget_set_realized (widget, TRUE);
+}
+
+void SPCanvas::handle_unrealize(GtkWidget *widget)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ canvas->_current_item = nullptr;
+ canvas->_grabbed_item = nullptr;
+ canvas->_focused_item = nullptr;
+
+ canvas->shutdownTransients();
+
+ if (GTK_WIDGET_CLASS(sp_canvas_parent_class)->unrealize)
+ (* GTK_WIDGET_CLASS(sp_canvas_parent_class)->unrealize)(widget);
+}
+
+void SPCanvas::handle_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width)
+{
+ static_cast<void>(SP_CANVAS (widget));
+ *minimum_width = 256;
+ *natural_width = 256;
+}
+
+void SPCanvas::handle_get_preferred_height(GtkWidget *widget, gint *minimum_height, gint *natural_height)
+{
+ static_cast<void>(SP_CANVAS (widget));
+ *minimum_height = 256;
+ *natural_height = 256;
+}
+
+void SPCanvas::handle_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+ // Allocation does not depend on device scale.
+ GtkAllocation old_allocation;
+ gtk_widget_get_allocation(widget, &old_allocation);
+
+ // For HiDPI monitors.
+ canvas->_device_scale = gtk_widget_get_scale_factor( widget );
+
+ Geom::IntRect new_area = Geom::IntRect::from_xywh(canvas->_x0, canvas->_y0,
+ allocation->width, allocation->height);
+
+ // Resize backing store.
+ cairo_surface_t *new_backing_store = nullptr;
+ if (canvas->_surface_for_similar != nullptr) {
+
+ // Size in device pixels. Does not set device scale.
+ new_backing_store =
+ cairo_surface_create_similar_image(canvas->_surface_for_similar,
+ CAIRO_FORMAT_ARGB32,
+ allocation->width * canvas->_device_scale,
+ allocation->height * canvas->_device_scale);
+ }
+ if (new_backing_store == nullptr) {
+
+ // Size in device pixels. Does not set device scale.
+ new_backing_store =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ allocation->width * canvas->_device_scale,
+ allocation->height * canvas->_device_scale);
+ }
+
+ // Set device scale
+ cairo_surface_set_device_scale(new_backing_store, canvas->_device_scale, canvas->_device_scale);
+
+ if (canvas->_backing_store) {
+ cairo_t *cr = cairo_create(new_backing_store);
+ cairo_translate(cr, -canvas->_x0, -canvas->_y0);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source(cr, canvas->_background);
+ cairo_paint(cr);
+ cairo_set_source_surface(cr, canvas->_backing_store, canvas->_x0, canvas->_y0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ cairo_surface_destroy(canvas->_backing_store);
+ }
+ canvas->_backing_store = new_backing_store;
+
+ // Clip the clean region to the new allocation
+ cairo_rectangle_int_t crect = { canvas->_x0, canvas->_y0, allocation->width, allocation->height };
+ cairo_region_intersect_rectangle(canvas->_clean_region, &crect);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (SP_CANVAS_ITEM_GET_CLASS (canvas->_root)->viewbox_changed)
+ SP_CANVAS_ITEM_GET_CLASS (canvas->_root)->viewbox_changed (canvas->_root, new_area);
+
+ if (gtk_widget_get_realized (widget)) {
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ }
+ // Schedule redraw of any newly exposed regions
+ canvas->_split_value = 0.5;
+ canvas->_spliter_control_pos = Geom::Point();
+ canvas->requestFullRedraw();
+}
+
+int SPCanvas::emitEvent(GdkEvent *event)
+{
+ guint mask;
+
+ if (_grabbed_item) {
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ mask = GDK_ENTER_NOTIFY_MASK;
+ break;
+ case GDK_LEAVE_NOTIFY:
+ mask = GDK_LEAVE_NOTIFY_MASK;
+ break;
+ case GDK_MOTION_NOTIFY:
+ mask = GDK_POINTER_MOTION_MASK;
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ mask = GDK_BUTTON_PRESS_MASK;
+ break;
+ case GDK_BUTTON_RELEASE:
+ mask = GDK_BUTTON_RELEASE_MASK;
+ break;
+ case GDK_KEY_PRESS:
+ mask = GDK_KEY_PRESS_MASK;
+ break;
+ case GDK_KEY_RELEASE:
+ mask = GDK_KEY_RELEASE_MASK;
+ break;
+ case GDK_SCROLL:
+ mask = GDK_SCROLL_MASK;
+ mask |= GDK_SMOOTH_SCROLL_MASK;
+ break;
+ default:
+ mask = 0;
+ break;
+ }
+
+ if (!(mask & _grabbed_event_mask)) return FALSE;
+ }
+
+ // Convert to world coordinates -- we have two cases because of different
+ // offsets of the fields in the event structures.
+
+ GdkEvent *ev = gdk_event_copy(event);
+ switch (ev->type) {
+ case GDK_ENTER_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ ev->crossing.x += _x0;
+ ev->crossing.y += _y0;
+ break;
+ case GDK_MOTION_NOTIFY:
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ ev->motion.x += _x0;
+ ev->motion.y += _y0;
+ break;
+ default:
+ break;
+ }
+ // Block Undo and Redo while we drag /anything/
+ if(event->type == GDK_BUTTON_PRESS && event->button.button == 1)
+ _is_dragging = true;
+ else if(event->type == GDK_BUTTON_RELEASE)
+ _is_dragging = false;
+
+ // Choose where we send the event
+
+ // canvas->current_item becomes NULL in some cases under Win32
+ // (e.g. if the pointer leaves the window). So this is a hack that
+ // Lauris applied to SP to get around the problem.
+ //
+ SPCanvasItem* item = nullptr;
+ if (_grabbed_item && !is_descendant(_current_item, _grabbed_item)) {
+ item = _grabbed_item;
+ } else {
+ item = _current_item;
+ }
+
+ if (_focused_item &&
+ ((event->type == GDK_KEY_PRESS) ||
+ (event->type == GDK_KEY_RELEASE) ||
+ (event->type == GDK_FOCUS_CHANGE))) {
+ item = _focused_item;
+ }
+
+ // The event is propagated up the hierarchy (for if someone connected to
+ // a group instead of a leaf event), and emission is stopped if a
+ // handler returns TRUE, just like for GtkWidget events.
+
+ gint finished = FALSE;
+
+ while (item && !finished) {
+ g_object_ref (item);
+ g_signal_emit (G_OBJECT (item), item_signals[ITEM_EVENT], 0, ev, &finished);
+ SPCanvasItem *parent = item->parent;
+ g_object_unref (item);
+ item = parent;
+ }
+
+ gdk_event_free(ev);
+
+ return finished;
+}
+
+int SPCanvas::pickCurrentItem(GdkEvent *event)
+{
+ int button_down = 0;
+
+ if (!_root) // canvas may have already be destroyed by closing desktop during interrupted display!
+ return FALSE;
+
+ int retval = FALSE;
+
+ if (_gen_all_enter_events == false) {
+ // If a button is down, we'll perform enter and leave events on the
+ // current item, but not enter on any other item. This is more or
+ // less like X pointer grabbing for canvas items.
+ //
+ button_down = _state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK |
+ GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | GDK_BUTTON5_MASK);
+
+ if (!button_down) _left_grabbed_item = FALSE;
+ }
+
+ // Save the event in the canvas. This is used to synthesize enter and
+ // leave events in case the current item changes. It is also used to
+ // re-pick the current item if the current one gets deleted. Also,
+ // synthesize an enter event.
+
+ if (event != &_pick_event) {
+ if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) {
+ // these fields have the same offsets in both types of events
+
+ _pick_event.crossing.type = GDK_ENTER_NOTIFY;
+ _pick_event.crossing.window = event->motion.window;
+ _pick_event.crossing.send_event = event->motion.send_event;
+ _pick_event.crossing.subwindow = nullptr;
+ _pick_event.crossing.x = event->motion.x;
+ _pick_event.crossing.y = event->motion.y;
+ _pick_event.crossing.mode = GDK_CROSSING_NORMAL;
+ _pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR;
+ _pick_event.crossing.focus = FALSE;
+ _pick_event.crossing.state = event->motion.state;
+
+ // these fields don't have the same offsets in both types of events
+
+ if (event->type == GDK_MOTION_NOTIFY) {
+ _pick_event.crossing.x_root = event->motion.x_root;
+ _pick_event.crossing.y_root = event->motion.y_root;
+ } else {
+ _pick_event.crossing.x_root = event->button.x_root;
+ _pick_event.crossing.y_root = event->button.y_root;
+ }
+ } else {
+ _pick_event = *event;
+ }
+ }
+
+ // Don't do anything else if this is a recursive call
+ if (_in_repick) {
+ return retval;
+ }
+
+ // LeaveNotify means that there is no current item, so we don't look for one
+ if (_pick_event.type != GDK_LEAVE_NOTIFY) {
+ // these fields don't have the same offsets in both types of events
+ double x, y;
+
+ if (_pick_event.type == GDK_ENTER_NOTIFY) {
+ x = _pick_event.crossing.x;
+ y = _pick_event.crossing.y;
+ } else {
+ x = _pick_event.motion.x;
+ y = _pick_event.motion.y;
+ }
+
+ // world coords
+ x += _x0;
+ y += _y0;
+
+ // find the closest item
+ if (_root->visible) {
+ sp_canvas_item_invoke_point (_root, Geom::Point(x, y), &_new_current_item);
+ } else {
+ _new_current_item = nullptr;
+ }
+ } else {
+ _new_current_item = nullptr;
+ }
+
+ if ((_new_current_item == _current_item) && !_left_grabbed_item) {
+ return retval; // current item did not change
+ }
+
+ // Synthesize events for old and new current items
+
+ if ((_new_current_item != _current_item) &&
+ _current_item != nullptr && !_left_grabbed_item)
+ {
+ GdkEvent new_event;
+
+ new_event = _pick_event;
+ new_event.type = GDK_LEAVE_NOTIFY;
+
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = nullptr;
+ _in_repick = TRUE;
+ retval = emitEvent(&new_event);
+ _in_repick = FALSE;
+ }
+
+ if (_gen_all_enter_events == false) {
+ // new_current_item may have been set to NULL during the call to
+ // emitEvent() above
+ if ((_new_current_item != _current_item) && button_down) {
+ _left_grabbed_item = TRUE;
+ return retval;
+ }
+ }
+
+ // Handle the rest of cases
+ _left_grabbed_item = FALSE;
+ _current_item = _new_current_item;
+
+ if (_current_item != nullptr) {
+ GdkEvent new_event;
+
+ new_event = _pick_event;
+ new_event.type = GDK_ENTER_NOTIFY;
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = nullptr;
+ retval = emitEvent(&new_event);
+ }
+
+ return retval;
+}
+
+gint SPCanvas::handle_doubleclick(GtkWidget *widget, GdkEventButton *event)
+{
+ SPCanvas *canvas = SP_CANVAS(widget);
+ // Maybe we want to use double click on canvas so retain here
+ return 0;
+}
+
+gint SPCanvas::handle_button(GtkWidget *widget, GdkEventButton *event)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ int retval = FALSE;
+
+ // dispatch normally regardless of the event's window if an item
+ // has a pointer grab in effect
+ if (!canvas->_grabbed_item &&
+ event->window != getWindow(canvas))
+ return retval;
+
+ int mask;
+ switch (event->button) {
+ case 1:
+ mask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ mask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ mask = GDK_BUTTON3_MASK;
+ break;
+ case 4:
+ mask = GDK_BUTTON4_MASK;
+ break;
+ case 5:
+ mask = GDK_BUTTON5_MASK;
+ break;
+ default:
+ mask = 0;
+ }
+ static unsigned next_canvas_doubleclick = 0;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ // Pick the current item as if the button were not pressed, and
+ // then process the event.
+ next_canvas_doubleclick = 0;
+ if (!canvas->_split_hover) {
+ canvas->_state = event->state;
+ canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+ canvas->_state ^= mask;
+ retval = canvas->emitEvent((GdkEvent *)event);
+ } else {
+ canvas->_split_pressed = true;
+ Geom::IntPoint cursor_pos = Geom::IntPoint(event->x, event->y);
+ canvas->_spliter_in_control_pos = cursor_pos - (*canvas->_spliter_control).midpoint();
+ if (canvas->_spliter && canvas->_spliter_control.contains(cursor_pos) && !canvas->_is_dragging) {
+ canvas->_split_control_pressed = true;
+ }
+ retval = TRUE;
+ }
+ break;
+ case GDK_2BUTTON_PRESS:
+ // Pick the current item as if the button were not pressed, and
+ // then process the event.
+ next_canvas_doubleclick = reinterpret_cast<GdkEvent *>(event)->button.button;
+
+ if (!canvas->_split_hover) {
+ canvas->_state = event->state;
+ canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+ canvas->_state ^= mask;
+ retval = canvas->emitEvent((GdkEvent *)event);
+ } else {
+ canvas->_split_pressed = true;
+ retval = TRUE;
+ }
+ break;
+ case GDK_3BUTTON_PRESS:
+ // Pick the current item as if the button were not pressed, and
+ // then process the event.
+ if (!canvas->_split_hover) {
+ canvas->_state = event->state;
+ canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+ canvas->_state ^= mask;
+ retval = canvas->emitEvent((GdkEvent *)event);
+ } else {
+ canvas->_split_pressed = true;
+ retval = TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ // Process the event as if the button were pressed, then repick
+ // after the button has been released
+ canvas->_split_pressed = false;
+ if (next_canvas_doubleclick) {
+ GdkEventButton *event2 = reinterpret_cast<GdkEventButton *>(event);
+ handle_doubleclick(GTK_WIDGET(canvas), event2);
+ }
+ if (canvas->_split_hover) {
+ retval = TRUE;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool spliter_clicked = false;
+ bool reset = false;
+ if (!canvas->_split_dragging) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(canvas), &allocation);
+ Geom::Point pos = canvas->_spliter_control_pos;
+ double value = canvas->_split_vertical ? 1 / (allocation.height / (double)pos[Geom::Y])
+ : 1 / (allocation.width / (double)pos[Geom::X]);
+ if (canvas->_split_hover_vertical) {
+ canvas->_split_inverse = !canvas->_split_inverse;
+ spliter_clicked = true;
+ reset = canvas->_split_vertical ? true : false;
+ if (reset) {
+ canvas->_split_value = value;
+ }
+ canvas->_split_vertical = false;
+ } else if (canvas->_split_hover_horizontal) {
+ canvas->_split_inverse = !canvas->_split_inverse;
+ spliter_clicked = true;
+ reset = !canvas->_split_vertical ? true : false;
+ if (reset) {
+ canvas->_split_value = value;
+ }
+ canvas->_split_vertical = true;
+ }
+ if (spliter_clicked) {
+ canvas->requestFullRedraw();
+ }
+ }
+ canvas->_split_control_pressed = false;
+ canvas->_split_dragging = false;
+ } else {
+ canvas->_state = event->state;
+ retval = canvas->emitEvent((GdkEvent *)event);
+ event->state ^= mask;
+ canvas->_state = event->state;
+ canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+ event->state ^= mask;
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return retval;
+}
+
+gint SPCanvas::handle_scroll(GtkWidget *widget, GdkEventScroll *event)
+{
+ return SP_CANVAS(widget)->emitEvent(reinterpret_cast<GdkEvent *>(event));
+}
+
+static inline void request_motions(GdkWindow *w, GdkEventMotion *event) {
+ gdk_window_get_device_position(w,
+ gdk_event_get_device((GdkEvent *)(event)),
+ nullptr, nullptr, nullptr);
+ gdk_event_request_motions(event);
+}
+
+void SPCanvas::set_cursor(GtkWidget *widget)
+{
+ SPCanvas *canvas = SP_CANVAS(widget);
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ GdkDisplay *display = gdk_display_get_default();
+ GdkCursor *cursor = nullptr;
+ if (canvas->_split_hover_vertical) {
+ if (canvas->_changecursor != 1) {
+ cursor = gdk_cursor_new_from_name(display, "pointer");
+ gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
+ g_object_unref(cursor);
+ canvas->paintSpliter();
+ canvas->_changecursor = 1;
+ }
+ } else if (canvas->_split_hover_horizontal) {
+ if (canvas->_changecursor != 2) {
+ cursor = gdk_cursor_new_from_name(display, "pointer");
+ gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
+ g_object_unref(cursor);
+ canvas->paintSpliter();
+ canvas->_changecursor = 2;
+ }
+ } else if (canvas->_split_hover) {
+ if (canvas->_changecursor != 3) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (_split_vertical) {
+ cursor = gdk_cursor_new_from_name(display, "ew-resize");
+ } else {
+ cursor = gdk_cursor_new_from_name(display, "ns-resize");
+ }
+ gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
+ g_object_unref(cursor);
+ canvas->paintSpliter();
+ canvas->_changecursor = 3;
+ }
+ } else {
+ if (desktop &&
+ desktop->event_context &&
+ !canvas->_split_pressed &&
+ (canvas->_changecursor != 0 && canvas->_changecursor != 4))
+ {
+ desktop->event_context->sp_event_context_update_cursor();
+ canvas->paintSpliter();
+ canvas->_changecursor = 4;
+ }
+ }
+}
+int SPCanvas::handle_motion(GtkWidget *widget, GdkEventMotion *event)
+{
+ int status;
+ SPCanvas *canvas = SP_CANVAS (widget);
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ trackLatency((GdkEvent *)event);
+
+ if (event->window != getWindow(canvas)) {
+ return FALSE;
+ }
+
+ if (canvas->_root == nullptr) // canvas being deleted
+ return FALSE;
+
+ Geom::IntPoint cursor_pos = Geom::IntPoint(event->x, event->y);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (desktop && desktop->splitMode()) {
+ if (canvas->_spliter &&
+ ((*canvas->_spliter).contains(cursor_pos) || canvas->_spliter_control.contains(cursor_pos)) &&
+ !canvas->_is_dragging) {
+ canvas->_split_hover = true;
+ } else {
+ canvas->_split_hover = false;
+ }
+ if (canvas->_spliter_left && canvas->_spliter_right &&
+ ((*canvas->_spliter_left).contains(cursor_pos) || (*canvas->_spliter_right).contains(cursor_pos)) &&
+ !canvas->_is_dragging) {
+ canvas->_split_hover_horizontal = true;
+ } else {
+ canvas->_split_hover_horizontal = false;
+ }
+ if (!canvas->_split_hover_horizontal && canvas->_spliter_top && canvas->_spliter_bottom &&
+ ((*canvas->_spliter_top).contains(cursor_pos) || (*canvas->_spliter_bottom).contains(cursor_pos)) &&
+ !canvas->_is_dragging) {
+ canvas->_split_hover_vertical = true;
+ } else {
+ canvas->_split_hover_vertical = false;
+ }
+
+ canvas->set_cursor(widget);
+ }
+ if (canvas->_split_pressed && desktop && desktop->event_context && desktop->splitMode()) {
+ GtkAllocation allocation;
+ canvas->_split_dragging = true;
+ gtk_widget_get_allocation(GTK_WIDGET(canvas), &allocation);
+ double hide_horiz = 1 / (allocation.width / (double)cursor_pos[Geom::X]);
+ double hide_vert = 1 / (allocation.height / (double)cursor_pos[Geom::Y]);
+ double value = canvas->_split_vertical ? hide_horiz : hide_vert;
+ if (hide_horiz < 0.03 || hide_horiz > 0.97 || hide_vert < 0.03 || hide_vert > 0.97) {
+ if (desktop && desktop->event_context) {
+ desktop->event_context->sp_event_context_update_cursor();
+ desktop->toggleSplitMode();
+ sp_reset_spliter(canvas);
+ }
+ } else {
+ canvas->_split_value = value;
+ if (canvas->_split_control_pressed && !canvas->_is_dragging) {
+ canvas->_spliter_control_pos = cursor_pos - canvas->_spliter_in_control_pos;
+ }
+ }
+ canvas->requestFullRedraw();
+ status = 1;
+ } else {
+ if (desktop && desktop->event_context && desktop->xrayMode()) {
+ sp_reset_spliter(canvas);
+ Geom::Point prev_orig = canvas->_xray_orig;
+ canvas->_xray_orig = desktop->point(true);
+ canvas->_xray_orig *= desktop->current_zoom();
+ if (!SP_ACTIVE_DOCUMENT->is_yaxisdown()) {
+ canvas->_xray_orig[Geom::Y] *= -1.0;
+ }
+ canvas->_xray = true;
+ if (canvas->_xray_orig[Geom::X] != Geom::infinity()) {
+ if (canvas->_xray_rect) {
+ canvas->dirtyRect(*canvas->_xray_rect);
+ canvas->_xray_rect = Geom::OptIntRect();
+ }
+ canvas->addIdle();
+ }
+ status = 1;
+ } else {
+ if (canvas->_xray_rect) {
+ canvas->dirtyRect(*canvas->_xray_rect);
+ canvas->_xray_rect = Geom::OptIntRect();
+ }
+ canvas->_xray = false;
+ }
+ canvas->_state = event->state;
+ canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+ status = canvas->emitEvent(reinterpret_cast<GdkEvent *>(event));
+ if (event->is_hint) {
+ request_motions(gtk_widget_get_window(widget), event);
+ }
+ }
+
+ if (desktop) {
+ SPCanvasArena *arena = SP_CANVAS_ARENA(desktop->drawing);
+ if (desktop->splitMode()) {
+ bool contains = canvas->_spliter_area.contains(cursor_pos);
+ bool setoutline = canvas->_split_inverse ? !contains : contains;
+ arena->drawing.setOutlineSensitive(setoutline);
+ } else if (canvas->_xray) {
+ arena->drawing.setOutlineSensitive(true);
+ } else {
+ arena->drawing.setOutlineSensitive(false);
+ }
+ }
+ return status;
+}
+
+void SPCanvas::paintSingleBuffer(Geom::IntRect const &paint_rect, Geom::IntRect const &canvas_rect, int /*sw*/)
+{
+
+ // Prevent crash if paintSingleBuffer is called before _backing_store is
+ // initialized.
+ if (_backing_store == nullptr)
+ return;
+
+ SPCanvasBuf buf;
+ buf.buf = nullptr;
+ buf.buf_rowstride = 0;
+ buf.rect = paint_rect;
+ buf.canvas_rect = canvas_rect;
+ buf.device_scale = _device_scale;
+ buf.is_empty = true;
+
+ // Make sure the following code does not go outside of _backing_store's data
+ // FIXME for device_scale.
+ assert(cairo_image_surface_get_format(_backing_store) == CAIRO_FORMAT_ARGB32);
+ assert(paint_rect.left() - _x0 >= 0);
+ assert(paint_rect.top() - _y0 >= 0);
+ assert(paint_rect.right() - _x0 <= cairo_image_surface_get_width(_backing_store));
+ assert(paint_rect.bottom() - _y0 <= cairo_image_surface_get_height(_backing_store));
+
+ // Create a temporary surface that draws directly to _backing_store
+ cairo_surface_flush(_backing_store);
+ // cairo_surface_write_to_png( _backing_store, "debug0.png" );
+ unsigned char *data = cairo_image_surface_get_data(_backing_store);
+ int stride = cairo_image_surface_get_stride(_backing_store);
+
+ // Check we are using correct device scale
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale(_backing_store, &x_scale, &y_scale);
+ assert (_device_scale == (int)x_scale);
+ assert (_device_scale == (int)y_scale);
+
+ // Move to the right row
+ data += stride * (paint_rect.top() - _y0) * (int)y_scale;
+ // Move to the right pixel inside of that row
+ data += 4 * (paint_rect.left() - _x0) * (int)x_scale;
+ cairo_surface_t *imgs =
+ cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32,
+ paint_rect.width() * _device_scale,
+ paint_rect.height() * _device_scale,
+ stride);
+ cairo_surface_set_device_scale(imgs, _device_scale, _device_scale);
+
+ buf.ct = cairo_create(imgs);
+ cairo_save(buf.ct);
+ cairo_translate(buf.ct, -paint_rect.left(), -paint_rect.top());
+ cairo_set_source(buf.ct, _background);
+ cairo_set_operator(buf.ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(buf.ct);
+ cairo_restore(buf.ct);
+ // cairo_surface_write_to_png( imgs, "debug1.png" );
+
+ if (_root->visible) {
+ SP_CANVAS_ITEM_GET_CLASS(_root)->render(_root, &buf);
+ }
+
+ // cairo_surface_write_to_png( imgs, "debug2.png" );
+
+ // output to X
+ cairo_destroy(buf.ct);
+
+#if defined(HAVE_LIBLCMS2)
+ if (_enable_cms_display_adj) {
+ cmsHTRANSFORM transf = nullptr;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display");
+ if ( fromDisplay ) {
+ transf = Inkscape::CMSSystem::getDisplayPer(_cms_key);
+ } else {
+ transf = Inkscape::CMSSystem::getDisplayTransform();
+ }
+
+ if (transf) {
+ cairo_surface_flush(imgs);
+ unsigned char *px = cairo_image_surface_get_data(imgs);
+ int stride = cairo_image_surface_get_stride(imgs);
+ for (int i=0; i<paint_rect.height(); ++i) {
+ unsigned char *row = px + i*stride;
+ Inkscape::CMSSystem::doTransform(transf, row, row, paint_rect.width());
+ }
+ cairo_surface_mark_dirty(imgs);
+ }
+ }
+#endif // defined(HAVE_LIBLCMS2)
+
+ cairo_surface_mark_dirty(_backing_store);
+ // cairo_surface_write_to_png( _backing_store, "debug3.png" );
+
+ // Mark the painted rectangle clean
+ markRect(paint_rect, 0);
+
+ cairo_surface_destroy(imgs);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(this), paint_rect.left() -_x0, paint_rect.top() - _y0,
+ paint_rect.width(), paint_rect.height());
+}
+
+void SPCanvas::paintXRayBuffer(Geom::IntRect const &paint_rect, Geom::IntRect const &canvas_rect)
+{
+
+ // Prevent crash if paintSingleBuffer is called before _backing_store is
+ // initialized.
+ if (_backing_store == nullptr)
+ return;
+ if (!canvas_rect.contains(paint_rect) && !canvas_rect.intersects(paint_rect)) {
+ return;
+ }
+ Geom::IntRect rect_moved = Geom::IntRect::from_xywh(_x0, _y0, _x0 + paint_rect.width(), _y0 + paint_rect.height());
+ SPCanvasBuf buf;
+ buf.buf = nullptr;
+ buf.buf_rowstride = 0;
+ buf.rect = paint_rect;
+ buf.canvas_rect = canvas_rect;
+ buf.device_scale = _device_scale;
+ buf.is_empty = true;
+ // Make sure the following code does not go outside of _backing_store's data
+ // FIXME for device_scale.
+ assert(cairo_image_surface_get_format(_backing_store) == CAIRO_FORMAT_ARGB32);
+ cairo_surface_t *copy_backing = cairo_surface_create_similar_image(_backing_store, CAIRO_FORMAT_ARGB32,
+ paint_rect.width(), paint_rect.height());
+ buf.ct = cairo_create(copy_backing);
+ cairo_t *result = cairo_create(_backing_store);
+ cairo_translate(result, -_x0, -_y0);
+ cairo_save(buf.ct);
+ cairo_set_source_rgba(buf.ct, 1, 1, 1, 0);
+ cairo_fill(buf.ct);
+ cairo_arc(buf.ct, _xray_radius, _xray_radius, _xray_radius, 0, 2 * M_PI);
+ cairo_clip(buf.ct);
+ cairo_paint(buf.ct);
+ cairo_translate(buf.ct, -paint_rect.left(), -paint_rect.top());
+ cairo_set_source(buf.ct, _background);
+ cairo_set_operator(buf.ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(buf.ct);
+ cairo_translate(buf.ct, paint_rect.left(), paint_rect.top());
+ // cairo_surface_write_to_png( copy_backing, "debug1.png" );
+
+
+
+ if (_root->visible) {
+ SP_CANVAS_ITEM_GET_CLASS(_root)->render(_root, &buf);
+ }
+ cairo_restore(buf.ct);
+ cairo_arc(buf.ct, _xray_radius, _xray_radius, _xray_radius, 0, 2 * M_PI);
+ cairo_clip(buf.ct);
+ cairo_set_operator(buf.ct, CAIRO_OPERATOR_DEST_IN);
+ cairo_paint(buf.ct);
+ // cairo_surface_write_to_png( copy_backing, "debug2.png" );
+
+ // output to X
+ cairo_arc(buf.ct, _xray_radius, _xray_radius, _xray_radius, 0, 2 * M_PI);
+
+ cairo_set_source_surface(result, copy_backing, paint_rect.left(), paint_rect.top());
+ cairo_set_operator(buf.ct, CAIRO_OPERATOR_IN);
+ cairo_paint(result);
+ cairo_surface_destroy(copy_backing);
+ cairo_destroy(buf.ct);
+ cairo_destroy(result);
+
+ // cairo_surface_write_to_png( _backing_store, "debug3.png" );
+ cairo_surface_mark_dirty(_backing_store);
+ // Mark the painted rectangle un-clean to remove old x-ray when mouse change position
+ _xray_rect = paint_rect;
+ gtk_widget_queue_draw_area(GTK_WIDGET(this), paint_rect.left() - _x0, paint_rect.top() - _y0, paint_rect.width(),
+ paint_rect.height());
+}
+
+void SPCanvas::paintSpliter()
+{
+ // Prevent crash if paintSingleBuffer is called before _backing_store is
+ // initialized.
+ if (_backing_store == nullptr)
+ return;
+ // Todo: scale for HiDPI screens
+ SPCanvas *canvas = SP_CANVAS(this);
+ double ds = canvas->_device_scale;
+ Geom::IntRect linerect = (*canvas->_spliter);
+ Geom::IntPoint c0 = Geom::IntPoint(linerect.corner(0));
+ Geom::IntPoint c1 = Geom::IntPoint(linerect.corner(1));
+ Geom::IntPoint c2 = Geom::IntPoint(linerect.corner(2));
+ Geom::IntPoint c3 = Geom::IntPoint(linerect.corner(3));
+ // We need to draw the line in middle of pixel
+ // https://developer.gnome.org/gtkmm-tutorial/stable/sec-cairo-drawing-lines.html.en:17.2.3
+ double gapx = _split_vertical ? 0.5 : 0;
+ double gapy = _split_vertical ? 0 : 0.5;
+ Geom::Point start = _split_vertical ? Geom::middle_point(c0, c1) : Geom::middle_point(c0, c3);
+ Geom::Point end = _split_vertical ? Geom::middle_point(c2, c3) : Geom::middle_point(c1, c2);
+ Geom::Point middle = Geom::middle_point(start, end);
+ if (canvas->_spliter_control_pos != Geom::Point()) {
+ middle[Geom::X] = _split_vertical ? middle[Geom::X] : canvas->_spliter_control_pos[Geom::X];
+ middle[Geom::Y] = _split_vertical ? canvas->_spliter_control_pos[Geom::Y] : middle[Geom::Y];
+ }
+ canvas->_spliter_control_pos = middle;
+ canvas->_spliter_control = Geom::OptIntRect(Geom::IntPoint(int(middle[0] - (25 * ds)), int(middle[1] - (25 * ds))),
+ Geom::IntPoint(int(middle[0] + (25 * ds)), int(middle[1] + (25 * ds))));
+ canvas->_spliter_top = Geom::OptIntRect(Geom::IntPoint(int(middle[0] - (25 * ds)), int(middle[1] - (25 * ds))),
+ Geom::IntPoint(int(middle[0] + (25 * ds)), int(middle[1] - (10 * ds))));
+ canvas->_spliter_bottom = Geom::OptIntRect(Geom::IntPoint(int(middle[0] - (25 * ds)), int(middle[1] + (25 * ds))),
+ Geom::IntPoint(int(middle[0] + (25 * ds)), int(middle[1] + (10 * ds))));
+ canvas->_spliter_left = Geom::OptIntRect(Geom::IntPoint(int(middle[0] - (25 * ds)), int(middle[1] - (10 * ds))),
+ Geom::IntPoint(int(middle[0]), int(middle[1] + (10 * ds))));
+ canvas->_spliter_right = Geom::OptIntRect(Geom::IntPoint(int(middle[0] + (25 * ds)), int(middle[1] + (10 * ds))),
+ Geom::IntPoint(int(middle[0]), int(middle[1] - (10 * ds))));
+ cairo_t *ct = cairo_create(_backing_store);
+ cairo_set_antialias(ct, CAIRO_ANTIALIAS_BEST);
+ cairo_set_line_width(ct, 1.0 * ds);
+ cairo_line_to(ct, start[0] + gapx, start[1] + gapy);
+ cairo_line_to(ct, end[0] + gapx, end[1] + gapy);
+ cairo_stroke_preserve(ct);
+ if (canvas->_split_hover || canvas->_split_pressed) {
+ cairo_set_source_rgba(ct, 0.15, 0.15, 0.15, 1);
+ } else {
+ cairo_set_source_rgba(ct, 0.3, 0.3, 0.3, 1);
+ }
+ cairo_stroke(ct);
+ /*
+ Get by: https://gitlab.com/snippets/1777221
+ M 40,19.999997 C 39.999998,8.9543032 31.045694,0 20,0 8.9543062,0 1.6568541e-6,8.9543032 0,19.999997
+ 0,31.045692 8.954305,39.999997 20,39.999997 31.045695,39.999997 40,31.045692 40,19.999997 Z M
+ 11.109859,15.230724 2.8492384,19.999997 11.109861,24.769269 Z M 29.249158,15.230724
+ 37.509779,19.999997 29.249158,24.769269 Z M 15.230728,29.03051 20,37.29113 24.769272,29.030509 Z
+ M 15.230728,10.891209 20,2.630586 24.769272,10.891209 Z */
+ double updwidth = _split_vertical ? 0 : linerect.width();
+ double updheight = _split_vertical ? linerect.height() : 0;
+ cairo_translate(ct, middle[0] - (20 * ds), middle[1] - (20 * ds));
+ cairo_scale(ct, ds, ds);
+ cairo_move_to(ct, 40, 19.999997);
+ cairo_curve_to(ct, 39.999998, 8.9543032, 31.045694, 0, 20, 0);
+ cairo_curve_to(ct, 8.9543062, 0, 0, 8.9543032, 0, 19.999997);
+ cairo_curve_to(ct, 0, 31.045692, 8.954305, 39.999997, 20, 39.999997);
+ cairo_curve_to(ct, 31.045695, 39.999997, 40, 31.045692, 40, 19.999997);
+ cairo_close_path(ct);
+ cairo_fill(ct);
+ cairo_move_to(ct, 15.230728, 10.891209);
+ cairo_line_to(ct, 20, 2.630586);
+ cairo_line_to(ct, 24.769272, 10.891209);
+ cairo_close_path(ct);
+ if (canvas->_split_hover_vertical) {
+ cairo_set_source_rgba(ct, 0.90, 0.90, 0.90, 1);
+ } else {
+ cairo_set_source_rgba(ct, 0.6, 0.6, 0.6, 1);
+ }
+ cairo_fill(ct);
+ cairo_move_to(ct, 15.230728, 29.03051);
+ cairo_line_to(ct, 20, 37.29113);
+ cairo_line_to(ct, 24.769272, 29.030509);
+ cairo_close_path(ct);
+ cairo_fill(ct);
+ cairo_move_to(ct, 11.109859, 15.230724);
+ cairo_line_to(ct, 2.8492384, 19.999997);
+ cairo_line_to(ct, 11.109861, 24.769269);
+ cairo_close_path(ct);
+ if (canvas->_split_hover_horizontal) {
+ cairo_set_source_rgba(ct, 0.90, 0.90, 0.90, 1);
+ } else {
+ cairo_set_source_rgba(ct, 0.6, 0.6, 0.6, 1);
+ }
+ cairo_fill(ct);
+ cairo_move_to(ct, 29.249158, 15.230724);
+ cairo_line_to(ct, 37.509779, 19.999997);
+ cairo_line_to(ct, 29.249158, 24.769269);
+ cairo_close_path(ct);
+ cairo_fill(ct);
+ cairo_scale(ct, 1 / ds, 1 / ds);
+ cairo_translate(ct, -middle[0] - (20 * ds), -middle[1] - (20 * ds));
+ cairo_restore(ct);
+ cairo_destroy(ct);
+ gtk_widget_queue_draw_area(GTK_WIDGET(this), start[0] - (21 * ds), start[1] - (21 * ds), updwidth + (42 * ds),
+ updheight + (42 * ds));
+}
+
+struct PaintRectSetup {
+ Geom::IntRect canvas_rect;
+ gint64 start_time;
+ int max_pixels;
+ Geom::Point mouse_loc;
+};
+
+int SPCanvas::paintRectInternal(PaintRectSetup const *setup, Geom::IntRect const &this_rect)
+{
+ gint64 now = g_get_monotonic_time();
+ gint64 elapsed = now - setup->start_time;
+
+ // Allow only very fast buffers to be run together;
+ // as soon as the total redraw time exceeds 1ms, cancel;
+ // this returns control to the idle loop and allows Inkscape to process user input
+ // (potentially interrupting the redraw); as soon as Inkscape has some more idle time,
+ if (elapsed > 1000) {
+
+ // Interrupting redraw isn't always good.
+ // For example, when you drag one node of a big path, only the buffer containing
+ // the mouse cursor will be redrawn again and again, and the rest of the path
+ // will remain stale because Inkscape never has enough idle time to redraw all
+ // of the screen. To work around this, such operations set a forced_redraw_limit > 0.
+ // If this limit is set, and if we have aborted redraw more times than is allowed,
+ // interrupting is blocked and we're forced to redraw full screen once
+ // (after which we can again interrupt forced_redraw_limit times).
+ if (_forced_redraw_limit < 0 ||
+ _forced_redraw_count < _forced_redraw_limit) {
+
+ if (_forced_redraw_limit != -1) {
+ _forced_redraw_count++;
+ }
+ return false;
+ }
+ _forced_redraw_count = 0;
+ }
+
+ // Find the optimal buffer dimensions
+ int bw = this_rect.width();
+ int bh = this_rect.height();
+ // we dont want to stop the idle process if the area is empty
+ if ((bw < 1) || (bh < 1))
+ return 1;
+
+ if (bw * bh < setup->max_pixels) {
+ // We are small enough
+ /*
+ GdkRectangle r;
+ r.x = this_rect.x0 - setup->canvas->x0;
+ r.y = this_rect.y0 - setup->canvas->y0;
+ r.width = this_rect.x1 - this_rect.x0;
+ r.height = this_rect.y1 - this_rect.y0;
+
+ GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(setup->canvas));
+ gdk_window_begin_paint_rect(window, &r);
+ */
+
+ paintSingleBuffer(this_rect, setup->canvas_rect, bw);
+ _splits++;
+ //gdk_window_end_paint(window);
+ return 1;
+ }
+
+ Geom::IntRect lo, hi;
+
+/*
+This test determines the redraw strategy:
+
+bw < bh (strips mode) splits across the smaller dimension of the rect and therefore (on
+horizontally-stretched windows) results in redrawing in horizontal strips (from cursor point, in
+both directions if the cursor is in the middle). This is traditional for Inkscape since old days,
+and seems to be faster for drawings with many smaller objects at zoom-out.
+
+bw > bh (chunks mode) splits across the larger dimension of the rect and therefore paints in
+almost-square chunks, again from the cursor point. It's sometimes faster for drawings with few slow
+(e.g. blurred) objects crossing the entire screen. It also appears to be somewhat psychologically
+faster.
+
+The default for now is the strips mode.
+*/
+ if (bw < bh || bh < 2 * TILE_SIZE) {
+ int mid = this_rect[Geom::X].middle();
+ // Make sure that mid lies on a tile boundary
+ mid = (mid / TILE_SIZE) * TILE_SIZE;
+
+ lo = Geom::IntRect(this_rect.left(), this_rect.top(), mid, this_rect.bottom());
+ hi = Geom::IntRect(mid, this_rect.top(), this_rect.right(), this_rect.bottom());
+
+ if (setup->mouse_loc[Geom::X] < mid) {
+ // Always paint towards the mouse first
+ return paintRectInternal(setup, lo)
+ && paintRectInternal(setup, hi);
+ } else {
+ return paintRectInternal(setup, hi)
+ && paintRectInternal(setup, lo);
+ }
+ } else {
+ int mid = this_rect[Geom::Y].middle();
+ // Make sure that mid lies on a tile boundary
+ mid = (mid / TILE_SIZE) * TILE_SIZE;
+
+ lo = Geom::IntRect(this_rect.left(), this_rect.top(), this_rect.right(), mid);
+ hi = Geom::IntRect(this_rect.left(), mid, this_rect.right(), this_rect.bottom());
+
+ if (setup->mouse_loc[Geom::Y] < mid) {
+ // Always paint towards the mouse first
+ return paintRectInternal(setup, lo)
+ && paintRectInternal(setup, hi);
+ } else {
+ return paintRectInternal(setup, hi)
+ && paintRectInternal(setup, lo);
+ }
+ }
+}
+
+
+bool SPCanvas::paintRect(int xx0, int yy0, int xx1, int yy1)
+{
+ GtkAllocation allocation;
+ g_return_val_if_fail (!_need_update, false);
+
+ gtk_widget_get_allocation(GTK_WIDGET(this), &allocation);
+
+ // Find window rectangle in 'world coordinates'.
+ Geom::IntRect canvas_rect = Geom::IntRect::from_xywh(_x0, _y0,
+ allocation.width, allocation.height);
+ Geom::IntRect paint_rect(xx0, yy0, xx1, yy1);
+
+ Geom::OptIntRect area = paint_rect & canvas_rect;
+ // we dont want to stop the idle process if the area is empty
+ if (!area || area->hasZeroArea()) {
+ return true;
+ }
+ paint_rect = *area;
+
+ PaintRectSetup setup;
+ setup.canvas_rect = canvas_rect;
+
+ // Save the mouse location
+ gint x, y;
+
+ auto const display = Gdk::Display::get_default();
+ auto const seat = display->get_default_seat();
+ auto const device = seat->get_pointer();
+
+ gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(this)),
+ device->gobj(),
+ &x, &y, nullptr);
+
+ setup.mouse_loc = sp_canvas_window_to_world(this, Geom::Point(x,y));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ unsigned tile_multiplier = prefs->getIntLimited("/options/rendering/tile-multiplier", 16, 1, 512);
+
+ if (_rendermode != Inkscape::RENDERMODE_OUTLINE) {
+ // use 256K as a compromise to not slow down gradients
+ // 256K is the cached buffer and we need 4 channels
+ setup.max_pixels = 65536 * tile_multiplier; // 256K/4
+ } else {
+ // paths only, so 1M works faster
+ // 1M is the cached buffer and we need 4 channels
+ setup.max_pixels = 262144;
+ }
+
+ // Start the clock
+ setup.start_time = g_get_monotonic_time();
+ // Go
+ return paintRectInternal(&setup, paint_rect);
+}
+
+void SPCanvas::forceFullRedrawAfterInterruptions(unsigned int count, bool reset)
+{
+ _forced_redraw_limit = count;
+ if (reset) {
+ _forced_redraw_count = 0;
+ }
+}
+
+void SPCanvas::endForcedFullRedraws()
+{
+ _forced_redraw_limit = -1;
+}
+
+gboolean SPCanvas::handle_draw(GtkWidget *widget, cairo_t *cr) {
+
+ SPCanvas *canvas = SP_CANVAS(widget);
+
+ if (canvas->_surface_for_similar == nullptr && canvas->_backing_store != nullptr) {
+
+ // Device scale is copied but since this is only created one time, we'll
+ // need to check/set device scale anytime it is used in case window moved
+ // to monitor with different scale.
+ canvas->_surface_for_similar =
+ cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, 1, 1);
+
+ // Check we are using correct device scale
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale(canvas->_backing_store, &x_scale, &y_scale);
+ assert (canvas->_device_scale == (int)x_scale);
+ assert (canvas->_device_scale == (int)y_scale);
+
+ // Reallocate backing store so that cairo can use shared memory
+ // Function does NOT copy device scale! Width and height are in device pixels.
+ cairo_surface_t *new_backing_store = cairo_surface_create_similar_image(
+ canvas->_surface_for_similar, CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_width(canvas->_backing_store),
+ cairo_image_surface_get_height(canvas->_backing_store));
+
+ cairo_surface_set_device_scale(new_backing_store, canvas->_device_scale, canvas->_device_scale);
+
+ // Copy the old backing store contents
+ cairo_t *cr = cairo_create(new_backing_store);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface(cr, canvas->_backing_store, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ cairo_surface_destroy(canvas->_backing_store);
+ canvas->_backing_store = new_backing_store;
+ }
+
+ // Blit from the backing store, without regard for the clean region.
+ // This is necessary because GTK clears the widget for us, which causes
+ // severe flicker while drawing if we don't blit the old contents.
+ cairo_set_source_surface(cr, canvas->_backing_store, 0, 0);
+ cairo_paint(cr);
+
+ cairo_rectangle_list_t *rects = cairo_copy_clip_rectangle_list(cr);
+ cairo_region_t *dirty_region = cairo_region_create();
+
+ for (int i = 0; i < rects->num_rectangles; i++) {
+ cairo_rectangle_t rectangle = rects->rectangles[i];
+ Geom::Rect dr = Geom::Rect::from_xywh(rectangle.x + canvas->_x0, rectangle.y + canvas->_y0,
+ rectangle.width, rectangle.height);
+
+ Geom::IntRect ir = dr.roundOutwards();
+ cairo_rectangle_int_t irect = { ir.left(), ir.top(), ir.width(), ir.height() };
+ cairo_region_union_rectangle(dirty_region, &irect);
+ }
+ cairo_rectangle_list_destroy(rects);
+ cairo_region_subtract(dirty_region, canvas->_clean_region);
+
+ // Render the dirty portion in the background
+ if (!cairo_region_is_empty(dirty_region)) {
+ canvas->addIdle();
+ }
+ cairo_region_destroy(dirty_region);
+
+ return TRUE;
+}
+
+gint SPCanvas::handle_key_event(GtkWidget *widget, GdkEventKey *event)
+{
+ return SP_CANVAS(widget)->emitEvent(reinterpret_cast<GdkEvent *>(event));
+}
+
+gint SPCanvas::handle_crossing(GtkWidget *widget, GdkEventCrossing *event)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (event->window != getWindow(canvas)) {
+ return FALSE;
+ }
+ canvas->_state = event->state;
+ return canvas->pickCurrentItem(reinterpret_cast<GdkEvent *>(event));
+}
+
+gint SPCanvas::handle_focus_in(GtkWidget *widget, GdkEventFocus *event)
+{
+ gtk_widget_grab_focus (widget);
+
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (canvas->_focused_item) {
+ return canvas->emitEvent(reinterpret_cast<GdkEvent *>(event));
+ } else {
+ return FALSE;
+ }
+}
+
+gint SPCanvas::handle_focus_out(GtkWidget *widget, GdkEventFocus *event)
+{
+ SPCanvas *canvas = SP_CANVAS(widget);
+
+ if (canvas->_focused_item) {
+ return canvas->emitEvent(reinterpret_cast<GdkEvent *>(event));
+ } else {
+ return FALSE;
+ }
+}
+
+int SPCanvas::paint()
+{
+ if (_need_update) {
+ sp_canvas_item_invoke_update(_root, Geom::identity(), 0);
+ _need_update = FALSE;
+ }
+ SPCanvas *canvas = SP_CANVAS(this);
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(canvas), &allocation);
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ SPCanvasArena *arena = nullptr;
+ bool split = false;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _xray_radius = prefs->getIntLimited("/options/rendering/xray-radius", 100, 1, 1500);
+
+ double split_x = 1;
+ double split_y = 1;
+ Inkscape::RenderMode rm = Inkscape::RENDERMODE_NORMAL;
+ if (desktop) {
+ split = desktop->splitMode();
+ arena = SP_CANVAS_ARENA(desktop->drawing);
+ rm = arena->drawing.renderMode();
+ if (split) {
+ auto window = desktop->getInkscapeWindow();
+ auto dtw = window->get_desktop_widget();
+ bool hasrullers = prefs->getBool(desktop->is_fullscreen() ? "/fullscreen/rulers/state" : "/window/rulers/state");
+ int hruler_gap = hasrullers ? dtw->get_hruler_thickness() : 1;
+ int vruler_gap = hasrullers ? dtw->get_vruler_thickness() : 1;
+
+ split_x = !_split_vertical ? 0 : _split_value;
+ split_y = _split_vertical ? 0 : _split_value;
+
+ Geom::IntCoord coord1x =
+ allocation.x + (int((allocation.width) * split_x)) - (3 * canvas->_device_scale) - vruler_gap;
+ Geom::IntCoord coord1y =
+ allocation.y + (int((allocation.height) * split_y)) - (3 * canvas->_device_scale) - hruler_gap;
+ split_x = !_split_vertical ? 1 : _split_value;
+ split_y = _split_vertical ? 1 : _split_value;
+ Geom::IntCoord coord2x =
+ allocation.x + (int((allocation.width) * split_x)) + (3 * canvas->_device_scale) - vruler_gap;
+ Geom::IntCoord coord2y =
+ allocation.y + (int((allocation.height) * split_y)) + (3 * canvas->_device_scale) - hruler_gap;
+ _spliter = Geom::OptIntRect(coord1x, coord1y, coord2x, coord2y);
+ split_x = !_split_vertical ? 0 : _split_value;
+ split_y = _split_vertical ? 0 : _split_value;
+ coord1x = allocation.x + (int((allocation.width ) * split_x)) - vruler_gap;
+ coord1y = allocation.y + (int((allocation.height) * split_y)) - hruler_gap;
+ split_x = !_split_vertical ? 1 : _split_value;
+ split_y = _split_vertical ? 1 : _split_value;
+ coord2x = allocation.x + allocation.width;
+ coord2y = allocation.y + allocation.height;
+ _spliter_area = Geom::OptIntRect(coord1x, coord1y, coord2x, coord2y);
+ } else {
+ sp_reset_spliter(canvas);
+ }
+ } else {
+ sp_reset_spliter(canvas);
+ }
+ cairo_rectangle_int_t crect = { _x0, _y0, int(allocation.width * split_x), int(allocation.height * split_y) };
+ split_x = !_split_vertical ? 0 : _split_value;
+ split_y = _split_vertical ? 0 : _split_value;
+ cairo_rectangle_int_t crect_outline = { _x0 + int(allocation.width * split_x),
+ _y0 + int(allocation.height * split_y),
+ int(allocation.width * (1 - split_x)),
+ int(allocation.height * (1 - split_y)) };
+ cairo_region_t *draw = nullptr;
+ cairo_region_t *to_draw = nullptr;
+ cairo_region_t *to_draw_outline = nullptr;
+ if (_split_inverse && split) {
+ to_draw = cairo_region_create_rectangle(&crect_outline);
+ to_draw_outline = cairo_region_create_rectangle(&crect);
+ } else {
+ to_draw = cairo_region_create_rectangle(&crect);
+ to_draw_outline = cairo_region_create_rectangle(&crect_outline);
+ }
+
+ cairo_region_get_extents(_clean_region, &crect);
+ draw = cairo_region_create_rectangle(&crect);
+ cairo_region_subtract(draw, _clean_region);
+ cairo_region_get_extents(draw, &crect);
+ cairo_region_subtract_rectangle(_clean_region, &crect);
+ cairo_region_subtract(to_draw, _clean_region);
+ cairo_region_subtract(to_draw_outline, _clean_region);
+ cairo_region_destroy(draw);
+ int n_rects = cairo_region_num_rectangles(to_draw);
+ for (int i = 0; i < n_rects; ++i) {
+ cairo_rectangle_int_t crect;
+ cairo_region_get_rectangle(to_draw, i, &crect);
+ if (!paintRect(crect.x, crect.y, crect.x + crect.width, crect.y + crect.height)) {
+ // Aborted
+ cairo_region_destroy(to_draw);
+ cairo_region_destroy(to_draw_outline);
+ return FALSE;
+ };
+ }
+
+ if (split) {
+ arena->drawing.setRenderMode(Inkscape::RENDERMODE_OUTLINE);
+ bool exact = arena->drawing.getExact();
+ arena->drawing.setExact(false);
+ int n_rects = cairo_region_num_rectangles(to_draw_outline);
+ for (int i = 0; i < n_rects; ++i) {
+ cairo_rectangle_int_t crect;
+ cairo_region_get_rectangle(to_draw_outline, i, &crect);
+ if (!paintRect(crect.x, crect.y, crect.x + crect.width, crect.y + crect.height)) {
+ // Aborted
+ arena->drawing.setExact(exact);
+ arena->drawing.setRenderMode(rm);
+ cairo_region_destroy(to_draw);
+ cairo_region_destroy(to_draw_outline);
+ return FALSE;
+ };
+ }
+ arena->drawing.setExact(exact);
+ arena->drawing.setRenderMode(rm);
+ canvas->paintSpliter();
+ } else if (desktop && _xray) {
+ if (rm != Inkscape::RENDERMODE_OUTLINE) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/desktop/xrayactive", true);
+ arena->drawing.setRenderMode(Inkscape::RENDERMODE_OUTLINE);
+ bool exact = arena->drawing.getExact();
+ arena->drawing.setExact(false);
+ Geom::IntRect canvas_rect = Geom::IntRect::from_xywh(_x0, _y0, allocation.width, allocation.height);
+ Geom::IntRect _xray_rect = Geom::IntRect::from_xywh(
+ _xray_orig[0] - _xray_radius, _xray_orig[1] - _xray_radius, (_xray_radius * 2), (_xray_radius * 2));
+ paintXRayBuffer(_xray_rect, canvas_rect);
+ arena->drawing.setExact(exact);
+ arena->drawing.setRenderMode(rm);
+ prefs->setBool("/desktop/xrayactive", false);
+ }
+ }
+
+ // we've had a full unaborted redraw, reset the full redraw counter
+ if (_forced_redraw_limit != -1) {
+ _forced_redraw_count = 0;
+ }
+
+ cairo_region_destroy(to_draw);
+ cairo_region_destroy(to_draw_outline);
+
+ return TRUE;
+}
+
+int SPCanvas::doUpdate()
+{
+ if (!_root) { // canvas may have already be destroyed by closing desktop during interrupted display!
+ return TRUE;
+ }
+ if (_drawing_disabled) {
+ return TRUE;
+ }
+
+ // Cause the update if necessary
+ if (_need_update) {
+ sp_canvas_item_invoke_update(_root, Geom::identity(), 0);
+ _need_update = FALSE;
+ }
+
+ // Paint if able to
+ if (gtk_widget_is_drawable(GTK_WIDGET(this))) {
+ return paint();
+ }
+
+ // Pick new current item
+ while (_need_repick) {
+ _need_repick = FALSE;
+ pickCurrentItem(&_pick_event);
+ }
+
+ return TRUE;
+}
+
+gint SPCanvas::idle_handler(gpointer data)
+{
+ SPCanvas *canvas = SP_CANVAS (data);
+#ifdef DEBUG_PERFORMANCE
+ static int totaloops = 1;
+ gint64 now = 0;
+ gint64 elapsed = 0;
+ now = g_get_monotonic_time();
+ elapsed = now - canvas->_idle_time;
+ g_message("[%i] start loop %i in split %i at %f", canvas->_idle_id, totaloops, canvas->_splits,
+ canvas->_totalelapsed / (double)1000000 + elapsed / (double)1000000);
+#endif
+ int ret = canvas->doUpdate();
+ int n_rects = cairo_region_num_rectangles(canvas->_clean_region);
+ if (n_rects > 1) { // not fully painted, maybe clean region is updated in middle of idle, reload again
+ ret = 0;
+ }
+
+#ifdef DEBUG_PERFORMANCE
+ if (ret == 0) {
+ now = g_get_monotonic_time();
+ elapsed = now - canvas->_idle_time;
+ g_message("[%i] loop ended unclean at %f", canvas->_idle_id,
+ canvas->_totalelapsed / (double)1000000 + elapsed / (double)1000000);
+ totaloops += 1;
+ }
+ if (ret) {
+ // Reset idle id
+ now = g_get_monotonic_time();
+ elapsed = now - canvas->_idle_time;
+ canvas->_totalelapsed += elapsed;
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop) {
+ SPCanvasArena *arena = SP_CANVAS_ARENA(desktop->drawing);
+ Inkscape::RenderMode rm = arena->drawing.renderMode();
+ if (rm == Inkscape::RENDERMODE_OUTLINE) {
+ canvas->_totalelapsed = 0;
+ g_message("Outline mode, we reset and stop total counter");
+ }
+ g_message("[%i] finished", canvas->_idle_id);
+ g_message("[%i] loops %i", canvas->_idle_id, totaloops);
+ g_message("[%i] splits %i", canvas->_idle_id, canvas->_splits);
+ g_message("[%i] duration %f", canvas->_idle_id, elapsed / (double)1000000);
+ g_message("[%i] total %f (toggle outline mode to reset)", canvas->_idle_id,
+ canvas->_totalelapsed / (double)1000000);
+ g_message("[%i] :::::::::::::::::::::::::::::::::::::::", canvas->_idle_id);
+ }
+ canvas->_idle_id = 0;
+ totaloops = 1;
+ canvas->_splits = 0;
+ }
+#else
+ if (ret) {
+ // Reset idle id
+ canvas->_idle_id = 0;
+ }
+#endif
+ return !ret;
+}
+
+void SPCanvas::addIdle()
+{
+ if (_idle_id == 0) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint redrawPriority = prefs->getIntLimited("/options/redrawpriority/value", G_PRIORITY_HIGH_IDLE, G_PRIORITY_HIGH_IDLE, G_PRIORITY_DEFAULT_IDLE);
+ SPCanvas *canvas = SP_CANVAS(this);
+ if (canvas->_in_full_redraw) {
+ canvas->_in_full_redraw = false;
+ redrawPriority = G_PRIORITY_DEFAULT_IDLE;
+ }
+#ifdef DEBUG_PERFORMANCE
+ _idle_time = g_get_monotonic_time();
+#endif
+ _idle_id = gdk_threads_add_idle_full(redrawPriority, idle_handler, this, nullptr);
+#ifdef DEBUG_PERFORMANCE
+ g_message("[%i] launched %f", _idle_id, _totalelapsed / (double)1000000);
+#endif
+ }
+}
+void SPCanvas::removeIdle()
+{
+ if (_idle_id) {
+ g_source_remove(_idle_id);
+#ifdef DEBUG_PERFORMANCE
+ g_message("[%i] aborted in split %i", _idle_id, _splits);
+ _splits = 0;
+#endif
+ _idle_id = 0;
+ }
+}
+
+SPCanvasGroup *SPCanvas::getRoot()
+{
+ return SP_CANVAS_GROUP(_root);
+}
+
+/**
+ * Scroll screen to point 'c'. 'c' is measured in screen pixels.
+ */
+void SPCanvas::scrollTo( Geom::Point const &c, unsigned int clear, bool is_scrolling)
+{
+ // To do: extract out common code with SPCanvas::handle_size_allocate()
+
+ // For HiDPI monitors
+ int device_scale = gtk_widget_get_scale_factor(GTK_WIDGET(this));
+ assert( device_scale == _device_scale);
+
+ double cx = c[Geom::X];
+ double cy = c[Geom::Y];
+
+ int ix = (int) round(cx); // ix and iy are the new canvas coordinates (integer screen pixels)
+ int iy = (int) round(cy); // cx might be negative, so (int)(cx + 0.5) will not do!
+ int dx = ix - _x0; // dx and dy specify the displacement (scroll) of the
+ int dy = iy - _y0; // canvas w.r.t its previous position
+
+ Geom::IntRect old_area = getViewboxIntegers();
+ Geom::IntRect new_area = old_area + Geom::IntPoint(dx, dy);
+ bool outsidescrool = false;
+ if (!new_area.intersects(old_area)) {
+ outsidescrool = true;
+ }
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(&_widget, &allocation);
+
+ // cairo_surface_write_to_png( _backing_store, "scroll1.png" );
+ bool split = false;
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop && desktop->splitMode()) {
+ split = true;
+ }
+ if (clear || split || _xray || outsidescrool) {
+ _dx0 = cx; // here the 'd' stands for double, not delta!
+ _dy0 = cy;
+ _x0 = ix;
+ _y0 = iy;
+ requestFullRedraw();
+ } else {
+ // Adjust backing store contents
+ assert(_backing_store);
+ // this cairo operation is slow, improvements welcome
+ cairo_surface_t *new_backing_store = nullptr;
+ if (_surface_for_similar != nullptr)
+
+ // Size in device pixels. Does not set device scale.
+ new_backing_store =
+ cairo_surface_create_similar_image(_surface_for_similar,
+ CAIRO_FORMAT_ARGB32,
+ allocation.width * _device_scale,
+ allocation.height * _device_scale);
+ if (new_backing_store == nullptr)
+ // Size in device pixels. Does not set device scale.
+ new_backing_store =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ allocation.width * _device_scale,
+ allocation.height * _device_scale);
+
+ // Set device scale
+ cairo_surface_set_device_scale(new_backing_store, _device_scale, _device_scale);
+
+ cairo_t *cr = cairo_create(new_backing_store);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ // Paint the background
+ cairo_translate(cr, -ix, -iy);
+ cairo_set_source_surface(cr, _backing_store, ix, iy);
+ cairo_paint(cr);
+
+ // cairo_surface_write_to_png( _backing_store, "scroll0.png" );
+
+ // Copy the old backing store contents
+ cairo_set_source_surface(cr, _backing_store, _x0, _y0);
+ cairo_rectangle(cr, _x0, _y0, allocation.width, allocation.height);
+ cairo_clip(cr);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ cairo_surface_destroy(_backing_store);
+ _backing_store = new_backing_store;
+ _dx0 = cx; // here the 'd' stands for double, not delta!
+ _dy0 = cy;
+ _x0 = ix;
+ _y0 = iy;
+ cairo_rectangle_int_t crect = { _x0, _y0, allocation.width, allocation.height };
+ cairo_region_intersect_rectangle(_clean_region, &crect);
+ }
+
+ SPCanvasArena *arena = SP_CANVAS_ARENA(desktop->drawing);
+ if (arena) {
+ Geom::IntRect expanded = new_area;
+ Geom::IntPoint expansion(new_area.width()/2, new_area.height()/2);
+ expanded.expandBy(expansion);
+ arena->drawing.setCacheLimit(expanded, false);
+ }
+
+ if (!clear) {
+ // scrolling without zoom; redraw only the newly exposed areas
+ if ((dx != 0) || (dy != 0)) {
+ if (gtk_widget_get_realized(GTK_WIDGET(this))) {
+ SPCanvas *canvas = SP_CANVAS(this);
+ if (split) {
+ double scroll_horiz = 1 / (allocation.width / (double)-dx);
+ double scroll_vert = 1 / (allocation.height / (double)-dy);
+ double gap = canvas->_split_vertical ? scroll_horiz : scroll_vert;
+ canvas->_split_value = canvas->_split_value + gap;
+ if (scroll_horiz < 0.03 || scroll_horiz > 0.97 || scroll_vert < 0.03 || scroll_vert > 0.97) {
+ if (canvas->_split_value > 0.97) {
+ canvas->_split_value = 0.97;
+ } else if (canvas->_split_value < 0.03) {
+ canvas->_split_value = 0.03;
+ }
+ }
+ }
+ gdk_window_scroll(getWindow(this), -dx, -dy);
+ }
+ }
+ }
+}
+
+void SPCanvas::updateNow()
+{
+ if (_need_update) {
+#ifdef DEBUG_PERFORMANCE
+ guint64 now = g_get_monotonic_time();
+ gint64 elapsed = now - _idle_time;
+ g_message("[%i] start updateNow(): %f at %f", _idle_id, elapsed / (double)1000000,
+ _totalelapsed / (double)1000000 + elapsed / (double)1000000);
+#endif
+ doUpdate();
+#ifdef DEBUG_PERFORMANCE
+ now = g_get_monotonic_time();
+ elapsed = now - _idle_time;
+ g_message("[%i] end updateNow(): %f at %f", _idle_id, elapsed / (double)1000000,
+ _totalelapsed / (double)1000000 + elapsed / (double)1000000);
+#endif
+ }
+}
+
+void SPCanvas::requestUpdate()
+{
+ _need_update = TRUE;
+ addIdle();
+}
+
+void SPCanvas::requestRedraw(int x0, int y0, int x1, int y1)
+{
+ if (!gtk_widget_is_drawable( GTK_WIDGET(this) )) {
+ return;
+ }
+ if (x0 >= x1 || y0 >= y1) {
+ return;
+ }
+
+ Geom::IntRect bbox(x0, y0, x1, y1);
+ dirtyRect(bbox);
+ addIdle();
+}
+void SPCanvas::requestFullRedraw()
+{
+ SPCanvas *canvas = SP_CANVAS(this);
+ canvas->_in_full_redraw = true;
+ dirtyAll();
+ addIdle();
+}
+
+void SPCanvas::setBackgroundColor(guint32 rgba) {
+ double new_r = SP_RGBA32_R_F(rgba);
+ double new_g = SP_RGBA32_G_F(rgba);
+ double new_b = SP_RGBA32_B_F(rgba);
+ if (!_background_is_checkerboard) {
+ double old_r, old_g, old_b;
+ cairo_pattern_get_rgba(_background, &old_r, &old_g, &old_b, nullptr);
+ if (new_r == old_r && new_g == old_g && new_b == old_b) return;
+ }
+ if (_background) {
+ cairo_pattern_destroy(_background);
+ }
+ _background = cairo_pattern_create_rgb(new_r, new_g, new_b);
+ _background_is_checkerboard = false;
+ requestFullRedraw();
+}
+
+void SPCanvas::setBackgroundCheckerboard(guint32 rgba)
+{
+ if (_background_is_checkerboard) return;
+ if (_background) {
+ cairo_pattern_destroy(_background);
+ }
+ _background = ink_cairo_pattern_create_checkerboard(rgba);
+ _background_is_checkerboard = true;
+ requestFullRedraw();
+}
+
+/**
+ * Sets world coordinates from win and canvas.
+ */
+void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy)
+{
+ g_return_if_fail (canvas != nullptr);
+ g_return_if_fail (SP_IS_CANVAS (canvas));
+
+ if (worldx) *worldx = canvas->_x0 + winx;
+ if (worldy) *worldy = canvas->_y0 + winy;
+}
+
+/**
+ * Sets win coordinates from world and canvas.
+ */
+void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy)
+{
+ g_return_if_fail (canvas != nullptr);
+ g_return_if_fail (SP_IS_CANVAS (canvas));
+
+ if (winx) *winx = worldx - canvas->_x0;
+ if (winy) *winy = worldy - canvas->_y0;
+}
+
+/**
+ * Converts point from win to world coordinates.
+ */
+Geom::Point sp_canvas_window_to_world(SPCanvas const *canvas, Geom::Point const win)
+{
+ g_assert (canvas != nullptr);
+ g_assert (SP_IS_CANVAS (canvas));
+
+ return Geom::Point(canvas->_x0 + win[0], canvas->_y0 + win[1]);
+}
+
+/**
+ * Converts point from world to win coordinates.
+ */
+Geom::Point sp_canvas_world_to_window(SPCanvas const *canvas, Geom::Point const world)
+{
+ g_assert (canvas != nullptr);
+ g_assert (SP_IS_CANVAS (canvas));
+
+ return Geom::Point(world[0] - canvas->_x0, world[1] - canvas->_y0);
+}
+
+/**
+ * Returns true if point given in world coordinates is inside window.
+ */
+bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, Geom::Point const &world)
+{
+ GtkAllocation allocation;
+
+ g_assert( canvas != nullptr );
+ g_assert(SP_IS_CANVAS(canvas));
+
+ GtkWidget *w = GTK_WIDGET(canvas);
+ gtk_widget_get_allocation (w, &allocation);
+
+ return ( ( canvas->_x0 <= world[Geom::X] ) &&
+ ( canvas->_y0 <= world[Geom::Y] ) &&
+ ( world[Geom::X] < canvas->_x0 + allocation.width ) &&
+ ( world[Geom::Y] < canvas->_y0 + allocation.height ) );
+}
+
+/**
+ * Return canvas window coordinates as Geom::Rect.
+ */
+Geom::Rect SPCanvas::getViewbox() const
+{
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (this), &allocation);
+ return Geom::Rect::from_xywh(_dx0, _dy0, allocation.width, allocation.height);
+}
+
+/**
+ * Return canvas window coordinates as integer rectangle.
+ */
+Geom::IntRect SPCanvas::getViewboxIntegers() const
+{
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET(this), &allocation);
+ return Geom::IntRect::from_xywh(_x0, _y0, allocation.width, allocation.height);
+}
+
+inline int sp_canvas_tile_floor(int x)
+{
+ return (x & (~(TILE_SIZE - 1))) / TILE_SIZE;
+}
+
+inline int sp_canvas_tile_ceil(int x)
+{
+ return ((x + (TILE_SIZE - 1)) & (~(TILE_SIZE - 1))) / TILE_SIZE;
+}
+
+void SPCanvas::dirtyRect(Geom::IntRect const &area) {
+ markRect(area, 1);
+}
+
+void SPCanvas::dirtyAll() {
+ if (_clean_region && !cairo_region_is_empty(_clean_region)) {
+ cairo_region_destroy(_clean_region);
+ _clean_region = cairo_region_create();
+ }
+}
+
+void SPCanvas::markRect(Geom::IntRect const &area, uint8_t val)
+{
+ cairo_rectangle_int_t crect = { area.left(), area.top(), area.width(), area.height() };
+ if (val) {
+ cairo_region_subtract_rectangle(_clean_region, &crect);
+ } else {
+ cairo_region_union_rectangle(_clean_region, &crect);
+ }
+}
+
+
+/*
+ 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/display/sp-canvas.h b/src/display/sp-canvas.h
new file mode 100644
index 0000000..a079714
--- /dev/null
+++ b/src/display/sp-canvas.h
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_CANVAS_H
+#define SEEN_SP_CANVAS_H
+
+/**
+ * @file
+ * SPCanvas, SPCanvasBuf.
+ */
+/*
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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 <2geom/affine.h>
+#include <2geom/rect.h>
+#include <cstdint>
+#include <glib.h>
+#include <glibmm/ustring.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SP_TYPE_CANVAS (sp_canvas_get_type())
+#define SP_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_CANVAS, SPCanvas))
+#define SP_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_CANVAS))
+
+struct SPCanvas;
+struct SPCanvasItem;
+struct SPCanvasGroup;
+
+enum {
+ SP_CANVAS_UPDATE_REQUESTED = 1 << 0,
+ SP_CANVAS_UPDATE_AFFINE = 1 << 1
+};
+
+/**
+ * Structure used when rendering canvas items.
+ */
+struct SPCanvasBuf {
+ cairo_t *ct;
+ Geom::IntRect rect;
+ Geom::IntRect canvas_rect; // visible window in world coordinates (i.e. offset by _x0, _y0)
+
+ unsigned char *buf;
+ int buf_rowstride;
+ int device_scale; // For high DPI monitors.
+ bool is_empty;
+};
+
+G_END_DECLS
+
+// SPCanvas -------------------------------------------------
+
+struct PaintRectSetup;
+
+GType sp_canvas_get_type() G_GNUC_CONST;
+
+/**
+ * Port of GnomeCanvas for inkscape needs.
+ */
+struct SPCanvas {
+ /// Scrolls canvas to specific position (c is measured in screen pixels).
+ void scrollTo(Geom::Point const &c, unsigned int clear, bool is_scrolling = false);
+
+ /// Synchronously updates the canvas if necessary.
+ void updateNow();
+
+ /// Queues a redraw of rectangular canvas area.
+ void requestRedraw(int x1, int y1, int x2, int y2);
+ void requestFullRedraw();
+ void requestUpdate();
+
+ void forceFullRedrawAfterInterruptions(unsigned int count, bool reset = true);
+ void endForcedFullRedraws();
+
+ Geom::Rect getViewbox() const;
+ Geom::IntRect getViewboxIntegers() const;
+ SPCanvasGroup *getRoot();
+
+ void setBackgroundColor(guint32 rgba);
+ void setBackgroundCheckerboard(guint32 rgba = 0xC4C4C4FF);
+
+ /// Returns new canvas as widget.
+ static GtkWidget *createAA();
+
+private:
+ /// Emits an event for an item in the canvas, be it the current
+ /// item, grabbed item, or focused item, as appropriate.
+ int emitEvent(GdkEvent *event);
+
+ /// Re-picks the current item in the canvas, based on the event's
+ /// coordinates and emits enter/leave events for items as appropriate.
+ int pickCurrentItem(GdkEvent *event);
+ void shutdownTransients();
+
+ /// Allocates a new tile array for the canvas, copying overlapping tiles from the old array
+ void resizeTiles(int nl, int nt, int nr, int nb);
+
+ /// Marks the specified area as dirty (requiring redraw)
+ void dirtyRect(Geom::IntRect const &area);
+ /// Marks the whole widget for redraw
+ void dirtyAll();
+ void markRect(Geom::IntRect const &area, uint8_t val);
+
+ /// Invokes update, paint, and repick on canvas.
+ int doUpdate();
+
+ void paintSingleBuffer(Geom::IntRect const &paint_rect, Geom::IntRect const &canvas_rect, int sw);
+ void paintXRayBuffer(Geom::IntRect const &paint_rect, Geom::IntRect const &canvas_rect);
+
+ /**
+ * Paint the given rect, recursively subdividing the region until it is the size of a single
+ * buffer.
+ *
+ * @return true if the drawing completes
+ */
+ int paintRectInternal(PaintRectSetup const *setup, Geom::IntRect const &this_rect);
+
+ void paintSpliter();
+ void set_cursor(GtkWidget *widget);
+ /// Draws a specific rectangular part of the canvas.
+ /// @return true if the rectangle painting succeeds.
+ bool paintRect(int xx0, int yy0, int xx1, int yy1);
+
+ /// Repaints the areas in the canvas that need it.
+ /// @return true if all the dirty parts have been redrawn
+ int paint();
+
+ /// Idle handler for the canvas that deals with pending updates and redraws.
+ static gint idle_handler(gpointer data);
+
+ /// Convenience function to add an idle handler to a canvas.
+ void addIdle();
+ void removeIdle();
+
+public:
+ // GTK virtual functions.
+ static void dispose(GObject *object);
+ static void handle_realize(GtkWidget *widget);
+ static void handle_unrealize(GtkWidget *widget);
+ static void handle_get_preferred_width(GtkWidget *widget, gint *min_w, gint *nat_w);
+ static void handle_get_preferred_height(GtkWidget *widget, gint *min_h, gint *nat_h);
+ static void handle_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
+ static gint handle_button(GtkWidget *widget, GdkEventButton *event);
+ static gint handle_doubleclick(GtkWidget *widget, GdkEventButton *event);
+
+ /**
+ * Scroll event handler for the canvas.
+ *
+ * @todo FIXME: generate motion events to re-select items.
+ */
+ static gint handle_scroll(GtkWidget *widget, GdkEventScroll *event);
+ static gint handle_motion(GtkWidget *widget, GdkEventMotion *event);
+ static gboolean handle_draw(GtkWidget *widget, cairo_t *cr);
+ static gint handle_key_event(GtkWidget *widget, GdkEventKey *event);
+ static gint handle_crossing(GtkWidget *widget, GdkEventCrossing *event);
+ static gint handle_focus_in(GtkWidget *widget, GdkEventFocus *event);
+ static gint handle_focus_out(GtkWidget *widget, GdkEventFocus *event);
+
+public:
+ // Data members: ----------------------------------------------------------
+ GtkWidget _widget;
+
+ guint _idle_id;
+
+ SPCanvasItem *_root;
+
+ Geom::OptIntRect _spliter;
+ Geom::OptIntRect _spliter_area;
+ Geom::OptIntRect _spliter_control;
+ Geom::OptIntRect _spliter_top;
+ Geom::OptIntRect _spliter_bottom;
+ Geom::OptIntRect _spliter_left;
+ Geom::OptIntRect _spliter_right;
+ Geom::OptIntRect _xray_rect;
+ Geom::Point _spliter_control_pos;
+ Geom::Point _spliter_in_control_pos;
+ Geom::Point _xray_orig;
+ double _split_value;
+ bool _split_vertical;
+ bool _split_inverse;
+ bool _split_hover_vertical;
+ bool _split_hover_horizontal;
+ bool _split_hover;
+ bool _split_pressed;
+ bool _split_control_pressed;
+ bool _split_dragging;
+ double _xray_radius;
+ bool _xray;
+ bool _is_dragging;
+ bool _in_full_redraw;
+ guint _changecursor;
+ double _dx0;
+ double _dy0;
+ int _x0; ///< World coordinate of the leftmost pixels of window
+ int _y0; ///< World coordinate of the topmost pixels of window
+ int _device_scale; ///< Scale for high DPI montiors
+ gint64 _idle_time;
+ int _splits;
+ gint64 _totalelapsed;
+
+ /// Image surface storing the contents of the widget
+ cairo_surface_t *_backing_store;
+ /// This should be e.g. a cairo-xlib surface that is used to allocate _backing_store; might be NULL.
+ cairo_surface_t *_surface_for_similar;
+ /// Area of the widget that has up-to-date content
+ cairo_region_t *_clean_region;
+ /// Widget background, defaults to white
+ cairo_pattern_t *_background;
+ bool _background_is_checkerboard;
+
+ /// Last known modifier state, for deferred repick when a button is down.
+ int _state;
+
+ /** The item containing the mouse pointer, or NULL if none. */
+ SPCanvasItem *_current_item;
+
+ /** Item that is about to become current (used to track deletions and such). */
+ SPCanvasItem *_new_current_item;
+
+ /** Item that holds a pointer grab, or NULL if none. */
+ SPCanvasItem *_grabbed_item;
+
+ /** Event mask specified when grabbing an item. */
+ guint _grabbed_event_mask;
+
+ /** If non-NULL, the currently focused item. */
+ SPCanvasItem *_focused_item;
+
+ /** Event on which selection of current item is based. */
+ GdkEvent _pick_event;
+
+ int _close_enough;
+
+ unsigned int _need_update : 1;
+ unsigned int _need_repick : 1;
+
+ int _forced_redraw_count;
+ int _forced_redraw_limit;
+
+ /** For use by internal pick_current_item() function. */
+ unsigned int _left_grabbed_item : 1;
+
+ /** For use by internal pick_current_item() function. */
+ unsigned int _in_repick : 1;
+
+ // In most tools Inkscape only generates enter and leave events
+ // on the current item, but no other enter events if a mouse button
+ // is depressed -- see function pickCurrentItem(). Some tools
+ // may wish the canvas to generate to all enter events, (e.g., the
+ // connector tool). If so, they may temporarily set this flag to
+ // 'true'.
+ bool _gen_all_enter_events;
+
+ /** For scripting, sometimes we want to delay drawing. */
+ bool _drawing_disabled;
+
+ int _rendermode;
+ int _colorrendermode;
+
+#if defined(HAVE_LIBLCMS2)
+ bool _enable_cms_display_adj;
+ Glib::ustring _cms_key;
+#endif // defined(HAVE_LIBLCMS2)
+
+ bool _is_scrolling;
+};
+
+bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, Geom::Point const &world);
+
+void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy);
+void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy);
+
+Geom::Point sp_canvas_window_to_world(SPCanvas const *canvas, Geom::Point const win);
+Geom::Point sp_canvas_world_to_window(SPCanvas const *canvas, Geom::Point const world);
+
+#endif // SEEN_SP_CANVAS_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/display/sp-ctrlcurve.cpp b/src/display/sp-ctrlcurve.cpp
new file mode 100644
index 0000000..41572cd
--- /dev/null
+++ b/src/display/sp-ctrlcurve.cpp
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Simple bezier curve used for Mesh Gradients
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2011 Tavmjong Bah
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/sp-ctrlcurve.h"
+#include "display/sp-canvas-util.h"
+#include "display/cairo-utils.h"
+#include "color.h"
+#include "display/sp-canvas.h"
+
+namespace {
+
+static void sp_ctrlcurve_destroy(SPCanvasItem *object);
+
+static void sp_ctrlcurve_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_ctrlcurve_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+} // namespace
+
+G_DEFINE_TYPE(SPCtrlCurve, sp_ctrlcurve, SP_TYPE_CANVAS_ITEM);
+
+static void
+sp_ctrlcurve_class_init(SPCtrlCurveClass *klass)
+{
+ klass->destroy = sp_ctrlcurve_destroy;
+
+ klass->update = sp_ctrlcurve_update;
+ klass->render = sp_ctrlcurve_render;
+}
+
+static void
+sp_ctrlcurve_init(SPCtrlCurve *ctrlcurve)
+{
+ // Points are initialized to 0,0
+ ctrlcurve->rgba = 0x0000ff7f;
+ ctrlcurve->item=nullptr;
+ ctrlcurve->corner0 = -1;
+ ctrlcurve->corner1 = -1;
+}
+
+namespace {
+static void
+sp_ctrlcurve_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CTRLCURVE (object));
+
+ SPCtrlCurve *ctrlcurve = SP_CTRLCURVE (object);
+
+ ctrlcurve->item=nullptr;
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlcurve_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_ctrlcurve_parent_class)->destroy(object);
+}
+
+static void
+sp_ctrlcurve_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrlCurve *cl = SP_CTRLCURVE (item);
+
+ if (!buf->ct)
+ return;
+
+ if ( cl->p0 == cl->p1 &&
+ cl->p1 == cl->p2 &&
+ cl->p2 == cl->p3 )
+ return;
+
+ ink_cairo_set_source_rgba32(buf->ct, cl->rgba);
+ cairo_set_line_width(buf->ct, 1);
+ cairo_new_path(buf->ct);
+
+ Geom::Point p0 = cl->p0 * cl->affine;
+ Geom::Point p1 = cl->p1 * cl->affine;
+ Geom::Point p2 = cl->p2 * cl->affine;
+ Geom::Point p3 = cl->p3 * cl->affine;
+
+ cairo_move_to (buf->ct, p0[Geom::X] - buf->rect.left(), p0[Geom::Y] - buf->rect.top());
+ cairo_curve_to (buf->ct,
+ p1[Geom::X] - buf->rect.left(), p1[Geom::Y] - buf->rect.top(),
+ p2[Geom::X] - buf->rect.left(), p2[Geom::Y] - buf->rect.top(),
+ p3[Geom::X] - buf->rect.left(), p3[Geom::Y] - buf->rect.top() );
+
+ cairo_stroke(buf->ct);
+}
+
+static void
+sp_ctrlcurve_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCtrlCurve *cl = SP_CTRLCURVE (item);
+
+ item->canvas->requestRedraw(item->x1, item->y1, item->x2, item->y2);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlcurve_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(sp_ctrlcurve_parent_class)->update(item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ cl->affine = affine;
+
+ if (cl->p0 == cl->p1 && cl->p1 == cl->p2 && cl->p2 == cl->p3 ) {
+ item->x1 = item->x2 = item->y1 = item->y2 = 0;
+ } else {
+
+ Geom::Point p0 = cl->p0 * affine;
+ Geom::Point p1 = cl->p1 * affine;
+ Geom::Point p2 = cl->p2 * affine;
+ Geom::Point p3 = cl->p3 * affine;
+
+ double min_x = p0[Geom::X];
+ double min_y = p0[Geom::Y];
+ double max_x = p0[Geom::X];
+ double max_y = p0[Geom::Y];
+
+ min_x = MIN( min_x, p1[Geom::X] );
+ min_y = MIN( min_y, p1[Geom::Y] );
+ max_x = MAX( max_x, p1[Geom::X] );
+ max_y = MAX( max_y, p1[Geom::Y] );
+
+ min_x = MIN( min_x, p2[Geom::X] );
+ min_y = MIN( min_y, p2[Geom::Y] );
+ max_x = MAX( max_x, p2[Geom::X] );
+ max_y = MAX( max_y, p2[Geom::Y] );
+
+ min_x = MIN( min_x, p3[Geom::X] );
+ min_y = MIN( min_y, p3[Geom::Y] );
+ max_x = MAX( max_x, p3[Geom::X] );
+ max_y = MAX( max_y, p3[Geom::Y] );
+
+ item->x1 = round( min_x - 1 );
+ item->y1 = round( min_y - 1 );
+ item->x2 = round( max_x + 1 );
+ item->y2 = round( max_y + 1 );
+
+ item->canvas->requestRedraw(item->x1, item->y1, item->x2, item->y2);
+
+ }
+}
+
+} // namespace
+
+#define EPSILON 1e-6
+#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON)
+
+void SPCtrlCurve::setCoords( gdouble x0, gdouble y0, gdouble x1, gdouble y1,
+ gdouble x2, gdouble y2, gdouble x3, gdouble y3 )
+{
+
+ Geom::Point q0( x0, y0 );
+ Geom::Point q1( x1, y1 );
+ Geom::Point q2( x2, y2 );
+ Geom::Point q3( x3, y3 );
+
+ setCoords( q0, q1, q2, q3 );
+
+}
+
+void SPCtrlCurve::setCoords( Geom::Point const &q0, Geom::Point const &q1,
+ Geom::Point const &q2, Geom::Point const &q3)
+{
+ if (DIFFER(p0[Geom::X], q0[Geom::X]) || DIFFER(p0[Geom::Y], q0[Geom::Y]) ||
+ DIFFER(p1[Geom::X], q1[Geom::X]) || DIFFER(p1[Geom::Y], q1[Geom::Y]) ||
+ DIFFER(p2[Geom::X], q2[Geom::X]) || DIFFER(p2[Geom::Y], q2[Geom::Y]) ||
+ DIFFER(p3[Geom::X], q3[Geom::X]) || DIFFER(p3[Geom::Y], q3[Geom::Y]) ) {
+ p0 = q0;
+ p1 = q1;
+ p2 = q2;
+ p3 = q3;
+ sp_canvas_item_request_update( this );
+ }
+}
+
+/*
+ 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/display/sp-ctrlcurve.h b/src/display/sp-ctrlcurve.h
new file mode 100644
index 0000000..a499d52
--- /dev/null
+++ b/src/display/sp-ctrlcurve.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_CTRLCURVE_H
+#define SEEN_INKSCAPE_CTRLCURVE_H
+
+/*
+ * Simple bezier curve (used for Mesh Gradients)
+ *
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Derived from sp-ctrlline
+ *
+ * Copyright (C) 2011 Tavmjong Bah
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "display/sp-ctrlline.h"
+
+class SPItem;
+
+#define SP_TYPE_CTRLCURVE (sp_ctrlcurve_get_type())
+#define SP_CTRLCURVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CTRLCURVE, SPCtrlCurve))
+#define SP_IS_CTRLCURVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CTRLCURVE))
+
+struct SPCtrlCurve : public SPCtrlLine {
+ void setCoords( gdouble x0, gdouble y0, gdouble x1, gdouble y1,
+ gdouble x2, gdouble y2, gdouble x3, gdouble y3 );
+
+ void setCoords( Geom::Point const &q0, Geom::Point const &q1,
+ Geom::Point const &q2, Geom::Point const &q3);
+
+ Geom::Point p0, p1, p2, p3;
+
+ int corner0; // Used to store index of corner for finding dragger.
+ int corner1;
+};
+
+GType sp_ctrlcurve_get_type();
+
+struct SPCtrlCurveClass : public SPCtrlLineClass{};
+
+#endif // SEEN_INKSCAPE_CTRLCURVE_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/display/sp-ctrlline.cpp b/src/display/sp-ctrlline.cpp
new file mode 100644
index 0000000..504e3d0
--- /dev/null
+++ b/src/display/sp-ctrlline.cpp
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Simple straight line
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * TODO:
+ * Draw it by hand - we really do not need aa stuff for it
+ *
+ */
+
+#include "display/sp-ctrlline.h"
+#include "display/sp-canvas-util.h"
+#include "display/cairo-utils.h"
+#include "color.h"
+#include "display/sp-canvas.h"
+
+namespace {
+
+void sp_ctrlline_destroy(SPCanvasItem *object);
+
+void sp_ctrlline_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+void sp_ctrlline_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+} // namespace
+
+G_DEFINE_TYPE(SPCtrlLine, sp_ctrlline, SP_TYPE_CANVAS_ITEM);
+
+static void sp_ctrlline_class_init(SPCtrlLineClass *klass)
+{
+ klass->destroy = sp_ctrlline_destroy;
+
+ klass->update = sp_ctrlline_update;
+ klass->render = sp_ctrlline_render;
+}
+
+static void sp_ctrlline_init(SPCtrlLine *ctrlline)
+{
+ ctrlline->rgba = 0x0000ff7f;
+ ctrlline->s[Geom::X] = ctrlline->s[Geom::Y] = ctrlline->e[Geom::X] = ctrlline->e[Geom::Y] = 0.0;
+ ctrlline->item=nullptr;
+ ctrlline->is_fill = true;
+}
+
+namespace {
+void sp_ctrlline_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail(object != nullptr);
+ g_return_if_fail(SP_IS_CTRLLINE(object));
+
+ SPCtrlLine *ctrlline = SP_CTRLLINE(object);
+
+ ctrlline->item = nullptr;
+
+ if(SP_CANVAS_ITEM_CLASS (sp_ctrlline_parent_class)->destroy) {
+ SP_CANVAS_ITEM_CLASS (sp_ctrlline_parent_class)->destroy(object);
+ }
+}
+
+void sp_ctrlline_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrlLine *cl = SP_CTRLLINE(item);
+
+ if (!buf->ct) {
+ return;
+ }
+
+ if (cl->s == cl->e) {
+ return;
+ }
+
+ Geom::Point s = cl->s * cl->affine;
+ Geom::Point e = cl->e * cl->affine;
+
+ ink_cairo_set_source_rgba32(buf->ct, 0xffffff7f);
+ cairo_set_line_width(buf->ct, 2);
+ cairo_new_path(buf->ct);
+
+ cairo_move_to(buf->ct, s[Geom::X] - buf->rect.left(), s[Geom::Y] - buf->rect.top());
+ cairo_line_to(buf->ct, e[Geom::X] - buf->rect.left(), e[Geom::Y] - buf->rect.top());
+
+ cairo_stroke(buf->ct);
+
+
+ ink_cairo_set_source_rgba32(buf->ct, cl->rgba);
+ cairo_set_line_width(buf->ct, 1);
+ cairo_new_path(buf->ct);
+
+ cairo_move_to(buf->ct, s[Geom::X] - buf->rect.left(), s[Geom::Y] - buf->rect.top());
+ cairo_line_to(buf->ct, e[Geom::X] - buf->rect.left(), e[Geom::Y] - buf->rect.top());
+
+ cairo_stroke(buf->ct);
+}
+
+void sp_ctrlline_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCtrlLine *cl = SP_CTRLLINE(item);
+
+ item->canvas->requestRedraw(item->x1, item->y1, item->x2, item->y2);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlline_parent_class)->update) {
+ SP_CANVAS_ITEM_CLASS(sp_ctrlline_parent_class)->update(item, affine, flags);
+ }
+
+ sp_canvas_item_reset_bounds(item);
+
+ cl->affine = affine;
+
+ if (cl->s == cl->e) {
+ item->x1 = item->x2 = item->y1 = item->y2 = 0;
+ } else {
+
+ Geom::Point s = cl->s * affine;
+ Geom::Point e = cl->e * affine;
+
+ item->x1 = round(MIN(s[Geom::X], e[Geom::X]) - 1);
+ item->y1 = round(MIN(s[Geom::Y], e[Geom::Y]) - 1);
+ item->x2 = round(MAX(s[Geom::X], e[Geom::X]) + 1);
+ item->y2 = round(MAX(s[Geom::Y], e[Geom::Y]) + 1);
+
+ item->canvas->requestRedraw(item->x1, item->y1, item->x2, item->y2);
+ }
+}
+
+} // namespace
+
+void SPCtrlLine::setRgba32(guint32 rgba)
+{
+ if (rgba != this->rgba) {
+ this->rgba = rgba;
+ canvas->requestRedraw(x1, y1, x2, y2);
+ }
+}
+
+#define EPSILON 1e-6
+#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON)
+
+void SPCtrlLine::setCoords(gdouble x0, gdouble y0, gdouble x1, gdouble y1)
+{
+ if (DIFFER(x0, s[Geom::X]) || DIFFER(y0, s[Geom::Y]) || DIFFER(x1, e[Geom::X]) || DIFFER(y1, e[Geom::Y])) {
+ s[Geom::X] = x0;
+ s[Geom::Y] = y0;
+ e[Geom::X] = x1;
+ e[Geom::Y] = y1;
+ sp_canvas_item_request_update(this);
+ }
+}
+
+void SPCtrlLine::setCoords(Geom::Point const &start, Geom::Point const &end)
+{
+ setCoords(start[0], start[1], end[0], end[1]);
+}
+
+/*
+ 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/display/sp-ctrlline.h b/src/display/sp-ctrlline.h
new file mode 100644
index 0000000..5ecd1ca
--- /dev/null
+++ b/src/display/sp-ctrlline.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_CTRLLINE_H
+#define SEEN_INKSCAPE_CTRLLINE_H
+
+/*
+ * Simple straight line
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2012 Authors
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-canvas-item.h"
+
+class SPItem;
+
+#define SP_TYPE_CTRLLINE (sp_ctrlline_get_type())
+#define SP_CTRLLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CTRLLINE, SPCtrlLine))
+#define SP_IS_CTRLLINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CTRLLINE))
+
+struct SPCtrlLine : public SPCanvasItem {
+ void setRgba32(guint32 rgba);
+
+ void setCoords(gdouble x0, gdouble y0, gdouble x1, gdouble y1);
+
+ void setCoords(Geom::Point const &start, Geom::Point const &end);
+
+
+ SPItem *item; // the item to which this line belongs in some sense; may be NULL for some users
+ bool is_fill; // fill or stroke... used with meshes.
+
+ guint32 rgba;
+ Geom::Point s;
+ Geom::Point e;
+ Geom::Affine affine;
+};
+
+GType sp_ctrlline_get_type();
+
+struct SPCtrlLineClass : public SPCanvasItemClass{};
+
+
+
+#endif // SEEN_INKSCAPE_CTRLLINE_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/display/sp-ctrlquadr.cpp b/src/display/sp-ctrlquadr.cpp
new file mode 100644
index 0000000..ffca867
--- /dev/null
+++ b/src/display/sp-ctrlquadr.cpp
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Quadrilateral
+ *
+ * Authors:
+ * bulia byak
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-canvas-item.h"
+#include "sp-canvas.h"
+#include "sp-canvas-util.h"
+#include "sp-ctrlquadr.h"
+#include "display/cairo-utils.h"
+#include "color.h"
+
+struct SPCtrlQuadr : public SPCanvasItem{
+ guint32 rgba;
+ Geom::Point p1, p2, p3, p4;
+ Geom::Affine affine;
+};
+
+struct SPCtrlQuadrClass : public SPCanvasItemClass{};
+
+static void sp_ctrlquadr_destroy(SPCanvasItem *object);
+
+static void sp_ctrlquadr_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+G_DEFINE_TYPE(SPCtrlQuadr, sp_ctrlquadr, SP_TYPE_CANVAS_ITEM);
+
+static void
+sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass)
+{
+ SPCanvasItemClass *item_class = SP_CANVAS_ITEM_CLASS(klass);
+
+ item_class->destroy = sp_ctrlquadr_destroy;
+ item_class->update = sp_ctrlquadr_update;
+ item_class->render = sp_ctrlquadr_render;
+}
+
+static void
+sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr)
+{
+ ctrlquadr->rgba = 0x000000ff;
+ ctrlquadr->p1 = Geom::Point(0, 0);
+ ctrlquadr->p2 = Geom::Point(0, 0);
+ ctrlquadr->p3 = Geom::Point(0, 0);
+ ctrlquadr->p4 = Geom::Point(0, 0);
+}
+
+static void sp_ctrlquadr_destroy(SPCanvasItem *object)
+{
+ g_return_if_fail (object != nullptr);
+ g_return_if_fail (SP_IS_CTRLQUADR (object));
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlquadr_parent_class)->destroy)
+ (* SP_CANVAS_ITEM_CLASS(sp_ctrlquadr_parent_class)->destroy) (object);
+}
+
+static void
+sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrlQuadr *cq = SP_CTRLQUADR (item);
+
+ if (!buf->ct)
+ return;
+
+ // RGB / BGR
+ cairo_new_path(buf->ct);
+
+ Geom::Point min = buf->rect.min();
+
+ Geom::Point p1 = (cq->p1 * cq->affine) - min;
+ Geom::Point p2 = (cq->p2 * cq->affine) - min;
+ Geom::Point p3 = (cq->p3 * cq->affine) - min;
+ Geom::Point p4 = (cq->p4 * cq->affine) - min;
+
+ cairo_move_to(buf->ct, p1[Geom::X], p1[Geom::Y]);
+ cairo_line_to(buf->ct, p2[Geom::X], p2[Geom::Y]);
+ cairo_line_to(buf->ct, p3[Geom::X], p3[Geom::Y]);
+ cairo_line_to(buf->ct, p4[Geom::X], p4[Geom::Y]);
+ cairo_line_to(buf->ct, p1[Geom::X], p1[Geom::Y]);
+
+ // FIXME: this is supposed to draw inverse but cairo apparently is unable of this trick :(
+ //cairo_set_operator (buf->ct, CAIRO_OPERATOR_XOR);
+
+ cairo_set_source_rgba(buf->ct, SP_RGBA32_B_F(cq->rgba), SP_RGBA32_G_F(cq->rgba), SP_RGBA32_R_F(cq->rgba), SP_RGBA32_A_F(cq->rgba));
+ cairo_fill(buf->ct);
+}
+
+#define MIN4(a,b,c,d)\
+ ((a <= b && a <= c && a <= d) ? a : \
+ (b <= a && b <= c && b <= d) ? b : \
+ (c <= a && c <= b && c <= d) ? c : \
+ d )
+
+#define MAX4(a,b,c,d)\
+ ((a >= b && a >= c && a >= d) ? a : \
+ (b >= a && b >= c && b >= d) ? b : \
+ (c >= a && c >= b && c >= d) ? c : \
+ d )
+
+
+static void sp_ctrlquadr_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCtrlQuadr *cq = SP_CTRLQUADR(item);
+
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_ctrlquadr_parent_class)->update) {
+ SP_CANVAS_ITEM_CLASS(sp_ctrlquadr_parent_class)->update(item, affine, flags);
+ }
+
+ sp_canvas_item_reset_bounds (item);
+
+ cq->affine = affine;
+
+ Geom::Point p1(cq->p1 * affine);
+ Geom::Point p2(cq->p2 * affine);
+ Geom::Point p3(cq->p3 * affine);
+ Geom::Point p4(cq->p4 * affine);
+
+ item->x1 = (int)(MIN4(p1[Geom::X], p2[Geom::X], p3[Geom::X], p4[Geom::X]));
+ item->y1 = (int)(MIN4(p1[Geom::Y], p2[Geom::Y], p3[Geom::Y], p4[Geom::Y]));
+ item->x2 = (int)(MAX4(p1[Geom::X], p2[Geom::X], p3[Geom::X], p4[Geom::X]));
+ item->y2 = (int)(MAX4(p1[Geom::Y], p2[Geom::Y], p3[Geom::Y], p4[Geom::Y]));
+
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+}
+
+void
+sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba)
+{
+ g_return_if_fail (cl != nullptr);
+ g_return_if_fail (SP_IS_CTRLQUADR (cl));
+
+ if (rgba != cl->rgba) {
+ SPCanvasItem *item;
+ cl->rgba = rgba;
+ item = SP_CANVAS_ITEM (cl);
+ item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+ }
+}
+
+void
+sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, Geom::Point p1, Geom::Point p2, Geom::Point p3, Geom::Point p4)
+{
+ g_return_if_fail (cl != nullptr);
+ g_return_if_fail (SP_IS_CTRLQUADR (cl));
+
+ if (p1 != cl->p1 || p2 != cl->p2 || p3 != cl->p3 || p4 != cl->p4) {
+ cl->p1 = p1;
+ cl->p2 = p2;
+ cl->p3 = p3;
+ cl->p4 = p4;
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cl));
+ }
+}
+
+/*
+ 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/display/sp-ctrlquadr.h b/src/display/sp-ctrlquadr.h
new file mode 100644
index 0000000..502227a
--- /dev/null
+++ b/src/display/sp-ctrlquadr.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __INKSCAPE_CTRLQUADR_H__
+#define __INKSCAPE_CTRLQUADR_H__
+
+/*
+ * Quadrilateral
+ *
+ * Authors:
+ * bulia byak
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/geom.h>
+
+#define SP_TYPE_CTRLQUADR (sp_ctrlquadr_get_type ())
+#define SP_CTRLQUADR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CTRLQUADR, SPCtrlQuadr))
+#define SP_IS_CTRLQUADR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CTRLQUADR))
+
+struct SPCtrlQuadr;
+struct SPCtrlQuadrClass;
+
+GType sp_ctrlquadr_get_type ();
+
+void sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba);
+void sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, const Geom::Point p1, const Geom::Point p2, const Geom::Point p3, const Geom::Point p4);
+
+
+#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 :