summaryrefslogtreecommitdiffstats
path: root/src/display
diff options
context:
space:
mode:
Diffstat (limited to 'src/display')
-rw-r--r--src/display/.cvsignore7
-rw-r--r--src/display/Makefile_insert66
-rw-r--r--src/display/bezier-utils-test.cpp334
-rw-r--r--src/display/bezier-utils-work.txt34
-rw-r--r--src/display/bezier-utils.cpp983
-rw-r--r--src/display/bezier-utils.h49
-rw-r--r--src/display/canvas-arena.cpp451
-rw-r--r--src/display/canvas-arena.h58
-rw-r--r--src/display/canvas-bpath.cpp485
-rw-r--r--src/display/canvas-bpath.h99
-rw-r--r--src/display/canvas-grid.cpp308
-rw-r--r--src/display/canvas-grid.h49
-rw-r--r--src/display/curve.cpp1289
-rw-r--r--src/display/curve.h133
-rw-r--r--src/display/display-forward.h46
-rw-r--r--src/display/gnome-canvas-acetate.cpp100
-rw-r--r--src/display/gnome-canvas-acetate.h41
-rw-r--r--src/display/guideline.cpp198
-rw-r--r--src/display/guideline.h55
-rw-r--r--src/display/makefile.in17
-rw-r--r--src/display/nr-arena-forward.h51
-rw-r--r--src/display/nr-arena-glyphs.cpp679
-rw-r--r--src/display/nr-arena-glyphs.h109
-rw-r--r--src/display/nr-arena-group.cpp256
-rw-r--r--src/display/nr-arena-group.h46
-rw-r--r--src/display/nr-arena-image.cpp258
-rw-r--r--src/display/nr-arena-image.h51
-rw-r--r--src/display/nr-arena-item.cpp1155
-rw-r--r--src/display/nr-arena-item.h188
-rw-r--r--src/display/nr-arena-shape.cpp1298
-rw-r--r--src/display/nr-arena-shape.h213
-rw-r--r--src/display/nr-arena.cpp131
-rw-r--r--src/display/nr-arena.h57
-rw-r--r--src/display/nr-gradient-gpl.cpp306
-rw-r--r--src/display/nr-gradient-gpl.h41
-rw-r--r--src/display/nr-plain-stuff-gdk.cpp46
-rw-r--r--src/display/nr-plain-stuff-gdk.h32
-rw-r--r--src/display/nr-plain-stuff.cpp94
-rw-r--r--src/display/nr-plain-stuff.h33
-rw-r--r--src/display/sodipodi-ctrl.cpp494
-rw-r--r--src/display/sodipodi-ctrl.h65
-rw-r--r--src/display/sodipodi-ctrlrect.cpp330
-rw-r--r--src/display/sodipodi-ctrlrect.h70
-rw-r--r--src/display/sp-canvas-util.cpp307
-rw-r--r--src/display/sp-canvas-util.h61
-rw-r--r--src/display/sp-canvas.cpp2074
-rw-r--r--src/display/sp-canvas.h186
-rw-r--r--src/display/sp-ctrlline.cpp217
-rw-r--r--src/display/sp-ctrlline.h45
-rw-r--r--src/display/sp-ctrlquadr.cpp210
-rw-r--r--src/display/sp-ctrlquadr.h43
-rw-r--r--src/display/testnr.cpp24
52 files changed, 13972 insertions, 0 deletions
diff --git a/src/display/.cvsignore b/src/display/.cvsignore
new file mode 100644
index 000000000..49cf2d7a9
--- /dev/null
+++ b/src/display/.cvsignore
@@ -0,0 +1,7 @@
+.deps
+.dirstamp
+.libs
+Makefile
+Makefile.in
+makefile
+*-test
diff --git a/src/display/Makefile_insert b/src/display/Makefile_insert
new file mode 100644
index 000000000..de6ec85c2
--- /dev/null
+++ b/src/display/Makefile_insert
@@ -0,0 +1,66 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+#
+# Sodipodi GnomeCanvas objects
+# Author: Lauris Kaplinski <lauris@ariman.ee>
+#
+# Here are major objects, used for displaying things
+#
+
+display/all: display/libspdisplay.a
+
+display/clean:
+ rm -f display/libspdisplay.a $(display_libspdisplay_a_OBJECTS)
+
+display/canvas-arena.$(OBJEXT): helper/sp-marshal.h
+display/sp-canvas.$(OBJEXT): helper/sp-marshal.h
+
+display_libspdisplay_a_SOURCES = \
+ display/nr-arena-forward.h \
+ display/nr-arena.cpp \
+ display/nr-arena.h \
+ display/nr-arena-item.cpp \
+ display/nr-arena-item.h \
+ display/nr-arena-group.cpp \
+ display/nr-arena-group.h \
+ display/nr-arena-image.cpp \
+ display/nr-arena-image.h \
+ display/nr-arena-shape.cpp \
+ display/nr-arena-shape.h \
+ display/nr-arena-glyphs.cpp \
+ display/nr-arena-glyphs.h \
+ display/canvas-arena.cpp \
+ display/canvas-arena.h \
+ display/bezier-utils.cpp \
+ display/bezier-utils.h \
+ display/canvas-bpath.cpp \
+ display/canvas-bpath.h \
+ display/canvas-grid.cpp \
+ display/canvas-grid.h \
+ display/curve.cpp \
+ display/curve.h \
+ display/display-forward.h \
+ display/gnome-canvas-acetate.cpp \
+ display/gnome-canvas-acetate.h \
+ display/guideline.cpp \
+ display/guideline.h \
+ display/nr-gradient-gpl.cpp \
+ display/nr-gradient-gpl.h \
+ display/nr-plain-stuff-gdk.cpp \
+ display/nr-plain-stuff-gdk.h \
+ display/nr-plain-stuff.cpp \
+ display/nr-plain-stuff.h \
+ display/sodipodi-ctrl.cpp \
+ display/sodipodi-ctrl.h \
+ display/sodipodi-ctrlrect.cpp \
+ display/sodipodi-ctrlrect.h \
+ display/sp-canvas-util.cpp \
+ display/sp-canvas-util.h \
+ display/sp-canvas.cpp \
+ display/sp-canvas.h \
+ display/sp-ctrlline.cpp \
+ display/sp-ctrlline.h \
+ display/sp-ctrlquadr.cpp \
+ display/sp-ctrlquadr.h
+
+display_bezier_utils_test_SOURCES = display/bezier-utils-test.cpp
+display_bezier_utils_test_LDADD = libnr/libnr.a -lglib-2.0
diff --git a/src/display/bezier-utils-test.cpp b/src/display/bezier-utils-test.cpp
new file mode 100644
index 000000000..d42672113
--- /dev/null
+++ b/src/display/bezier-utils-test.cpp
@@ -0,0 +1,334 @@
+#include "../utest/utest.h"
+#include <glib.h>
+#include <libnr/nr-macros.h> /* NR_DF_TEST_CLOSE */
+
+/* mental disclaims all responsibility for this evil idea for testing
+ static functions. The main disadvantages are that we retain the
+ #define's and `using' directives of the included file. */
+#include "bezier-utils.cpp"
+
+using NR::Point;
+
+static bool range_approx_equal(double const a[], double const b[], unsigned len);
+
+/* (Returns false if NaN encountered.) */
+template<class T>
+static bool range_equal(T const a[], T const b[], unsigned len) {
+ for (unsigned i = 0; i < len; ++i) {
+ if ( a[i] != b[i] ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline bool point_approx_equal(NR::Point const &a, NR::Point const &b, double const eps)
+{
+ using NR::X; using NR::Y;
+ return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) &&
+ NR_DF_TEST_CLOSE(a[Y], b[Y], eps) );
+}
+
+static inline double square(double const x) {
+ return x * x;
+}
+
+/** Determine whether the found control points are the same as previously found on some developer's
+ machine. Doesn't call utest__fail, just writes a message to stdout for diagnostic purposes:
+ the most important test is that the root-mean-square of errors in the estimation are low rather
+ than that the control points found are the same.
+**/
+static void compare_ctlpts(Point const est_b[], Point const exp_est_b[])
+{
+ unsigned diff_mask = 0;
+ for (unsigned i = 0; i < 4; ++i) {
+ for (unsigned d = 0; d < 2; ++d) {
+ if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) {
+ diff_mask |= 1 << ( i * 2 + d );
+ }
+ }
+ }
+ if ( diff_mask != 0 ) {
+ printf("Warning: got different control points from previously-coded (diffs=0x%x).\n",
+ diff_mask);
+ printf(" Previous:");
+ for (unsigned i = 0; i < 4; ++i) {
+ printf(" (%g, %g)", exp_est_b[i][0], exp_est_b[i][1]); // localizing ok
+ }
+ putchar('\n');
+ printf(" Found: ");
+ for (unsigned i = 0; i < 4; ++i) {
+ printf(" (%g, %g)", est_b[i][0], est_b[i][1]); // localizing ok
+ }
+ putchar('\n');
+ }
+}
+
+static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n,
+ double const exp_rms_error)
+{
+ double sum_errsq = 0.0;
+ for (unsigned i = 0; i < n; ++i) {
+ Point const fit_pt = bezier_pt(3, est_b, t[i]);
+ Point const diff = fit_pt - d[i];
+ sum_errsq += dot(diff, diff);
+ }
+ double const rms_error = sqrt( sum_errsq / n );
+ UTEST_ASSERT( rms_error <= exp_rms_error + 1.1e-6 );
+ if ( rms_error < exp_rms_error - 1.1e-6 ) {
+ /* The fitter code appears to have improved [or the floating point calculations differ
+ on this machine from the machine where exp_rms_error was calculated]. */
+ printf("N.B. rms_error regression requirement can be decreased: have rms_error=%g.\n", rms_error); // localizing ok
+ }
+}
+
+int main(int argc, char *argv[]) {
+ utest_start("bezier-utils.cpp");
+
+ UTEST_TEST("copy_without_nans_or_adjacent_duplicates") {
+ NR::Point const src[] = {
+ Point(2., 3.),
+ Point(2., 3.),
+ Point(0., 0.),
+ Point(2., 3.),
+ Point(2., 3.),
+ Point(1., 9.),
+ Point(1., 9.)
+ };
+ Point const exp_dest[] = {
+ Point(2., 3.),
+ Point(0., 0.),
+ Point(2., 3.),
+ Point(1., 9.)
+ };
+ g_assert( G_N_ELEMENTS(src) == 7 );
+ Point dest[7];
+ struct tst {
+ unsigned src_ix0;
+ unsigned src_len;
+ unsigned exp_dest_ix0;
+ unsigned exp_dest_len;
+ } const test_data[] = {
+ /* src start ix, src len, exp_dest start ix, exp dest len */
+ {0, 0, 0, 0},
+ {2, 1, 1, 1},
+ {0, 1, 0, 1},
+ {0, 2, 0, 1},
+ {0, 3, 0, 2},
+ {1, 3, 0, 3},
+ {0, 5, 0, 3},
+ {0, 6, 0, 4},
+ {0, 7, 0, 4}
+ };
+ for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) {
+ tst const &t = test_data[i];
+ UTEST_ASSERT( t.exp_dest_len
+ == copy_without_nans_or_adjacent_duplicates(src + t.src_ix0,
+ t.src_len,
+ dest) );
+ UTEST_ASSERT(range_equal(dest,
+ exp_dest + t.exp_dest_ix0,
+ t.exp_dest_len));
+ }
+ }
+
+ UTEST_TEST("bezier_pt(1)") {
+ Point const a[] = {Point(2.0, 4.0),
+ Point(1.0, 8.0)};
+ UTEST_ASSERT( bezier_pt(1, a, 0.0) == a[0] );
+ UTEST_ASSERT( bezier_pt(1, a, 1.0) == a[1] );
+ UTEST_ASSERT( bezier_pt(1, a, 0.5) == Point(1.5, 6.0) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ UTEST_ASSERT( bezier_pt(1, a, ti) == si * a[0] + ti * a[1] );
+ }
+ }
+
+ UTEST_TEST("bezier_pt(2)") {
+ Point const b[] = {Point(1.0, 2.0),
+ Point(8.0, 4.0),
+ Point(3.0, 1.0)};
+ UTEST_ASSERT( bezier_pt(2, b, 0.0) == b[0] );
+ UTEST_ASSERT( bezier_pt(2, b, 1.0) == b[2] );
+ UTEST_ASSERT( bezier_pt(2, b, 0.5) == Point(5.0, 2.75) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] );
+ Point const pt(bezier_pt(2, b, ti));
+ UTEST_ASSERT(point_approx_equal(pt, exp_pt, 1e-11));
+ }
+ }
+
+ Point const c[] = {Point(1.0, 2.0),
+ Point(8.0, 4.0),
+ Point(3.0, 1.0),
+ Point(-2.0, -4.0)};
+ UTEST_TEST("bezier_pt(3)") {
+ UTEST_ASSERT( bezier_pt(3, c, 0.0) == c[0] );
+ UTEST_ASSERT( bezier_pt(3, c, 1.0) == c[3] );
+ UTEST_ASSERT( bezier_pt(3, c, 0.5) == Point(4.0, 13.0/8.0) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ UTEST_ASSERT( LInfty( bezier_pt(3, c, ti)
+ - ( si*si*si * c[0] +
+ 3*si*si*ti * c[1] +
+ 3*si*ti*ti * c[2] +
+ ti*ti*ti * c[3] ) )
+ < 1e-4 );
+ }
+ }
+
+ struct Err_tst {
+ Point pt;
+ double u;
+ double err;
+ } const err_tst[] = {
+ {c[0], 0.0, 0.0},
+ {Point(4.0, 13.0/8.0), 0.5, 0.0},
+ {Point(4.0, 2.0), 0.5, 9.0/64.0},
+ {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0},
+ {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0},
+ {c[3], 1.0, 0.0},
+ };
+
+ UTEST_TEST("compute_max_error_ratio") {
+ Point d[G_N_ELEMENTS(err_tst)];
+ double u[G_N_ELEMENTS(err_tst)];
+ for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) {
+ Err_tst const &t = err_tst[i];
+ d[i] = t.pt;
+ u[i] = t.u;
+ }
+ g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) );
+ unsigned max_ix = ~0u;
+ double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix);
+ UTEST_ASSERT( fabs( sqrt(err_tst[4].err) - err_ratio ) < 1e-12 );
+ UTEST_ASSERT( max_ix == 4 );
+ }
+
+ UTEST_TEST("chord_length_parameterize") {
+ /* n == 2 */
+ {
+ Point const d[] = {Point(2.9415, -5.8149),
+ Point(23.021, 4.9814)};
+ double u[G_N_ELEMENTS(d)];
+ double const exp_u[] = {0.0, 1.0};
+ g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) );
+ chord_length_parameterize(d, u, G_N_ELEMENTS(d));
+ UTEST_ASSERT(range_equal(u, exp_u, G_N_ELEMENTS(exp_u)));
+ }
+
+ /* Straight line. */
+ {
+ double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0};
+ unsigned const n = G_N_ELEMENTS(exp_u);
+ Point d[n];
+ double u[n];
+ Point const a(-23.985, 4.915), b(4.9127, 5.203);
+ for (unsigned i = 0; i < n; ++i) {
+ double bi = exp_u[i], ai = 1.0 - bi;
+ d[i] = ai * a + bi * b;
+ }
+ chord_length_parameterize(d, u, n);
+ UTEST_ASSERT(range_approx_equal(u, exp_u, n));
+ }
+ }
+
+ /* Feed it some points that can be fit exactly with a single bezier segment, and see how
+ well it manages. */
+ Point const src_b[4] = {Point(5., -3.),
+ Point(8., 0.),
+ Point(4., 2.),
+ Point(3., 3.)};
+ double const t[] = {0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33, .39, .44,
+ .51, .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0};
+ unsigned const n = G_N_ELEMENTS(t);
+ Point d[n];
+ for (unsigned i = 0; i < n; ++i) {
+ d[i] = bezier_pt(3, src_b, t[i]);
+ }
+ Point const tHat1(unit_vector( src_b[1] - src_b[0] ));
+ Point const tHat2(unit_vector( src_b[2] - src_b[3] ));
+
+ UTEST_TEST("generate_bezier") {
+ Point est_b[4];
+ generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0);
+
+ compare_ctlpts(est_b, src_b);
+
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, 1e-8);
+ }
+
+ UTEST_TEST("sp_bezier_fit_cubic_full") {
+ Point est_b[4];
+ int splitpoints[2];
+ gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1);
+ UTEST_ASSERT( succ == 1 );
+
+ Point const exp_est_b[4] = {
+ Point(5.000000, -3.000000),
+ Point(7.5753, -0.4247),
+ Point(4.77533, 1.22467),
+ Point(3, 3)
+ };
+ compare_ctlpts(est_b, exp_est_b);
+
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, .307911);
+ }
+
+ UTEST_TEST("sp_bezier_fit_cubic") {
+ Point est_b[4];
+ gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2));
+ UTEST_ASSERT( succ == 1 );
+
+ Point const exp_est_b[4] = {
+ Point(5.000000, -3.000000),
+ Point(7.57134, -0.423509),
+ Point(4.77929, 1.22426),
+ Point(3, 3)
+ };
+ compare_ctlpts(est_b, exp_est_b);
+
+#if 1 /* A change has been made to right_tangent. I believe that usually this change
+ will result in better fitting, but it won't do as well for this example where
+ we happen to be feeding a t=0.999 point to the fitter. */
+ printf("TODO: Update this test case for revised right_tangent implementation.\n");
+ /* In particular, have a test case to show whether the new implementation
+ really is likely to be better on average. */
+#else
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, .307983);
+#endif
+ }
+
+ return !utest_end();
+}
+
+/* (Returns false if NaN encountered.) */
+static bool range_approx_equal(double const a[], double const b[], unsigned const len) {
+ for (unsigned i = 0; i < len; ++i) {
+ if (!( fabs( a[i] - b[i] ) < 1e-4 )) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/display/bezier-utils-work.txt b/src/display/bezier-utils-work.txt
new file mode 100644
index 000000000..8604c1207
--- /dev/null
+++ b/src/display/bezier-utils-work.txt
@@ -0,0 +1,34 @@
+min .5 * sum_i lensq(bez_pt(b, u[i]) - d[i])
+
+lensq(d)=dot(d, d) = d.x * d.x + d.y * d.y
+
+sum_i (f(i) + g(i)) = sum_i f(i) + sum_i g(i), so
+we can separate into x,y parts. Since they are the same, we write `z' in the below
+to mean either x or y.
+
+.5 * sum_i (bez_pt(b, u[i]) - d[i]).z^2
+
+= .5 * sum_i (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B2(u[i]) * b[2] +
+ B3(u[i]) * b[3]
+ - d[i] ).z^2
+
+= H.
+
+Suppose that b[0,1,3] are fixed (with b[1] perhaps being calculated
+from a prior call to existing generate_bezier).
+
+d H / d b[2].z = sum_i B2(u[i]) * (bez_pt(b, u[i]) - d[i]).z
+
+Solve for dH/db[2].z==0:
+
+-sum_i B2(u[i]) B2(u[i]) * b[2].z = sum_i B2(u[i]) * (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B3(u[i]) * b[3]
+ - d[i] ).z
+b[2].z = ((sum_i B2(u[i]) * (B0(u[i]) * b[0] +
+ B1(u[i]) * b[1] +
+ B3(u[i]) * b[3]
+ - d[i] ).z)
+ / -sum_i (B2(u[i]))^2)
diff --git a/src/display/bezier-utils.cpp b/src/display/bezier-utils.cpp
new file mode 100644
index 000000000..8316c86cc
--- /dev/null
+++ b/src/display/bezier-utils.cpp
@@ -0,0 +1,983 @@
+#define __SP_BEZIER_UTILS_C__
+
+/** \file
+ * Bezier interpolation for inkscape drawing code.
+ */
+/*
+ * Original code published in:
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2003,2004 Monash University
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#define SP_HUGE 1e5
+#define noBEZIER_DEBUG
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_IEEEFP_H
+# include <ieefp.h>
+#endif
+
+#include <glib/gmessages.h>
+#include <glib/gmem.h>
+#include "bezier-utils.h"
+#include <libnr/nr-point-fns.h>
+
+#include "isnan.h"
+
+
+typedef NR::Point BezierCurve[];
+
+/* Forward declarations */
+static void generate_bezier(NR::Point b[], NR::Point const d[], gdouble const u[], unsigned len,
+ NR::Point const &tHat1, NR::Point const &tHat2, double tolerance_sq);
+static void estimate_lengths(NR::Point bezier[],
+ NR::Point const data[], gdouble const u[], unsigned len,
+ NR::Point const &tHat1, NR::Point const &tHat2);
+static void estimate_bi(NR::Point b[4], unsigned ei,
+ NR::Point const data[], double const u[], unsigned len);
+static void reparameterize(NR::Point const d[], unsigned len, double u[], BezierCurve const bezCurve);
+static gdouble NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble u);
+static NR::Point bezier_pt(unsigned degree, NR::Point const V[], gdouble t);
+static NR::Point sp_darray_center_tangent(NR::Point const d[], unsigned center, unsigned length);
+static NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const len);
+static unsigned copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[]);
+static void chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned len);
+static double compute_max_error_ratio(NR::Point const d[], double const u[], unsigned len,
+ BezierCurve const bezCurve, double tolerance,
+ unsigned *splitPoint);
+static double compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve,
+ double const tolerance);
+
+
+static NR::Point const unconstrained_tangent(0, 0);
+
+
+/*
+ * B0, B1, B2, B3 : Bezier multipliers
+ */
+
+#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B2(u) ( 3 * u * u * ( 1.0 - u ) )
+#define B3(u) ( u * u * u )
+
+#ifdef BEZIER_DEBUG
+# define DOUBLE_ASSERT(x) g_assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) )
+# define BEZIER_ASSERT(b) do { \
+ DOUBLE_ASSERT((b)[0][NR::X]); DOUBLE_ASSERT((b)[0][NR::Y]); \
+ DOUBLE_ASSERT((b)[1][NR::X]); DOUBLE_ASSERT((b)[1][NR::Y]); \
+ DOUBLE_ASSERT((b)[2][NR::X]); DOUBLE_ASSERT((b)[2][NR::Y]); \
+ DOUBLE_ASSERT((b)[3][NR::X]); DOUBLE_ASSERT((b)[3][NR::Y]); \
+ } while(0)
+#else
+# define DOUBLE_ASSERT(x) do { } while(0)
+# define BEZIER_ASSERT(b) do { } while(0)
+#endif
+
+
+/**
+ * Fit a single-segment Bezier curve to a set of digitized points.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+gint
+sp_bezier_fit_cubic(NR::Point *bezier, NR::Point const *data, gint len, gdouble error)
+{
+ return sp_bezier_fit_cubic_r(bezier, data, len, error, 1);
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, with
+ * possible weedout of identical points and NaNs.
+ *
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+gint
+sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint const len, gdouble const error, unsigned const max_beziers)
+{
+ g_return_val_if_fail(bezier != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(len > 0, -1);
+ g_return_val_if_fail(max_beziers < (1ul << (31 - 2 - 1 - 3)), -1);
+
+ NR::Point *uniqued_data = g_new(NR::Point, len);
+ unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data);
+
+ if ( uniqued_len < 2 ) {
+ g_free(uniqued_data);
+ return 0;
+ }
+
+ /* Call fit-cubic function with recursion. */
+ gint const ret = sp_bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len,
+ unconstrained_tangent, unconstrained_tangent,
+ error, max_beziers);
+ g_free(uniqued_data);
+ return ret;
+}
+
+/**
+ * Copy points from src to dest, filter out points containing NaN and
+ * adjacent points with equal x and y.
+ * \return length of dest
+ */
+static unsigned
+copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[])
+{
+ unsigned si = 0;
+ for (;;) {
+ if ( si == src_len ) {
+ return 0;
+ }
+ if (!isNaN(src[si][NR::X]) &&
+ !isNaN(src[si][NR::Y])) {
+ dest[0] = NR::Point(src[si]);
+ ++si;
+ break;
+ }
+ }
+ unsigned di = 0;
+ for (; si < src_len; ++si) {
+ NR::Point const src_pt = NR::Point(src[si]);
+ if ( src_pt != dest[di]
+ && !isNaN(src_pt[NR::X])
+ && !isNaN(src_pt[NR::Y])) {
+ dest[++di] = src_pt;
+ }
+ }
+ unsigned dest_len = di + 1;
+ g_assert( dest_len <= src_len );
+ return dest_len;
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, without
+ * possible weedout of identical points and NaNs.
+ *
+ * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1].
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ */
+gint
+sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[],
+ NR::Point const data[], gint const len,
+ NR::Point const &tHat1, NR::Point const &tHat2,
+ double const error, unsigned const max_beziers)
+{
+ int const maxIterations = 4; /* Max times to try iterating */
+
+ g_return_val_if_fail(bezier != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(len > 0, -1);
+ g_return_val_if_fail(max_beziers >= 1, -1);
+ g_return_val_if_fail(error >= 0.0, -1);
+
+ if ( len < 2 ) return 0;
+
+ if ( len == 2 ) {
+ /* We have 2 points, which can be fitted trivially. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+ double const dist = ( L2( data[len - 1]
+ - data[0] )
+ / 3.0 );
+ if (isNaN(dist)) {
+ /* Numerical problem, fall back to straight line segment. */
+ bezier[1] = bezier[0];
+ bezier[2] = bezier[3];
+ } else {
+ bezier[1] = ( is_zero(tHat1)
+ ? ( 2 * bezier[0] + bezier[3] ) / 3.
+ : bezier[0] + dist * tHat1 );
+ bezier[2] = ( is_zero(tHat2)
+ ? ( bezier[0] + 2 * bezier[3] ) / 3.
+ : bezier[3] + dist * tHat2 );
+ }
+ BEZIER_ASSERT(bezier);
+ return 1;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ unsigned splitPoint; /* Point to split point set at. */
+ bool is_corner;
+ {
+ double *u = g_new(double, len);
+ chord_length_parameterize(data, u, len);
+ if ( u[len - 1] == 0.0 ) {
+ /* Zero-length path: every point in data[] is the same.
+ *
+ * (Clients aren't allowed to pass such data; handling the case is defensive
+ * programming.)
+ */
+ g_free(u);
+ return 0;
+ }
+
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+
+ /* Find max deviation of points to fitted curve. */
+ double const tolerance = sqrt(error + 1e-9);
+ double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ g_free(u);
+ return 1;
+ }
+
+ /* If error not too large, then try some reparameterization and iteration. */
+ if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) {
+ for (int i = 0; i < maxIterations; i++) {
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+ maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ g_free(u);
+ return 1;
+ }
+ }
+ }
+ g_free(u);
+ is_corner = (maxErrorRatio < 0);
+ }
+
+ if (is_corner) {
+ g_assert(splitPoint < unsigned(len));
+ if (splitPoint == 0) {
+ if (is_zero(tHat1)) {
+ /* Got spike even with unconstrained initial tangent. */
+ ++splitPoint;
+ } else {
+ return sp_bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2,
+ error, max_beziers);
+ }
+ } else if (splitPoint == unsigned(len - 1)) {
+ if (is_zero(tHat2)) {
+ /* Got spike even with unconstrained final tangent. */
+ --splitPoint;
+ } else {
+ return sp_bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent,
+ error, max_beziers);
+ }
+ }
+ }
+
+ if ( 1 < max_beziers ) {
+ /*
+ * Fitting failed -- split at max error point and fit recursively
+ */
+ unsigned const rec_max_beziers1 = max_beziers - 1;
+
+ NR::Point recTHat2, recTHat1;
+ if (is_corner) {
+ g_return_val_if_fail(0 < splitPoint && splitPoint < unsigned(len - 1), -1);
+ recTHat1 = recTHat2 = unconstrained_tangent;
+ } else {
+ /* Unit tangent vector at splitPoint. */
+ recTHat2 = sp_darray_center_tangent(data, splitPoint, len);
+ recTHat1 = -recTHat2;
+ }
+ gint const nsegs1 = sp_bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1,
+ tHat1, recTHat2, error, rec_max_beziers1);
+ if ( nsegs1 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[1]: recursive call failed\n");
+#endif
+ return -1;
+ }
+ g_assert( nsegs1 != 0 );
+ if (split_points != NULL) {
+ split_points[nsegs1 - 1] = splitPoint;
+ }
+ unsigned const rec_max_beziers2 = max_beziers - nsegs1;
+ gint const nsegs2 = sp_bezier_fit_cubic_full(bezier + nsegs1*4,
+ ( split_points == NULL
+ ? NULL
+ : split_points + nsegs1 ),
+ data + splitPoint, len - splitPoint,
+ recTHat1, tHat2, error, rec_max_beziers2);
+ if ( nsegs2 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[2]: recursive call failed\n");
+#endif
+ return -1;
+ }
+
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n",
+ nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers);
+#endif
+ return nsegs1 + nsegs2;
+ } else {
+ return -1;
+ }
+}
+
+
+/**
+ * Fill in \a bezier[] based on the given data and tangent requirements, using
+ * a least-squares fit.
+ *
+ * Each of tHat1 and tHat2 should be either a zero vector or a unit vector.
+ * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise,
+ * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3].
+ *
+ * \param tolerance_sq Used only for an initial guess as to tangent directions
+ * when \a tHat1 or \a tHat2 is zero.
+ */
+static void
+generate_bezier(NR::Point bezier[],
+ NR::Point const data[], gdouble const u[], unsigned const len,
+ NR::Point const &tHat1, NR::Point const &tHat2,
+ double const tolerance_sq)
+{
+ bool const est1 = is_zero(tHat1);
+ bool const est2 = is_zero(tHat2);
+ NR::Point est_tHat1( est1
+ ? sp_darray_left_tangent(data, len, tolerance_sq)
+ : tHat1 );
+ NR::Point est_tHat2( est2
+ ? sp_darray_right_tangent(data, len, tolerance_sq)
+ : tHat2 );
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ /* We find that sp_darray_right_tangent tends to produce better results
+ for our current freehand tool than full estimation. */
+ if (est1) {
+ estimate_bi(bezier, 1, data, u, len);
+ if (bezier[1] != bezier[0]) {
+ est_tHat1 = unit_vector(bezier[1] - bezier[0]);
+ }
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ }
+}
+
+
+static void
+estimate_lengths(NR::Point bezier[],
+ NR::Point const data[], gdouble const uPrime[], unsigned const len,
+ NR::Point const &tHat1, NR::Point const &tHat2)
+{
+ double C[2][2]; /* Matrix C. */
+ double X[2]; /* Matrix X. */
+
+ /* Create the C and X matrices. */
+ C[0][0] = 0.0;
+ C[0][1] = 0.0;
+ C[1][0] = 0.0;
+ C[1][1] = 0.0;
+ X[0] = 0.0;
+ X[1] = 0.0;
+
+ /* First and last control points of the Bezier curve are positioned exactly at the first and
+ last data points. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+
+ for (unsigned i = 0; i < len; i++) {
+ /* Bezier control point coefficients. */
+ double const b0 = B0(uPrime[i]);
+ double const b1 = B1(uPrime[i]);
+ double const b2 = B2(uPrime[i]);
+ double const b3 = B3(uPrime[i]);
+
+ /* rhs for eqn */
+ NR::Point const a1 = b1 * tHat1;
+ NR::Point const a2 = b2 * tHat2;
+
+ C[0][0] += dot(a1, a1);
+ C[0][1] += dot(a1, a2);
+ C[1][0] = C[0][1];
+ C[1][1] += dot(a2, a2);
+
+ /* Additional offset to the data point from the predicted point if we were to set bezier[1]
+ to bezier[0] and bezier[2] to bezier[3]. */
+ NR::Point const shortfall
+ = ( data[i]
+ - ( ( b0 + b1 ) * bezier[0] )
+ - ( ( b2 + b3 ) * bezier[3] ) );
+ X[0] += dot(a1, shortfall);
+ X[1] += dot(a2, shortfall);
+ }
+
+ /* We've constructed a pair of equations in the form of a matrix product C * alpha = X.
+ Now solve for alpha. */
+ double alpha_l, alpha_r;
+
+ /* Compute the determinants of C and X. */
+ double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+ if ( det_C0_C1 != 0 ) {
+ /* Apparently Kramer's rule. */
+ double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+ double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+ alpha_l = det_X_C1 / det_C0_C1;
+ alpha_r = det_C0_X / det_C0_C1;
+ } else {
+ /* The matrix is under-determined. Try requiring alpha_l == alpha_r.
+ *
+ * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same
+ * variable in the equations. We can do this by adding the columns of C to form a single
+ * column, to be multiplied by alpha to give the column vector X.
+ *
+ * We try each row in turn.
+ */
+ double const c0 = C[0][0] + C[0][1];
+ if (c0 != 0) {
+ alpha_l = alpha_r = X[0] / c0;
+ } else {
+ double const c1 = C[1][0] + C[1][1];
+ if (c1 != 0) {
+ alpha_l = alpha_r = X[1] / c1;
+ } else {
+ /* Let the below code handle this. */
+ alpha_l = alpha_r = 0.;
+ }
+ }
+ }
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get
+ coincident control points that lead to divide by zero in any subsequent
+ NewtonRaphsonRootFind() call.) */
+ /// \todo Check whether this special-casing is necessary now that
+ /// NewtonRaphsonRootFind handles non-positive denominator.
+ if ( alpha_l < 1.0e-6 ||
+ alpha_r < 1.0e-6 )
+ {
+ alpha_l = alpha_r = ( L2( data[len - 1]
+ - data[0] )
+ / 3.0 );
+ }
+
+ /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and
+ right, respectively. */
+ bezier[1] = alpha_l * tHat1 + bezier[0];
+ bezier[2] = alpha_r * tHat2 + bezier[3];
+
+ return;
+}
+
+static double lensq(NR::Point const p) {
+ return dot(p, p);
+}
+
+static void
+estimate_bi(NR::Point bezier[4], unsigned const ei,
+ NR::Point const data[], double const u[], unsigned const len)
+{
+ g_return_if_fail(1 <= ei && ei <= 2);
+ unsigned const oi = 3 - ei;
+ double num[2] = {0., 0.};
+ double den = 0.;
+ for (unsigned i = 0; i < len; ++i) {
+ double const ui = u[i];
+ double const b[4] = {
+ B0(ui),
+ B1(ui),
+ B2(ui),
+ B3(ui)
+ };
+
+ for (unsigned d = 0; d < 2; ++d) {
+ num[d] += b[ei] * (b[0] * bezier[0][d] +
+ b[oi] * bezier[oi][d] +
+ b[3] * bezier[3][d] +
+ - data[i][d]);
+ }
+ den -= b[ei] * b[ei];
+ }
+
+ if (den != 0.) {
+ for (unsigned d = 0; d < 2; ++d) {
+ bezier[ei][d] = num[d] / den;
+ }
+ } else {
+ bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.;
+ }
+}
+
+/**
+ * Given set of points and their parameterization, try to find a better assignment of parameter
+ * values for the points.
+ *
+ * \param d Array of digitized points.
+ * \param u Current parameter values.
+ * \param bezCurve Current fitted curve.
+ * \param len Number of values in both d and u arrays.
+ * Also the size of the array that is allocated for return.
+ */
+static void
+reparameterize(NR::Point const d[],
+ unsigned const len,
+ double u[],
+ BezierCurve const bezCurve)
+{
+ g_assert( 2 <= len );
+
+ unsigned const last = len - 1;
+ g_assert( bezCurve[0] == d[0] );
+ g_assert( bezCurve[3] == d[last] );
+ g_assert( u[0] == 0.0 );
+ g_assert( u[last] == 1.0 );
+ /* Otherwise, consider including 0 and last in the below loop. */
+
+ for (unsigned i = 1; i < last; i++) {
+ u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]);
+ }
+}
+
+/**
+ * Use Newton-Raphson iteration to find better root.
+ *
+ * \param Q Current fitted curve
+ * \param P Digitized point
+ * \param u Parameter value for "P"
+ *
+ * \return Improved u
+ */
+static gdouble
+NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble const u)
+{
+ g_assert( 0.0 <= u );
+ g_assert( u <= 1.0 );
+
+ /* Generate control vertices for Q'. */
+ NR::Point Q1[3];
+ for (unsigned i = 0; i < 3; i++) {
+ Q1[i] = 3.0 * ( Q[i+1] - Q[i] );
+ }
+
+ /* Generate control vertices for Q''. */
+ NR::Point Q2[2];
+ for (unsigned i = 0; i < 2; i++) {
+ Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] );
+ }
+
+ /* Compute Q(u), Q'(u) and Q''(u). */
+ NR::Point const Q_u = bezier_pt(3, Q, u);
+ NR::Point const Q1_u = bezier_pt(2, Q1, u);
+ NR::Point const Q2_u = bezier_pt(1, Q2, u);
+
+ /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the
+ distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the
+ distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum
+ distance from P to Q(u)). */
+ NR::Point const diff = Q_u - P;
+ double numerator = dot(diff, Q1_u);
+ double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u);
+
+ double improved_u;
+ if ( denominator > 0. ) {
+ /* One iteration of Newton-Raphson:
+ improved_u = u - f(u)/f'(u) */
+ improved_u = u - ( numerator / denominator );
+ } else {
+ /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather
+ than local minimum), so we move an arbitrary amount in the right direction. */
+ if ( numerator > 0. ) {
+ improved_u = u * .98 - .01;
+ } else if ( numerator < 0. ) {
+ /* Deliberately asymmetrical, to reduce the chance of cycling. */
+ improved_u = .031 + u * .98;
+ } else {
+ improved_u = u;
+ }
+ }
+
+ if (!isFinite(improved_u)) {
+ improved_u = u;
+ } else if ( improved_u < 0.0 ) {
+ improved_u = 0.0;
+ } else if ( improved_u > 1.0 ) {
+ improved_u = 1.0;
+ }
+
+ /* Ensure that improved_u isn't actually worse. */
+ {
+ double const diff_lensq = lensq(diff);
+ for (double proportion = .125; ; proportion += .125) {
+ if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) {
+ if ( proportion > 1.0 ) {
+ //g_warning("found proportion %g", proportion);
+ improved_u = u;
+ break;
+ }
+ improved_u = ( ( 1 - proportion ) * improved_u +
+ proportion * u );
+ } else {
+ break;
+ }
+ }
+ }
+
+ DOUBLE_ASSERT(improved_u);
+ return improved_u;
+}
+
+/**
+ * Evaluate a Bezier curve at parameter value \a t.
+ *
+ * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc.
+ * \param V The control points for the Bezier curve. Must have (\a degree+1)
+ * elements.
+ * \param t The "parameter" value, specifying whereabouts along the curve to
+ * evaluate. Typically in the range [0.0, 1.0].
+ *
+ * Let s = 1 - t.
+ * BezierII(1, V) gives (s, t) * V, i.e. t of the way
+ * from V[0] to V[1].
+ * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V.
+ * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V.
+ *
+ * The derivative of BezierII(i, V) with respect to t
+ * is i * BezierII(i-1, V'), where for all j, V'[j] =
+ * V[j + 1] - V[j].
+ */
+static NR::Point
+bezier_pt(unsigned const degree, NR::Point const V[], gdouble const t)
+{
+ /** Pascal's triangle. */
+ static int const pascal[4][4] = {{1},
+ {1, 1},
+ {1, 2, 1},
+ {1, 3, 3, 1}};
+ g_assert( degree < G_N_ELEMENTS(pascal) );
+ gdouble const s = 1.0 - t;
+
+ /* Calculate powers of t and s. */
+ double spow[4];
+ double tpow[4];
+ spow[0] = 1.0; spow[1] = s;
+ tpow[0] = 1.0; tpow[1] = t;
+ for (unsigned i = 1; i < degree; ++i) {
+ spow[i + 1] = spow[i] * s;
+ tpow[i + 1] = tpow[i] * t;
+ }
+
+ NR::Point ret = spow[degree] * V[0];
+ for (unsigned i = 1; i <= degree; ++i) {
+ ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i];
+ }
+ return ret;
+}
+
+/*
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ * Approximate unit tangents at endpoints and "center" of digitized curve
+ */
+
+/**
+ * Estimate the (forward) tangent at point d[first + 0.5].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ * \pre (2 \<= len) and (d[0] != d[1]).
+ **/
+NR::Point
+sp_darray_left_tangent(NR::Point const d[], unsigned const len)
+{
+ g_assert( len >= 2 );
+ g_assert( d[0] != d[1] );
+ return unit_vector( d[1] - d[0] );
+}
+
+/**
+ * Estimates the (backward) tangent at d[last - 0.5].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+static NR::Point
+sp_darray_right_tangent(NR::Point const d[], unsigned const len)
+{
+ g_assert( 2 <= len );
+ unsigned const last = len - 1;
+ unsigned const prev = last - 1;
+ g_assert( d[last] != d[prev] );
+ return unit_vector( d[prev] - d[last] );
+}
+
+/**
+ * Estimate the (forward) tangent at point d[0].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ *
+ * \pre 2 \<= len.
+ * \pre d[0] != d[1].
+ * \pre all[p in d] in_svg_plane(p).
+ * \post is_unit_vector(ret).
+ **/
+NR::Point
+sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq)
+{
+ g_assert( 2 <= len );
+ g_assert( 0 <= tolerance_sq );
+ for (unsigned i = 1;;) {
+ NR::Point const pi(d[i]);
+ NR::Point const t(pi - d[0]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ ++i;
+ if (i == len) {
+ return ( distsq == 0
+ ? sp_darray_left_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[last].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+NR::Point
+sp_darray_right_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq)
+{
+ g_assert( 2 <= len );
+ g_assert( 0 <= tolerance_sq );
+ unsigned const last = len - 1;
+ for (unsigned i = last - 1;; i--) {
+ NR::Point const pi(d[i]);
+ NR::Point const t(pi - d[last]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ if (i == 0) {
+ return ( distsq == 0
+ ? sp_darray_right_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[center], by averaging the two
+ * segments connected to d[center] (and then normalizing the result).
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre (0 \< center \< len - 1) and d is uniqued (at least in
+ * the immediate vicinity of \a center).
+ */
+static NR::Point
+sp_darray_center_tangent(NR::Point const d[],
+ unsigned const center,
+ unsigned const len)
+{
+ g_assert( center != 0 );
+ g_assert( center < len - 1 );
+
+ NR::Point ret;
+ if ( d[center + 1] == d[center - 1] ) {
+ /* Rotate 90 degrees in an arbitrary direction. */
+ NR::Point const diff = d[center] - d[center - 1];
+ ret = NR::rot90(diff);
+ } else {
+ ret = d[center - 1] - d[center + 1];
+ }
+ ret.normalize();
+ return ret;
+}
+
+
+/**
+ * Assign parameter values to digitized points using relative distances between points.
+ *
+ * \pre Parameter array u must have space for \a len items.
+ */
+static void
+chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned const len)
+{
+ g_return_if_fail( 2 <= len );
+
+ /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */
+ u[0] = 0.0;
+ for (unsigned i = 1; i < len; i++) {
+ double const dist = L2( d[i] - d[i-1] );
+ u[i] = u[i-1] + dist;
+ }
+
+ /* Then scale to [0.0 .. 1.0]. */
+ gdouble tot_len = u[len - 1];
+ g_return_if_fail( tot_len != 0 );
+ if (isFinite(tot_len)) {
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] /= tot_len;
+ }
+ } else {
+ /* We could do better, but this probably never happens anyway. */
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] = i / (gdouble) ( len - 1 );
+ }
+ }
+
+ /** \todo
+ * It's been reported that u[len - 1] can differ from 1.0 on some
+ * systems (amd64), despite it having been calculated as x / x where x
+ * is isFinite and non-zero.
+ */
+ if (u[len - 1] != 1) {
+ double const diff = u[len - 1] - 1;
+ if (fabs(diff) > 1e-13) {
+ g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1",
+ u[len - 1], diff);
+ }
+ u[len - 1] = 1;
+ }
+
+#ifdef BEZIER_DEBUG
+ g_assert( u[0] == 0.0 && u[len - 1] == 1.0 );
+ for (unsigned i = 1; i < len; i++) {
+ g_assert( u[i] >= u[i-1] );
+ }
+#endif
+}
+
+
+
+
+/**
+ * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum
+ * error is non-zero) set \a *splitPoint to the corresponding index.
+ *
+ * \pre 2 \<= len.
+ * \pre u[0] == 0.
+ * \pre u[len - 1] == 1.0.
+ * \post ((ret == 0.0)
+ * || ((*splitPoint \< len - 1)
+ * \&\& (*splitPoint != 0 || ret \< 0.0))).
+ */
+static gdouble
+compute_max_error_ratio(NR::Point const d[], double const u[], unsigned const len,
+ BezierCurve const bezCurve, double const tolerance,
+ unsigned *const splitPoint)
+{
+ g_assert( 2 <= len );
+ unsigned const last = len - 1;
+ g_assert( bezCurve[0] == d[0] );
+ g_assert( bezCurve[3] == d[last] );
+ g_assert( u[0] == 0.0 );
+ g_assert( u[last] == 1.0 );
+ /* I.e. assert that the error for the first & last points is zero.
+ * Otherwise we should include those points in the below loop.
+ * The assertion is also necessary to ensure 0 < splitPoint < last.
+ */
+
+ double maxDistsq = 0.0; /* Maximum error */
+ double max_hook_ratio = 0.0;
+ unsigned snap_end = 0;
+ NR::Point prev = bezCurve[0];
+ for (unsigned i = 1; i <= last; i++) {
+ NR::Point const curr = bezier_pt(3, bezCurve, u[i]);
+ double const distsq = lensq( curr - d[i] );
+ if ( distsq > maxDistsq ) {
+ maxDistsq = distsq;
+ *splitPoint = i;
+ }
+ double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance);
+ if (max_hook_ratio < hook_ratio) {
+ max_hook_ratio = hook_ratio;
+ snap_end = i;
+ }
+ prev = curr;
+ }
+
+ double const dist_ratio = sqrt(maxDistsq) / tolerance;
+ double ret;
+ if (max_hook_ratio <= dist_ratio) {
+ ret = dist_ratio;
+ } else {
+ g_assert(0 < snap_end);
+ ret = -max_hook_ratio;
+ *splitPoint = snap_end - 1;
+ }
+ g_assert( ret == 0.0
+ || ( ( *splitPoint < last )
+ && ( *splitPoint != 0 || ret < 0. ) ) );
+ return ret;
+}
+
+/**
+ * Whereas compute_max_error_ratio() checks for itself that each data point
+ * is near some point on the curve, this function checks that each point on
+ * the curve is near some data point (or near some point on the polyline
+ * defined by the data points, or something like that: we allow for a
+ * "reasonable curviness" from such a polyline). "Reasonable curviness"
+ * means we draw a circle centred at the midpoint of a..b, of radius
+ * proportional to the length |a - b|, and require that each point on the
+ * segment of bezCurve between the parameters of a and b be within that circle.
+ * If any point P on the bezCurve segment is outside of that allowable
+ * region (circle), then we return some metric that increases with the
+ * distance from P to the circle.
+ *
+ * Given that this is a fairly arbitrary criterion for finding appropriate
+ * places for sharp corners, we test only one point on bezCurve, namely
+ * the point on bezCurve with parameter halfway between our estimated
+ * parameters for a and b. (Alternatives are taking the farthest of a
+ * few parameters between those of a and b, or even using a variant of
+ * NewtonRaphsonFindRoot() for finding the maximum rather than minimum
+ * distance.)
+ */
+static double
+compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve,
+ double const tolerance)
+{
+ NR::Point const P = bezier_pt(3, bezCurve, u);
+ NR::Point const diff = .5 * (a + b) - P;
+ double const dist = NR::L2(diff);
+ if (dist < tolerance) {
+ return 0;
+ }
+ double const allowed = NR::L2(b - a) + tolerance;
+ return dist / allowed;
+ /** \todo
+ * effic: Hooks are very rare. We could start by comparing
+ * distsq, only resorting to the more expensive L2 in cases of
+ * uncertainty.
+ */
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/display/bezier-utils.h b/src/display/bezier-utils.h
new file mode 100644
index 000000000..f281ab220
--- /dev/null
+++ b/src/display/bezier-utils.h
@@ -0,0 +1,49 @@
+#ifndef __SP_BEZIER_UTILS_H__
+#define __SP_BEZIER_UTILS_H__
+
+/*
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * from "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ */
+
+#include <libnr/nr-forward.h>
+#include <glib/gtypes.h>
+
+/* Bezier approximation utils */
+
+gint sp_bezier_fit_cubic(NR::Point bezier[], NR::Point const data[], gint len, gdouble error);
+
+gint sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint len, gdouble error,
+ unsigned max_beziers);
+
+gint sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[], NR::Point const data[], gint len,
+ NR::Point const &tHat1, NR::Point const &tHat2,
+ gdouble error, unsigned max_beziers);
+
+NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len);
+NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq);
+NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const length, double const tolerance_sq);
+
+
+#endif /* __SP_BEZIER_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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/canvas-arena.cpp b/src/display/canvas-arena.cpp
new file mode 100644
index 000000000..1d77de158
--- /dev/null
+++ b/src/display/canvas-arena.cpp
@@ -0,0 +1,451 @@
+#define __SP_CANVAS_ARENA_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#include <libnr/nr-blit.h>
+#include <gtk/gtksignal.h>
+
+#include <display/display-forward.h>
+#include <display/sp-canvas-util.h>
+#include <helper/sp-marshal.h>
+#include <display/nr-arena.h>
+#include <display/nr-arena-group.h>
+#include <display/canvas-arena.h>
+
+enum {
+ ARENA_EVENT,
+ LAST_SIGNAL
+};
+
+static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass);
+static void sp_canvas_arena_init(SPCanvasArena *group);
+static void sp_canvas_arena_destroy(GtkObject *object);
+
+static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+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 (NRArena *arena, NRArenaItem *item, void *data);
+static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data);
+
+NRArenaEventVector carenaev = {
+ {NULL},
+ sp_canvas_arena_request_update,
+ sp_canvas_arena_request_render
+};
+
+static SPCanvasItemClass *parent_class;
+static guint signals[LAST_SIGNAL] = {0};
+
+GtkType
+sp_canvas_arena_get_type (void)
+{
+ static GtkType type = 0;
+ if (!type) {
+ GtkTypeInfo info = {
+ "SPCanvasArena",
+ sizeof (SPCanvasArena),
+ sizeof (SPCanvasArenaClass),
+ (GtkClassInitFunc) sp_canvas_arena_class_init,
+ (GtkObjectInitFunc) sp_canvas_arena_init,
+ NULL, NULL, NULL
+ };
+ type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
+ }
+ return type;
+}
+
+static void
+sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
+{
+ GtkObjectClass *object_class;
+ SPCanvasItemClass *item_class;
+
+ object_class = (GtkObjectClass *) klass;
+ item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
+
+ signals[ARENA_EVENT] = gtk_signal_new ("arena_event",
+ GTK_RUN_LAST,
+ GTK_CLASS_TYPE(object_class),
+ GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event),
+ sp_marshal_INT__POINTER_POINTER,
+ GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
+
+ object_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;
+}
+
+static void
+sp_canvas_arena_init (SPCanvasArena *arena)
+{
+ arena->sticky = FALSE;
+
+ arena->arena = NRArena::create();
+ arena->root = NRArenaGroup::create(arena->arena);
+ nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE);
+
+#ifdef arena_item_tile_cache
+ arena->root->skipCaching=true;
+#endif
+
+ arena->active = NULL;
+
+ nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena);
+}
+
+static void
+sp_canvas_arena_destroy (GtkObject *object)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (object);
+
+ if (arena->active) {
+ nr_object_unref ((NRObject *) arena->active);
+ arena->active = NULL;
+ }
+
+ if (arena->root) {
+ nr_arena_item_unref (arena->root);
+ arena->root = NULL;
+ }
+
+ if (arena->arena) {
+ nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena);
+
+ nr_object_unref ((NRObject *) arena->arena);
+ arena->arena = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ if (((SPCanvasItemClass *) parent_class)->update)
+ (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
+
+ arena->gc.transform = affine;
+
+ guint reset;
+ reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE;
+
+ nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset);
+
+ item->x1 = arena->root->bbox.x0 - 1;
+ item->y1 = arena->root->bbox.y0 - 1;
+ item->x2 = arena->root->bbox.x1 + 1;
+ item->y2 = arena->root->bbox.y1 + 1;
+
+ if (arena->cursor) {
+ /* Mess with enter/leave notifiers */
+ NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
+ if (new_arena != arena->active) {
+ GdkEventCrossing ec;
+ ec.window = GTK_WIDGET (item->canvas)->window;
+ ec.send_event = TRUE;
+ ec.subwindow = ec.window;
+ ec.time = GDK_CURRENT_TIME;
+ ec.x = arena->c[NR::X];
+ ec.y = arena->c[NR::Y];
+ /* fixme: */
+ if (arena->active) {
+ ec.type = GDK_LEAVE_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ /* fixme: This is not optimal - better track ::destroy (Lauris) */
+ if (arena->active) nr_object_unref ((NRObject *) arena->active);
+ arena->active = new_arena;
+ if (arena->active) nr_object_ref ((NRObject *) arena->active);
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ }
+}
+
+#ifdef arena_item_tile_cache
+extern void age_cache(void);
+#endif
+
+static void
+sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ gint bw, bh, sw, sh;
+ gint x, y;
+
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
+ NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER,
+ NR_ARENA_ITEM_STATE_NONE);
+
+ sp_canvas_prepare_buffer(buf);
+
+#ifdef arena_item_tile_cache
+ age_cache();
+#endif
+ bw = buf->rect.x1 - buf->rect.x0;
+ bh = buf->rect.y1 - buf->rect.y0;
+ if ((bw < 1) || (bh < 1)) return;
+
+ if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
+ /* 256K is the cached buffer and we need 4 channels */
+ if (bw * bh < 65536) { // 256K/4
+ /* We can go with single buffer */
+ sw = bw;
+ sh = bh;
+ } else if (bw <= 4096) {
+ /* Go with row buffer */
+ sw = bw;
+ sh = 65536 / bw;
+ } else if (bh <= 4096) {
+ /* Go with column buffer */
+ sw = 65536 / bh;
+ sh = bh;
+ } else {
+ sw = 256;
+ sh = 256;
+ }
+ } else { // paths only, so 1M works faster
+ /* 1M is the cached buffer and we need 4 channels */
+ if (bw * bh < 262144) { // 1M/4
+ /* We can go with single buffer */
+ sw = bw;
+ sh = bh;
+ } else if (bw <= 8192) {
+ /* Go with row buffer */
+ sw = bw;
+ sh = 262144 / bw;
+ } else if (bh <= 8192) {
+ /* Go with column buffer */
+ sw = 262144 / bh;
+ sh = bh;
+ } else {
+ sw = 512;
+ sh = 512;
+ }
+ }
+
+/* fixme: RGB transformed bitmap blit is not implemented (Lauris) */
+/* And even if it would be, unless it uses MMX there is little reason to go RGB */
+#define STRICT_RGBA
+
+ for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
+ for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
+ NRRectL area;
+#ifdef STRICT_RGBA
+ NRPixBlock pb;
+#endif
+ NRPixBlock cb;
+
+ area.x0 = x;
+ area.y0 = y;
+ area.x1 = MIN (x + sw, buf->rect.x1);
+ area.y1 = MIN (y + sh, buf->rect.y1);
+
+#ifdef STRICT_RGBA
+ nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE);
+ /* fixme: */
+ pb.empty = FALSE;
+#endif
+
+ nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1,
+ buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0),
+ buf->buf_rowstride,
+ FALSE, FALSE);
+
+#ifdef STRICT_RGBA
+ nr_arena_item_invoke_render (arena->root, &area, &pb, 0);
+ nr_blit_pixblock_pixblock (&cb, &pb);
+ nr_pixblock_release (&pb);
+#else
+ nr_arena_item_invoke_render (arena->root, &area, &cb, 0);
+#endif
+
+ nr_pixblock_release (&cb);
+ }
+ }
+}
+
+static double
+sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
+ NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK,
+ NR_ARENA_ITEM_STATE_NONE);
+
+ NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky);
+
+ arena->picked = picked;
+
+ if (picked) {
+ *actual_item = item;
+ return 0.0;
+ }
+
+ return 1e18;
+}
+
+static gint
+sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
+{
+ NRArenaItem *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");
+ nr_object_unref ((NRObject *) arena->active);
+ }
+ arena->cursor = TRUE;
+
+ /* TODO ... event -> arena transform? */
+ arena->c = NR::Point(event->crossing.x, event->crossing.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
+ arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
+ if (arena->active) nr_object_ref ((NRObject *) arena->active);
+ ret = sp_canvas_arena_send_event (arena, event);
+ }
+ break;
+ case GDK_LEAVE_NOTIFY:
+ if (arena->cursor) {
+ ret = sp_canvas_arena_send_event (arena, event);
+ if (arena->active) nr_object_unref ((NRObject *) arena->active);
+ arena->active = NULL;
+ arena->cursor = FALSE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ /* TODO ... event -> arena transform? */
+ arena->c = NR::Point(event->motion.x, event->motion.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
+ new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->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);
+ }
+ if (arena->active) nr_object_unref ((NRObject *) arena->active);
+ arena->active = new_arena;
+ if (arena->active) nr_object_ref ((NRObject *) arena->active);
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ 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 */
+ gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
+
+ return ret;
+}
+
+static void
+sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
+{
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
+}
+
+static void
+sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
+{
+ sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
+}
+
+void
+sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
+{
+ g_return_if_fail (ca != NULL);
+ 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 != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: repick? */
+ ca->sticky = sticky;
+}
+
+void
+sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
+{
+ NRRectL area;
+
+ g_return_if_fail (ca != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: */
+ pb->empty = FALSE;
+
+ area.x0 = pb->area.x0;
+ area.y0 = pb->area.y0;
+ area.x1 = pb->area.x1;
+ area.y1 = pb->area.y1;
+
+ nr_arena_item_invoke_render (ca->root, &area, pb, 0);
+}
+
diff --git a/src/display/canvas-arena.h b/src/display/canvas-arena.h
new file mode 100644
index 000000000..805bee60a
--- /dev/null
+++ b/src/display/canvas-arena.h
@@ -0,0 +1,58 @@
+#ifndef __SP_CANVAS_ARENA_H__
+#define __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, read the file 'COPYING' for more information
+ */
+
+struct SPCanvasArena;
+struct SPCanvasArenaClass;
+
+#define SP_TYPE_CANVAS_ARENA (sp_canvas_arena_get_type ())
+#define SP_CANVAS_ARENA(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_ARENA, SPCanvasArena))
+#define SP_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_ARENA, SPCanvasArenaClass))
+#define SP_IS_CANVAS_ARENA(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_ARENA))
+#define SP_IS_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_ARENA))
+
+#include "../display/sp-canvas.h"
+#include "nr-arena-item.h"
+
+struct SPCanvasArena {
+ SPCanvasItem item;
+
+ guint cursor : 1;
+ guint sticky : 1;
+ NR::Point c; // what is this?
+
+ NRArena *arena;
+ NRArenaItem *root;
+ NRGC gc;
+
+ NRArenaItem *active;
+ /* fixme: */
+ NRArenaItem *picked;
+ gdouble delta;
+};
+
+struct SPCanvasArenaClass {
+ SPCanvasItemClass parent_class;
+
+ gint (* arena_event) (SPCanvasArena *carena, NRArenaItem *item, GdkEvent *event);
+};
+
+GtkType sp_canvas_arena_get_type (void);
+
+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_pixblock (SPCanvasArena *ca, NRPixBlock *pb);
+
+#endif
diff --git a/src/display/canvas-bpath.cpp b/src/display/canvas-bpath.cpp
new file mode 100644
index 000000000..a6434ae3d
--- /dev/null
+++ b/src/display/canvas-bpath.cpp
@@ -0,0 +1,485 @@
+#define __SP_CANVAS_BPATH_C__
+
+/*
+ * Simple bezier bpath CanvasItem for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "sp-canvas-util.h"
+#include "canvas-bpath.h"
+#include "display/display-forward.h"
+#include "display/curve.h"
+#include <livarot/Shape.h>
+#include <livarot/Path.h>
+#include <livarot/int-line.h>
+#include <livarot/BitLigne.h>
+#include <libnr/nr-pixops.h>
+
+void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride);
+
+static void sp_canvas_bpath_class_init (SPCanvasBPathClass *klass);
+static void sp_canvas_bpath_init (SPCanvasBPath *path);
+static void sp_canvas_bpath_destroy (GtkObject *object);
+
+static void sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static double sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+
+static SPCanvasItemClass *parent_class;
+
+GtkType
+sp_canvas_bpath_get_type (void)
+{
+ static GtkType type = 0;
+ if (!type) {
+ GtkTypeInfo info = {
+ "SPCanvasBPath",
+ sizeof (SPCanvasBPath),
+ sizeof (SPCanvasBPathClass),
+ (GtkClassInitFunc) sp_canvas_bpath_class_init,
+ (GtkObjectInitFunc) sp_canvas_bpath_init,
+ NULL, NULL, NULL
+ };
+ type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
+ }
+ return type;
+}
+
+static void
+sp_canvas_bpath_class_init (SPCanvasBPathClass *klass)
+{
+ GtkObjectClass *object_class;
+ SPCanvasItemClass *item_class;
+
+ object_class = GTK_OBJECT_CLASS (klass);
+ item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
+
+ object_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 = 0x000000ff;
+ 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->fill_shp=NULL;
+ bpath->stroke_shp=NULL;
+}
+
+static void
+sp_canvas_bpath_destroy (GtkObject *object)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (object);
+ if (cbp->fill_shp) {
+ delete cbp->fill_shp;
+ cbp->fill_shp = NULL;
+ }
+
+ if (cbp->stroke_shp) {
+ delete cbp->stroke_shp;
+ cbp->stroke_shp = NULL;
+ }
+ if (cbp->curve) {
+ cbp->curve = sp_curve_unref (cbp->curve);
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);
+
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+
+ if (((SPCanvasItemClass *) parent_class)->update)
+ ((SPCanvasItemClass *) parent_class)->update (item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ if (cbp->fill_shp) {
+ delete cbp->fill_shp;
+ cbp->fill_shp = NULL;
+ }
+
+ if (cbp->stroke_shp) {
+ delete cbp->stroke_shp;
+ cbp->stroke_shp = NULL;
+ }
+ if (!cbp->curve) return;
+
+ NRRect dbox;
+
+ dbox.x0 = dbox.y0 = 0.0;
+ dbox.x1 = dbox.y1 = -1.0;
+
+ if ((cbp->fill_rgba & 0xff) || (cbp->stroke_rgba & 0xff)) {
+ Path* thePath=new Path;
+ thePath->LoadArtBPath(cbp->curve->bpath, affine, true);
+ thePath->Convert(0.25);
+ if ((cbp->fill_rgba & 0xff) && (cbp->curve->end > 2)) {
+ Shape* theShape=new Shape;
+ thePath->Fill(theShape,0);
+ if ( cbp->fill_shp == NULL ) cbp->fill_shp=new Shape;
+ if ( cbp->fill_rule == SP_WIND_RULE_EVENODD ) {
+ cbp->fill_shp->ConvertToShape(theShape,fill_oddEven);
+ } else {
+ cbp->fill_shp->ConvertToShape(theShape,fill_nonZero);
+ }
+ delete theShape;
+ cbp->fill_shp->CalcBBox();
+ if ( cbp->fill_shp->leftX < cbp->fill_shp->rightX ) {
+ if ( dbox.x0 >= dbox.x1 ) {
+ dbox.x0 = cbp->fill_shp->leftX; dbox.x1 = cbp->fill_shp->rightX;
+ dbox.y0 = cbp->fill_shp->topY; dbox.y1 = cbp->fill_shp->bottomY;
+ } else {
+ if ( cbp->fill_shp->leftX < dbox.x0 ) dbox.x0=cbp->fill_shp->leftX;
+ if ( cbp->fill_shp->rightX > dbox.x1 ) dbox.x1=cbp->fill_shp->rightX;
+ if ( cbp->fill_shp->topY < dbox.y0 ) dbox.y0=cbp->fill_shp->topY;
+ if ( cbp->fill_shp->bottomY > dbox.y1 ) dbox.y1=cbp->fill_shp->bottomY;
+ }
+ }
+ }
+ if ((cbp->stroke_rgba & 0xff) && (cbp->curve->end > 1)) {
+ JoinType join=join_straight;
+// Shape* theShape=new Shape;
+ ButtType butt=butt_straight;
+ if ( cbp->stroke_shp == NULL ) cbp->stroke_shp=new Shape;
+ if ( cbp->stroke_linecap == SP_STROKE_LINECAP_BUTT ) butt=butt_straight;
+ if ( cbp->stroke_linecap == SP_STROKE_LINECAP_ROUND ) butt=butt_round;
+ if ( cbp->stroke_linecap == SP_STROKE_LINECAP_SQUARE ) butt=butt_square;
+ if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_MITER ) join=join_pointy;
+ if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_ROUND ) join=join_round;
+ if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_BEVEL ) join=join_straight;
+ thePath->Stroke(cbp->stroke_shp,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit );
+ // thePath->Stroke(theShape,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit );
+ // cbp->stroke_shp->ConvertToShape(theShape,fill_nonZero);
+
+ cbp->stroke_shp->CalcBBox();
+ if ( cbp->stroke_shp->leftX < cbp->stroke_shp->rightX ) {
+ if ( dbox.x0 >= dbox.x1 ) {
+ dbox.x0 = cbp->stroke_shp->leftX;dbox.x1 = cbp->stroke_shp->rightX;
+ dbox.y0 = cbp->stroke_shp->topY;dbox.y1 = cbp->stroke_shp->bottomY;
+ } else {
+ if ( cbp->stroke_shp->leftX < dbox.x0 ) dbox.x0 = cbp->stroke_shp->leftX;
+ if ( cbp->stroke_shp->rightX > dbox.x1 ) dbox.x1 = cbp->stroke_shp->rightX;
+ if ( cbp->stroke_shp->topY < dbox.y0 ) dbox.y0 = cbp->stroke_shp->topY;
+ if ( cbp->stroke_shp->bottomY > dbox.y1 ) dbox.y1 = cbp->stroke_shp->bottomY;
+ }
+ }
+// delete theShape;
+ }
+ delete thePath;
+ }
+
+
+ item->x1 = (int)dbox.x0;
+ item->y1 = (int)dbox.y0;
+ item->x2 = (int)dbox.x1;
+ item->y2 = (int)dbox.y1;
+
+ sp_canvas_request_redraw (item->canvas, (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);
+
+ sp_canvas_prepare_buffer(buf);
+
+ NRRectL area;
+ area.x0=buf->rect.x0;
+ area.x1=buf->rect.x1;
+ area.y0=buf->rect.y0;
+ area.y1=buf->rect.y1;
+ if ( cbp->fill_shp ) {
+ nr_pixblock_render_bpath_rgba (cbp->fill_shp,cbp->fill_rgba,area,(char*)buf->buf, buf->buf_rowstride);
+ }
+ if ( cbp->stroke_shp ) {
+ nr_pixblock_render_bpath_rgba (cbp->stroke_shp,cbp->stroke_rgba,area,(char*)buf->buf, buf->buf_rowstride);
+ }
+
+}
+
+#define BIGVAL 1e18
+
+static double
+sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);
+
+ if (cbp->fill_shp && (cbp->fill_shp->PtWinding(p) > 0 )) {
+ *actual_item = item;
+ return 0.0;
+ }
+ if (cbp->stroke_shp ) {
+ if (cbp->stroke_shp->PtWinding(p) > 0 ) {
+ *actual_item = item;
+ return 0.0;
+ }
+ return distance(cbp->stroke_shp, p);
+ }
+ if (cbp->fill_shp) {
+ return distance(cbp->fill_shp, p);
+ }
+
+ return BIGVAL;
+}
+
+SPCanvasItem *
+sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve)
+{
+ g_return_val_if_fail (parent != NULL, NULL);
+ g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL);
+
+ SPCanvasItem *item = sp_canvas_item_new (parent, SP_TYPE_CANVAS_BPATH, NULL);
+
+ sp_canvas_bpath_set_bpath (SP_CANVAS_BPATH (item), curve);
+
+ return item;
+}
+
+void
+sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve)
+{
+ g_return_if_fail (cbp != NULL);
+ g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));
+
+ if (cbp->curve) {
+ cbp->curve = sp_curve_unref (cbp->curve);
+ }
+
+ if (curve) {
+ cbp->curve = sp_curve_ref (curve);
+ }
+
+ 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 != NULL);
+ 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)
+{
+ g_return_if_fail (cbp != NULL);
+ 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;
+
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
+}
+
+
+
+static void
+bpath_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven)
+{
+ union {
+ uint8_t comp[4];
+ uint32_t col;
+ } tempCol;
+ if ( st >= en ) return;
+ tempCol.col=*(uint32_t*)data;
+
+ unsigned int r, g, b, a;
+ r = NR_RGBA32_R (tempCol.col);
+ g = NR_RGBA32_G (tempCol.col);
+ b = NR_RGBA32_B (tempCol.col);
+ a = NR_RGBA32_A (tempCol.col);
+ if (a == 0) return;
+
+ vst*=a;
+ ven*=a;
+
+ if ( vst < 0 ) vst=0;
+ if ( vst > 255 ) vst=255;
+ if ( ven < 0 ) ven=0;
+ if ( ven > 255 ) ven=255;
+ float sv=vst;
+ float dv=ven-vst;
+ int len=en-st;
+ uint8_t* d=(uint8_t*)dest.buffer;
+
+ d+=3*(st-dest.startPix);
+ if ( fabs(dv) < 0.001 ) {
+ if ( sv > 249.999 ) {
+ /* Simple copy */
+ while (len > 0) {
+ d[0] = NR_COMPOSEN11 (r, 255, d[0]);
+ d[1] = NR_COMPOSEN11 (g, 255, d[1]);
+ d[2] = NR_COMPOSEN11 (b, 255, d[2]);
+ d += 3;
+ len -= 1;
+ }
+ } else {
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ while (len > 0) {
+ d[0] = NR_COMPOSEN11 (r, c0_24, d[0]);
+ d[1] = NR_COMPOSEN11 (g, c0_24, d[1]);
+ d[2] = NR_COMPOSEN11 (b, c0_24, d[2]);
+ d += 3;
+ len -= 1;
+ }
+ }
+ } else {
+ if ( en <= st+1 ) {
+ sv=0.5*(vst+ven);
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ d[0] = NR_COMPOSEN11 (r, c0_24, d[0]);
+ d[1] = NR_COMPOSEN11 (g, c0_24, d[1]);
+ d[2] = NR_COMPOSEN11 (b, c0_24, d[2]);
+ } else {
+ dv/=len;
+ sv+=0.5*dv; // correction trapezoidale
+ sv*=65536;
+ dv*=65536;
+ int c0_24 = static_cast<int>(CLAMP(sv, 0, 16777216));
+ int s0_24 = static_cast<int>(dv);
+ while (len > 0) {
+ unsigned int ca;
+ /* Draw */
+ ca = c0_24 >> 16;
+ if ( ca > 255 ) ca=255;
+ d[0] = NR_COMPOSEN11 (r, ca, d[0]);
+ d[1] = NR_COMPOSEN11 (g, ca, d[1]);
+ d[2] = NR_COMPOSEN11 (b, ca, d[2]);
+ d += 3;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+ }
+}
+
+void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride)
+{
+
+ theS->CalcBBox();
+ float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY;
+ int il,ir,it,ib;
+ il=(int)floor(l);
+ ir=(int)ceil(r);
+ it=(int)floor(t);
+ ib=(int)ceil(b);
+
+ if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return;
+ if ( il < area.x0 ) il=area.x0;
+ if ( it < area.y0 ) it=area.y0;
+ if ( ir > area.x1 ) ir=area.x1;
+ if ( ib > area.y1 ) ib=area.y1;
+
+/* // version par FloatLigne
+ int curPt;
+ float curY;
+ theS->BeginRaster(curY,curPt,1.0);
+
+ FloatLigne* theI=new FloatLigne();
+ IntLigne* theIL=new IntLigne();
+
+ theS->Scan(curY,curPt,(float)(it),1.0);
+
+ char* mdata=(char*)destBuf;
+ uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0))));
+ for (int y=it;y<ib;y++) {
+ theI->Reset();
+ if ( y&0x00000003 ) {
+ theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0);
+ } else {
+ theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0);
+ }
+ theI->Flatten();
+ theIL->Copy(theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,&color,bpath_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+stride));
+ }
+ theS->EndRaster();
+ delete theI;
+ delete theIL; */
+
+ // version par BitLigne directe
+ int curPt;
+ float curY;
+ theS->BeginQuickRaster(curY, curPt);
+
+ BitLigne* theI[4];
+ for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir);
+ IntLigne* theIL=new IntLigne();
+
+ theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25);
+
+ char* mdata=(char*)destBuf;
+ uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0))));
+ for (int y=it;y<ib;y++) {
+ for (int i = 0; i < 4; i++)
+ theI[i]->Reset();
+
+ for (int i = 0; i < 4; i++)
+ theS->QuickScan(curY, curPt, ((float)(y+0.25*(i+1))),
+ fill_oddEven, theI[i], 0.25);
+
+ theIL->Copy(4,theI);
+ // theI[0]->Affiche();
+ // theIL->Affiche();
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,&color,bpath_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+stride));
+ }
+ theS->EndQuickRaster();
+ for (int i=0;i<4;i++) delete theI[i];
+ delete theIL;
+}
diff --git a/src/display/canvas-bpath.h b/src/display/canvas-bpath.h
new file mode 100644
index 000000000..072fe1087
--- /dev/null
+++ b/src/display/canvas-bpath.h
@@ -0,0 +1,99 @@
+#ifndef __SP_CANVAS_BPATH_H__
+#define __SP_CANVAS_BPATH_H__
+
+/*
+ * Simple bezier bpath CanvasItem for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#include <glib/gtypes.h>
+
+#include <display/sp-canvas.h>
+
+struct SPCanvasBPath;
+struct SPCanvasBPathClass;
+struct SPCurve;
+
+#define SP_TYPE_CANVAS_BPATH (sp_canvas_bpath_get_type ())
+#define SP_CANVAS_BPATH(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_BPATH, SPCanvasBPath))
+#define SP_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_BPATH, SPCanvasBPathClass))
+#define SP_IS_CANVAS_BPATH(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_BPATH))
+#define SP_IS_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_BPATH))
+
+#define bpath_liv
+
+class Shape;
+
+/* stroke-linejoin */
+
+typedef enum {
+ SP_STROKE_LINEJOIN_MITER,
+ SP_STROKE_LINEJOIN_ROUND,
+ SP_STROKE_LINEJOIN_BEVEL
+} SPStrokeJoinType;
+
+/* stroke-linecap */
+
+typedef enum {
+ SP_STROKE_LINECAP_BUTT,
+ SP_STROKE_LINECAP_ROUND,
+ SP_STROKE_LINECAP_SQUARE
+} SPStrokeCapType;
+
+
+/* fill-rule */
+/* clip-rule */
+
+typedef enum {
+ SP_WIND_RULE_NONZERO,
+ SP_WIND_RULE_INTERSECT,
+ SP_WIND_RULE_EVENODD,
+ SP_WIND_RULE_POSITIVE
+} SPWindRule;
+
+
+struct SPCanvasBPath {
+ SPCanvasItem item;
+
+ /* Line def */
+ SPCurve *curve;
+
+ /* Fill attributes */
+ guint32 fill_rgba;
+ SPWindRule fill_rule;
+
+ /* Line attributes */
+ guint32 stroke_rgba;
+ gdouble stroke_width;
+ SPStrokeJoinType stroke_linejoin;
+ SPStrokeCapType stroke_linecap;
+ gdouble stroke_miterlimit;
+
+ /* State */
+ Shape *fill_shp;
+ Shape *stroke_shp;
+};
+
+struct SPCanvasBPathClass {
+ SPCanvasItemClass parent_class;
+};
+
+GtkType sp_canvas_bpath_get_type (void);
+
+SPCanvasItem *sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve);
+
+void sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve);
+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);
+
+
+
+#endif
+
diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp
new file mode 100644
index 000000000..6618c2358
--- /dev/null
+++ b/src/display/canvas-grid.cpp
@@ -0,0 +1,308 @@
+#define SP_CANVAS_GRID_C
+
+/*
+ * SPCGrid
+ *
+ * Copyright (C) Lauris Kaplinski 2000
+ *
+ */
+
+
+#include "sp-canvas-util.h"
+#include "canvas-grid.h"
+#include "display-forward.h"
+#include <libnr/nr-pixops.h>
+
+enum {
+ ARG_0,
+ ARG_ORIGINX,
+ ARG_ORIGINY,
+ ARG_SPACINGX,
+ ARG_SPACINGY,
+ ARG_COLOR,
+ ARG_EMPCOLOR,
+ ARG_EMPSPACING
+};
+
+
+static void sp_cgrid_class_init (SPCGridClass *klass);
+static void sp_cgrid_init (SPCGrid *grid);
+static void sp_cgrid_destroy (GtkObject *object);
+static void sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+
+static void sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_cgrid_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static SPCanvasItemClass * parent_class;
+
+GtkType
+sp_cgrid_get_type (void)
+{
+ static GtkType cgrid_type = 0;
+
+ if (!cgrid_type) {
+ GtkTypeInfo cgrid_info = {
+ "SPCGrid",
+ sizeof (SPCGrid),
+ sizeof (SPCGridClass),
+ (GtkClassInitFunc) sp_cgrid_class_init,
+ (GtkObjectInitFunc) sp_cgrid_init,
+ NULL, NULL,
+ (GtkClassInitFunc) NULL
+ };
+ cgrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cgrid_info);
+ }
+ return cgrid_type;
+}
+
+static void
+sp_cgrid_class_init (SPCGridClass *klass)
+{
+ GtkObjectClass *object_class;
+ SPCanvasItemClass *item_class;
+
+ object_class = (GtkObjectClass *) klass;
+ item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
+
+ gtk_object_add_arg_type ("SPCGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);
+ gtk_object_add_arg_type ("SPCGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);
+ gtk_object_add_arg_type ("SPCGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX);
+ gtk_object_add_arg_type ("SPCGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);
+ gtk_object_add_arg_type ("SPCGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);
+ gtk_object_add_arg_type ("SPCGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);
+ gtk_object_add_arg_type ("SPCGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);
+
+ object_class->destroy = sp_cgrid_destroy;
+ object_class->set_arg = sp_cgrid_set_arg;
+
+ item_class->update = sp_cgrid_update;
+ item_class->render = sp_cgrid_render;
+}
+
+static void
+sp_cgrid_init (SPCGrid *grid)
+{
+ grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;
+ grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0;
+ grid->color = 0x0000ff7f;
+ grid->empcolor = 0x3F3FFF40;
+ grid->empspacing = 5;
+}
+
+static void
+sp_cgrid_destroy (GtkObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (SP_IS_CGRID (object));
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ SPCanvasItem *item = SP_CANVAS_ITEM (object);
+ SPCGrid *grid = SP_CGRID (object);
+
+ switch (arg_id) {
+ case ARG_ORIGINX:
+ grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_ORIGINY:
+ grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_SPACINGX:
+ grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg);
+ if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_SPACINGY:
+ grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg);
+ if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_COLOR:
+ grid->color = GTK_VALUE_INT (* arg);
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_EMPCOLOR:
+ grid->empcolor = GTK_VALUE_INT (* arg);
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_EMPSPACING:
+ grid->empspacing = GTK_VALUE_INT (* arg);
+ // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;
+ sp_canvas_item_request_update (item);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sp_grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba)
+{
+ if ((y >= buf->rect.y0) && (y < buf->rect.y1)) {
+ guint r, g, b, a;
+ gint x0, x1, x;
+ guchar *p;
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+ x0 = MAX (buf->rect.x0, xs);
+ x1 = MIN (buf->rect.x1, xe + 1);
+ p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
+ for (x = x0; x < x1; x++) {
+ p[0] = NR_COMPOSEN11 (r, a, p[0]);
+ p[1] = NR_COMPOSEN11 (g, a, p[1]);
+ p[2] = NR_COMPOSEN11 (b, a, p[2]);
+ p += 3;
+ }
+ }
+}
+
+static void
+sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)
+{
+ if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {
+ guint r, g, b, a;
+ gint y0, y1, y;
+ guchar *p;
+ r = NR_RGBA32_R(rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+ y0 = MAX (buf->rect.y0, ys);
+ y1 = MIN (buf->rect.y1, ye + 1);
+ p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
+ for (y = y0; y < y1; y++) {
+ p[0] = NR_COMPOSEN11 (r, a, p[0]);
+ p[1] = NR_COMPOSEN11 (g, a, p[1]);
+ p[2] = NR_COMPOSEN11 (b, a, p[2]);
+ p += buf->buf_rowstride;
+ }
+ }
+}
+
+/**
+ \brief This function renders the grid on a particular canvas buffer
+ \param item The grid to render on the buffer
+ \param buf The buffer to render the grid on
+
+ This function gets called a touch more than you might believe,
+ about once per tile. This means that it could probably be optimized
+ and help things out.
+
+ Basically this function has to determine where in the canvas it is,
+ and how that associates with the grid. It does this first by looking
+ at the bounding box of the buffer, and then calculates where the grid
+ starts in that buffer. It will then step through grid lines until
+ it is outside of the buffer.
+
+ For each grid line it is drawn using the function \c sp_grid_hline
+ or \c sp_grid_vline. These are convience functions for the sake
+ of making the function easier to read.
+
+ Also, there are emphisized lines on the grid. While the \c syg and
+ \c sxg variable track grid positioning, the \c xlinestart and \c
+ ylinestart variables track the 'count' of what lines they are. If
+ that count is a multiple of the line seperation between emphisis
+ lines, then that line is drawn in the emphisis color.
+*/
+static void
+sp_cgrid_render (SPCanvasItem * item, SPCanvasBuf * buf)
+{
+ SPCGrid *grid = SP_CGRID (item);
+
+ sp_canvas_prepare_buffer (buf);
+
+ const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X];
+ const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]);
+ const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y];
+ const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]);
+
+ gint ylinenum;
+ gdouble y;
+ for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) {
+ const gint y0 = (gint) Inkscape::round(y);
+ const gint y1 = (gint) Inkscape::round(y + grid->sw[NR::Y]);
+
+ if (!grid->scaled[NR::Y] &&
+ (ylinenum % grid->empspacing) == 0) {
+ sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor);
+ } else {
+ sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color);
+ }
+
+ gint xlinenum;
+ gdouble x;
+ for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) {
+ const gint ix = (gint) Inkscape::round(x);
+ if (!grid->scaled[NR::X] &&
+ (xlinenum % grid->empspacing) == 0) {
+ sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->empcolor);
+ } else {
+ sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->color);
+ }
+ }
+ }
+}
+
+static void
+sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SPCGrid *grid = SP_CGRID (item);
+
+ if (parent_class->update)
+ (* parent_class->update) (item, affine, flags);
+
+ grid->ow = grid->origin * affine;
+ grid->sw = grid->spacing * affine;
+ grid->sw -= NR::Point(affine[4], affine[5]);
+
+ for(int dim = 0; dim < 2; dim++) {
+ gint scaling_factor = grid->empspacing;
+
+ if (scaling_factor <= 1)
+ scaling_factor = 5;
+
+ grid->scaled[dim] = FALSE;
+ grid->sw[dim] = fabs (grid->sw[dim]);
+ while (grid->sw[dim] < 8.0) {
+ grid->scaled[dim] = TRUE;
+ grid->sw[dim] *= scaling_factor;
+ /* First pass, go up to the major line spacing, then
+ keep increasing by two. */
+ scaling_factor = 2;
+ }
+ }
+
+ if (grid->empspacing == 0) {
+ grid->scaled[NR::Y] = TRUE;
+ grid->scaled[NR::X] = TRUE;
+ }
+
+ sp_canvas_request_redraw (item->canvas,
+ -1000000, -1000000,
+ 1000000, 1000000);
+
+ item->x1 = item->y1 = -1000000;
+ item->x2 = item->y2 = 1000000;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 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.h b/src/display/canvas-grid.h
new file mode 100644
index 000000000..0f3791773
--- /dev/null
+++ b/src/display/canvas-grid.h
@@ -0,0 +1,49 @@
+#ifndef SP_CANVAS_GRID_H
+#define SP_CANVAS_GRID_H
+
+/*
+ * SPCGrid
+ *
+ * Generic (and quite unintelligent) grid item for gnome canvas
+ *
+ * Copyright (C) Lauris Kaplinski 2000
+ *
+ */
+
+#include <display/sp-canvas.h>
+
+
+
+#define SP_TYPE_CGRID (sp_cgrid_get_type ())
+#define SP_CGRID(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CGRID, SPCGrid))
+#define SP_CGRID_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CGRID, SPCGridClass))
+#define SP_IS_CGRID(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CGRID))
+#define SP_IS_CGRID_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CGRID))
+
+
+/** \brief All the variables that are tracked for a grid specific
+ canvas item. */
+struct SPCGrid : public SPCanvasItem{
+ NR::Point origin; /**< Origin of the grid */
+ NR::Point spacing; /**< Spacing between elements of the grid */
+ guint32 color; /**< Color for normal lines */
+ guint32 empcolor; /**< Color for emphisis lines */
+ gint empspacing; /**< Spacing between emphisis lines */
+ bool scaled[2]; /**< Whether the grid is in scaled mode, which can
+ be different in the X or Y direction, hense two
+ variables */
+ NR::Point ow; /**< Transformed origin by the affine for the zoom */
+ NR::Point sw; /**< Transformed spacing by the affine for the zoom */
+};
+
+struct SPCGridClass {
+ SPCanvasItemClass parent_class;
+};
+
+
+/* Standard Gtk function */
+GtkType sp_cgrid_get_type (void);
+
+
+
+#endif
diff --git a/src/display/curve.cpp b/src/display/curve.cpp
new file mode 100644
index 000000000..a8a6e354e
--- /dev/null
+++ b/src/display/curve.cpp
@@ -0,0 +1,1289 @@
+#define __CURVE_C__
+
+/** \file
+ * Routines for SPCurve and for NArtBpath arrays in general.
+ */
+
+/*
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+
+#include <display/curve.h>
+#include <libnr/n-art-bpath.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-translate-ops.h>
+
+#define SP_CURVE_LENSTEP 32
+
+static bool sp_bpath_good(NArtBpath const bpath[]);
+static NArtBpath *sp_bpath_clean(NArtBpath const bpath[]);
+static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[]);
+static unsigned sp_bpath_length(NArtBpath const bpath[]);
+static bool sp_bpath_closed(NArtBpath const bpath[]);
+
+/* Constructors */
+
+/**
+ * The returned curve's state is as if sp_curve_reset has just been called on it.
+ */
+SPCurve *
+sp_curve_new()
+{
+ return sp_curve_new_sized(SP_CURVE_LENSTEP);
+}
+
+/**
+ * Like sp_curve_new, but overriding the default initial capacity.
+ *
+ * The returned curve's state is as if sp_curve_reset has just been called on it.
+ *
+ * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END
+ * element).
+ */
+SPCurve *
+sp_curve_new_sized(gint length)
+{
+ g_return_val_if_fail(length > 0, NULL);
+
+ SPCurve *curve = g_new(SPCurve, 1);
+
+ curve->refcount = 1;
+ curve->bpath = nr_new(NArtBpath, length);
+ curve->bpath->code = NR_END;
+ curve->end = 0;
+ curve->length = length;
+ curve->substart = 0;
+ curve->sbpath = false;
+ curve->hascpt = false;
+ curve->posSet = false;
+ curve->moving = false;
+ curve->closed = false;
+
+ return curve;
+}
+
+/**
+ * Convert NArtBpath object to SPCurve object.
+ *
+ * \return new SPCurve, or NULL if the curve was not created for some reason.
+ */
+SPCurve *
+sp_curve_new_from_bpath(NArtBpath *bpath)
+{
+ g_return_val_if_fail(bpath != NULL, NULL);
+
+ if (!sp_bpath_good(bpath)) {
+ NArtBpath *new_bpath = sp_bpath_clean(bpath);
+ if (new_bpath == NULL) {
+ return NULL;
+ }
+ nr_free(bpath);
+ bpath = new_bpath;
+ }
+
+ SPCurve *curve = g_new(SPCurve, 1);
+
+ curve->refcount = 1;
+ curve->bpath = bpath;
+ curve->length = sp_bpath_length(bpath);
+ curve->end = curve->length - 1;
+ gint i = curve->end;
+ for (; i > 0; i--)
+ if ((curve->bpath[i].code == NR_MOVETO) ||
+ (curve->bpath[i].code == NR_MOVETO_OPEN))
+ break;
+ curve->substart = i;
+ curve->sbpath = false;
+ curve->hascpt = false;
+ curve->posSet = false;
+ curve->moving = false;
+ curve->closed = sp_bpath_closed(bpath);
+
+ return curve;
+}
+
+/**
+ * Construct an SPCurve from read-only, static storage.
+ *
+ * We could treat read-onliness and staticness (i.e. can't call free on bpath) as orthogonal
+ * attributes, but at the time of writing we have only one caller.
+ */
+SPCurve *
+sp_curve_new_from_static_bpath(NArtBpath const *bpath)
+{
+ g_return_val_if_fail(bpath != NULL, NULL);
+
+ bool sbpath;
+ if (!sp_bpath_good(bpath)) {
+ NArtBpath *new_bpath = sp_bpath_clean(bpath);
+ g_return_val_if_fail(new_bpath != NULL, NULL);
+ sbpath = false;
+ bpath = new_bpath;
+ } else {
+ sbpath = true;
+ }
+
+ SPCurve *curve = g_new(SPCurve, 1);
+
+ curve->refcount = 1;
+ curve->bpath = const_cast<NArtBpath *>(bpath);
+ curve->length = sp_bpath_length(bpath);
+ curve->end = curve->length - 1;
+ gint i = curve->end;
+ for (; i > 0; i--)
+ if ((curve->bpath[i].code == NR_MOVETO) ||
+ (curve->bpath[i].code == NR_MOVETO_OPEN))
+ break;
+ curve->substart = i;
+ curve->sbpath = sbpath;
+ curve->hascpt = false;
+ curve->posSet = false;
+ curve->moving = false;
+ curve->closed = sp_bpath_closed(bpath);
+
+ return curve;
+}
+
+/**
+ * Convert const NArtBpath array to SPCurve.
+ *
+ * \return new SPCurve, or NULL if the curve was not created for some reason.
+ */
+SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[])
+{
+ g_return_val_if_fail(bpath != NULL, NULL);
+
+ NArtBpath *new_bpath;
+ if (!sp_bpath_good(bpath)) {
+ new_bpath = sp_bpath_clean(bpath);
+ g_return_val_if_fail(new_bpath != NULL, NULL);
+ } else {
+ unsigned const len = sp_bpath_length(bpath);
+ new_bpath = nr_new(NArtBpath, len);
+ memcpy(new_bpath, bpath, len * sizeof(NArtBpath));
+ }
+
+ SPCurve *curve = sp_curve_new_from_bpath(new_bpath);
+
+ if (!curve)
+ nr_free(new_bpath);
+
+ return curve;
+}
+
+/**
+ * Increase refcount of curve.
+ *
+ * \todo should this be shared with other refcounting code?
+ */
+SPCurve *
+sp_curve_ref(SPCurve *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ curve->refcount += 1;
+
+ return curve;
+}
+
+/**
+ * Decrease refcount of curve, with possible destruction.
+ *
+ * \todo should this be shared with other refcounting code?
+ */
+SPCurve *
+sp_curve_unref(SPCurve *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ curve->refcount -= 1;
+
+ if (curve->refcount < 1) {
+ if ((!curve->sbpath) && (curve->bpath)) {
+ nr_free(curve->bpath);
+ }
+ g_free(curve);
+ }
+
+ return NULL;
+}
+
+/**
+ * Add space for more paths in curve.
+ */
+static void
+sp_curve_ensure_space(SPCurve *curve, gint space)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(space > 0);
+
+ if (curve->end + space < curve->length)
+ return;
+
+ if (space < SP_CURVE_LENSTEP)
+ space = SP_CURVE_LENSTEP;
+
+ curve->bpath = nr_renew(curve->bpath, NArtBpath, curve->length + space);
+
+ curve->length += space;
+}
+
+/**
+ * Create new curve from its own bpath array.
+ */
+SPCurve *
+sp_curve_copy(SPCurve *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ return sp_curve_new_from_foreign_bpath(curve->bpath);
+}
+
+/**
+ * Return new curve that is the concatenation of all curves in list.
+ */
+SPCurve *
+sp_curve_concat(GSList const *list)
+{
+ g_return_val_if_fail(list != NULL, NULL);
+
+ gint length = 0;
+
+ for (GSList const *l = list; l != NULL; l = l->next) {
+ SPCurve *c = (SPCurve *) l->data;
+ length += c->end;
+ }
+
+ SPCurve *new_curve = sp_curve_new_sized(length + 1);
+
+ NArtBpath *bp = new_curve->bpath;
+
+ for (GSList const *l = list; l != NULL; l = l->next) {
+ SPCurve *c = (SPCurve *) l->data;
+ memcpy(bp, c->bpath, c->end * sizeof(NArtBpath));
+ bp += c->end;
+ }
+
+ bp->code = NR_END;
+
+ new_curve->end = length;
+ gint i;
+ for (i = new_curve->end; i > 0; i--) {
+ if ((new_curve->bpath[i].code == NR_MOVETO) ||
+ (new_curve->bpath[i].code == NR_MOVETO_OPEN) )
+ break;
+ }
+
+ new_curve->substart = i;
+
+ return new_curve;
+}
+
+/**
+ * Returns a list of new curves corresponding to the subpaths in \a curve.
+ */
+GSList *
+sp_curve_split(SPCurve const *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ gint p = 0;
+ GSList *l = NULL;
+
+ while (p < curve->end) {
+ gint i = 1;
+ while ((curve->bpath[p + i].code == NR_LINETO) ||
+ (curve->bpath[p + i].code == NR_CURVETO))
+ i++;
+ SPCurve *new_curve = sp_curve_new_sized(i + 1);
+ memcpy(new_curve->bpath, curve->bpath + p, i * sizeof(NArtBpath));
+ new_curve->end = i;
+ new_curve->bpath[i].code = NR_END;
+ new_curve->substart = 0;
+ new_curve->closed = (new_curve->bpath->code == NR_MOVETO);
+ new_curve->hascpt = (new_curve->bpath->code == NR_MOVETO_OPEN);
+ l = g_slist_append(l, new_curve);
+ /** \todo
+ * effic: Use g_slist_prepend instead. Either work backwards from
+ * the end of curve, or work forwards as at present but do
+ * g_slist_reverse before returning.
+ */
+ p += i;
+ }
+
+ return l;
+}
+
+/**
+ * Transform all paths in curve, template helper.
+ */
+template<class M>
+static void
+tmpl_curve_transform(SPCurve *const curve, M const &m)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+
+ for (gint i = 0; i < curve->end; i++) {
+ NArtBpath *p = curve->bpath + i;
+ switch (p->code) {
+ case NR_MOVETO:
+ case NR_MOVETO_OPEN:
+ case NR_LINETO: {
+ p->setC(3, p->c(3) * m);
+ break;
+ }
+ case NR_CURVETO:
+ for (unsigned i = 1; i <= 3; ++i) {
+ p->setC(i, p->c(i) * m);
+ }
+ break;
+ default:
+ g_warning("Illegal pathcode %d", p->code);
+ break;
+ }
+ }
+}
+
+/**
+ * Transform all paths in curve using matrix.
+ */
+void
+sp_curve_transform(SPCurve *const curve, NR::Matrix const &m)
+{
+ tmpl_curve_transform<NR::Matrix>(curve, m);
+}
+
+/**
+ * Transform all paths in curve using NR::translate.
+ */
+void
+sp_curve_transform(SPCurve *const curve, NR::translate const &m)
+{
+ tmpl_curve_transform<NR::translate>(curve, m);
+}
+
+
+/* Methods */
+
+/**
+ * Set curve to empty curve.
+ */
+void
+sp_curve_reset(SPCurve *curve)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+
+ curve->bpath->code = NR_END;
+ curve->end = 0;
+ curve->substart = 0;
+ curve->hascpt = false;
+ curve->posSet = false;
+ curve->moving = false;
+ curve->closed = false;
+}
+
+/* Several consecutive movetos are ALLOWED */
+
+/**
+ * Calls sp_curve_moveto() with point made of given coordinates.
+ */
+void
+sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y)
+{
+ sp_curve_moveto(curve, NR::Point(x, y));
+}
+
+/**
+ * Perform a moveto to a point, thus starting a new subpath.
+ */
+void
+sp_curve_moveto(SPCurve *curve, NR::Point const &p)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(!curve->moving);
+
+ curve->substart = curve->end;
+ curve->hascpt = true;
+ curve->posSet = true;
+ curve->movePos = p;
+}
+
+/**
+ * Calls sp_curve_lineto() with a point's coordinates.
+ */
+void
+sp_curve_lineto(SPCurve *curve, NR::Point const &p)
+{
+ sp_curve_lineto(curve, p[NR::X], p[NR::Y]);
+}
+
+/**
+ * Adds a line to the current subpath.
+ */
+void
+sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(curve->hascpt);
+
+ if (curve->moving) {
+ /* fix endpoint */
+ g_return_if_fail(!curve->posSet);
+ g_return_if_fail(curve->end > 1);
+ NArtBpath *bp = curve->bpath + curve->end - 1;
+ g_return_if_fail(bp->code == NR_LINETO);
+ bp->x3 = x;
+ bp->y3 = y;
+ curve->moving = false;
+ return;
+ }
+
+ if (curve->posSet) {
+ /* start a new segment */
+ sp_curve_ensure_space(curve, 2);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_MOVETO_OPEN;
+ bp->setC(3, curve->movePos);
+ bp++;
+ bp->code = NR_LINETO;
+ bp->x3 = x;
+ bp->y3 = y;
+ bp++;
+ bp->code = NR_END;
+ curve->end += 2;
+ curve->posSet = false;
+ curve->closed = false;
+ return;
+ }
+
+ /* add line */
+
+ g_return_if_fail(curve->end > 1);
+ sp_curve_ensure_space(curve, 1);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_LINETO;
+ bp->x3 = x;
+ bp->y3 = y;
+ bp++;
+ bp->code = NR_END;
+ curve->end++;
+}
+
+/// Unused
+void
+sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(curve->hascpt);
+
+ if (curve->moving) {
+ /* change endpoint */
+ g_return_if_fail(!curve->posSet);
+ g_return_if_fail(curve->end > 1);
+ NArtBpath *bp = curve->bpath + curve->end - 1;
+ g_return_if_fail(bp->code == NR_LINETO);
+ bp->x3 = x;
+ bp->y3 = y;
+ return;
+ }
+
+ if (curve->posSet) {
+ /* start a new segment */
+ sp_curve_ensure_space(curve, 2);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_MOVETO_OPEN;
+ bp->setC(3, curve->movePos);
+ bp++;
+ bp->code = NR_LINETO;
+ bp->x3 = x;
+ bp->y3 = y;
+ bp++;
+ bp->code = NR_END;
+ curve->end += 2;
+ curve->posSet = false;
+ curve->moving = true;
+ curve->closed = false;
+ return;
+ }
+
+ /* add line */
+
+ g_return_if_fail(curve->end > 1);
+ sp_curve_ensure_space(curve, 1);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_LINETO;
+ bp->x3 = x;
+ bp->y3 = y;
+ bp++;
+ bp->code = NR_END;
+ curve->end++;
+ curve->moving = true;
+}
+
+/**
+ * Calls sp_curve_curveto() with coordinates of three points.
+ */
+void
+sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2)
+{
+ using NR::X;
+ using NR::Y;
+ sp_curve_curveto(curve,
+ p0[X], p0[Y],
+ p1[X], p1[Y],
+ p2[X], p2[Y]);
+}
+
+/**
+ * Adds a bezier segment to the current subpath.
+ */
+void
+sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(curve->hascpt);
+ g_return_if_fail(!curve->moving);
+
+ if (curve->posSet) {
+ /* start a new segment */
+ sp_curve_ensure_space(curve, 2);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_MOVETO_OPEN;
+ bp->setC(3, curve->movePos);
+ bp++;
+ bp->code = NR_CURVETO;
+ bp->x1 = x0;
+ bp->y1 = y0;
+ bp->x2 = x1;
+ bp->y2 = y1;
+ bp->x3 = x2;
+ bp->y3 = y2;
+ bp++;
+ bp->code = NR_END;
+ curve->end += 2;
+ curve->posSet = false;
+ curve->closed = false;
+ return;
+ }
+
+ /* add curve */
+
+ g_return_if_fail(curve->end > 1);
+ sp_curve_ensure_space(curve, 1);
+ NArtBpath *bp = curve->bpath + curve->end;
+ bp->code = NR_CURVETO;
+ bp->x1 = x0;
+ bp->y1 = y0;
+ bp->x2 = x1;
+ bp->y2 = y1;
+ bp->x3 = x2;
+ bp->y3 = y2;
+ bp++;
+ bp->code = NR_END;
+ curve->end++;
+}
+
+/**
+ * Close current subpath by possibly adding a line between start and end.
+ */
+void
+sp_curve_closepath(SPCurve *curve)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(curve->hascpt);
+ g_return_if_fail(!curve->posSet);
+ g_return_if_fail(!curve->moving);
+ g_return_if_fail(!curve->closed);
+ /* We need at least moveto, curveto, end. */
+ g_return_if_fail(curve->end - curve->substart > 1);
+
+ {
+ NArtBpath *bs = curve->bpath + curve->substart;
+ NArtBpath *be = curve->bpath + curve->end - 1;
+
+ if (bs->c(3) != be->c(3)) {
+ sp_curve_lineto(curve, bs->c(3));
+ bs = curve->bpath + curve->substart;
+ }
+
+ bs->code = NR_MOVETO;
+ }
+ curve->closed = true;
+
+ for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) {
+ /** \todo
+ * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
+ * the closed boolean).
+ */
+ if (bp->code == NR_MOVETO_OPEN) {
+ curve->closed = false;
+ break;
+ }
+ }
+
+ curve->hascpt = false;
+}
+
+/** Like sp_curve_closepath() but sets the end point of the current
+ command 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
+sp_curve_closepath_current(SPCurve *curve)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(!curve->sbpath);
+ g_return_if_fail(curve->hascpt);
+ g_return_if_fail(!curve->posSet);
+ g_return_if_fail(!curve->closed);
+ /* We need at least moveto, curveto, end. */
+ g_return_if_fail(curve->end - curve->substart > 1);
+
+ {
+ NArtBpath *bs = curve->bpath + curve->substart;
+ NArtBpath *be = curve->bpath + curve->end - 1;
+
+ be->x3 = bs->x3;
+ be->y3 = bs->y3;
+
+ bs->code = NR_MOVETO;
+ }
+ curve->closed = true;
+
+ for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) {
+ /** \todo
+ * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
+ * the closed boolean).
+ */
+ if (bp->code == NR_MOVETO_OPEN) {
+ curve->closed = false;
+ break;
+ }
+ }
+
+ curve->hascpt = false;
+ curve->moving = false;
+}
+
+/**
+ * True if no paths are in curve.
+ */
+bool
+sp_curve_empty(SPCurve *curve)
+{
+ g_return_val_if_fail(curve != NULL, TRUE);
+
+ return (curve->bpath->code == NR_END);
+}
+
+/**
+ * Return last subpath or NULL.
+ */
+NArtBpath *
+sp_curve_last_bpath(SPCurve const *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ if (curve->end == 0) {
+ return NULL;
+ }
+
+ return curve->bpath + curve->end - 1;
+}
+
+/**
+ * Return first subpath or NULL.
+ */
+NArtBpath *
+sp_curve_first_bpath(SPCurve const *curve)
+{
+ g_return_val_if_fail(curve != NULL, NULL);
+
+ if (curve->end == 0) {
+ return NULL;
+ }
+
+ return curve->bpath;
+}
+
+/**
+ * Return first point of first subpath or (0,0).
+ */
+NR::Point
+sp_curve_first_point(SPCurve const *const curve)
+{
+ NArtBpath *const bpath = sp_curve_first_bpath(curve);
+ g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
+ return bpath->c(3);
+}
+
+/**
+ * Return the second point of first subpath or curve->movePos if curve too short.
+ */
+NR::Point
+sp_curve_second_point(SPCurve const *const curve)
+{
+ g_return_val_if_fail(curve != NULL, NR::Point(0, 0));
+
+ if (curve->end < 1) {
+ return curve->movePos;
+ }
+
+ NArtBpath *bpath = NULL;
+ if (curve->end < 2) {
+ bpath = curve->bpath;
+ } else {
+ bpath = curve->bpath + 1;
+ }
+ g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
+ return bpath->c(3);
+}
+
+/**
+ * Return the second-last point of last subpath or curve->movePos if curve too short.
+ */
+NR::Point
+sp_curve_penultimate_point(SPCurve const *const curve)
+{
+ g_return_val_if_fail(curve != NULL, NR::Point(0, 0));
+
+ if (curve->end < 2) {
+ return curve->movePos;
+ }
+
+ NArtBpath *const bpath = curve->bpath + curve->end - 2;
+ g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
+ return bpath->c(3);
+}
+
+/**
+ * Return last point of last subpath or (0,0).
+ */
+NR::Point
+sp_curve_last_point(SPCurve const *const curve)
+{
+ NArtBpath *const bpath = sp_curve_last_bpath(curve);
+ g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
+ return bpath->c(3);
+}
+
+inline static bool
+is_moveto(NRPathcode const c)
+{
+ return c == NR_MOVETO || c == NR_MOVETO_OPEN;
+}
+
+/**
+ * Returns \a curve but drawn in the opposite direction.
+ * Should result in the same shape, but
+ * with all its markers drawn facing the other direction.
+ **/
+SPCurve *
+sp_curve_reverse(SPCurve const *curve)
+{
+ /* We need at least moveto, curveto, end. */
+ g_return_val_if_fail(curve->end - curve->substart > 1, NULL);
+
+ NArtBpath const *be = curve->bpath + curve->end - 1;
+
+ g_assert(is_moveto(curve->bpath[curve->substart].code));
+ g_assert(is_moveto(curve->bpath[0].code));
+ g_assert((be+1)->code == NR_END);
+
+ SPCurve *new_curve = sp_curve_new_sized(curve->length);
+ sp_curve_moveto(new_curve, be->c(3));
+
+ for (NArtBpath const *bp = be; ; --bp) {
+ switch (bp->code) {
+ case NR_MOVETO:
+ g_assert(new_curve->bpath[new_curve->substart].code == NR_MOVETO_OPEN);
+ new_curve->bpath[new_curve->substart].code = NR_MOVETO;
+ /* FALL-THROUGH */
+ case NR_MOVETO_OPEN:
+ if (bp == curve->bpath) {
+ return new_curve;
+ }
+ sp_curve_moveto(new_curve, (bp-1)->c(3));
+ break;
+
+ case NR_LINETO:
+ sp_curve_lineto(new_curve, (bp-1)->c(3));
+ break;
+
+ case NR_CURVETO:
+ sp_curve_curveto(new_curve, bp->c(2), bp->c(1), (bp-1)->c(3));
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+ }
+}
+
+/**
+ * Append \a curve2 to \a curve.
+ */
+void
+sp_curve_append(SPCurve *curve,
+ SPCurve const *curve2,
+ bool use_lineto)
+{
+ g_return_if_fail(curve != NULL);
+ g_return_if_fail(curve2 != NULL);
+
+ if (curve2->end < 1)
+ return;
+
+ NArtBpath const *bs = curve2->bpath;
+
+ bool closed = curve->closed;
+
+ for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) {
+ switch (bp->code) {
+ case NR_MOVETO_OPEN:
+ if (use_lineto && curve->hascpt) {
+ sp_curve_lineto(curve, bp->x3, bp->y3);
+ use_lineto = FALSE;
+ } else {
+ if (closed) sp_curve_closepath(curve);
+ sp_curve_moveto(curve, bp->x3, bp->y3);
+ }
+ closed = false;
+ break;
+
+ case NR_MOVETO:
+ if (use_lineto && curve->hascpt) {
+ sp_curve_lineto(curve, bp->x3, bp->y3);
+ use_lineto = FALSE;
+ } else {
+ if (closed) sp_curve_closepath(curve);
+ sp_curve_moveto(curve, bp->x3, bp->y3);
+ }
+ closed = true;
+ break;
+
+ case NR_LINETO:
+ sp_curve_lineto(curve, bp->x3, bp->y3);
+ break;
+
+ case NR_CURVETO:
+ sp_curve_curveto(curve, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3);
+ break;
+
+ case NR_END:
+ g_assert_not_reached();
+ }
+ }
+
+ if (closed) {
+ sp_curve_closepath(curve);
+ }
+}
+
+/**
+ * Append \a c1 to \a c0 with possible fusing of close endpoints.
+ */
+SPCurve *
+sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance)
+{
+ g_return_val_if_fail(c0 != NULL, NULL);
+ g_return_val_if_fail(c1 != NULL, NULL);
+ g_return_val_if_fail(!c0->closed, NULL);
+ g_return_val_if_fail(!c1->closed, NULL);
+
+ if (c1->end < 1) {
+ return c0;
+ }
+
+ NArtBpath *be = sp_curve_last_bpath(c0);
+ if (be) {
+ NArtBpath const *bs = sp_curve_first_bpath(c1);
+ if ( bs
+ && ( fabs( bs->x3 - be->x3 ) <= tolerance )
+ && ( fabs( bs->y3 - be->y3 ) <= tolerance ) )
+ {
+ /** \todo
+ * fixme: Strictly we mess in case of multisegment mixed
+ * open/close curves
+ */
+ bool closed = false;
+ for (bs = bs + 1; bs->code != NR_END; bs++) {
+ switch (bs->code) {
+ case NR_MOVETO_OPEN:
+ if (closed) sp_curve_closepath(c0);
+ sp_curve_moveto(c0, bs->x3, bs->y3);
+ closed = false;
+ break;
+ case NR_MOVETO:
+ if (closed) sp_curve_closepath(c0);
+ sp_curve_moveto(c0, bs->x3, bs->y3);
+ closed = true;
+ break;
+ case NR_LINETO:
+ sp_curve_lineto(c0, bs->x3, bs->y3);
+ break;
+ case NR_CURVETO:
+ sp_curve_curveto(c0, bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3);
+ break;
+ case NR_END:
+ g_assert_not_reached();
+ }
+ }
+ } else {
+ sp_curve_append(c0, c1, TRUE);
+ }
+ } else {
+ sp_curve_append(c0, c1, TRUE);
+ }
+
+ return c0;
+}
+
+/**
+ * Remove last segment of curve.
+ */
+void
+sp_curve_backspace(SPCurve *curve)
+{
+ g_return_if_fail(curve != NULL);
+
+ if (curve->end > 0) {
+ curve->end -= 1;
+ if (curve->end > 0) {
+ NArtBpath *bp = curve->bpath + curve->end - 1;
+ if ((bp->code == NR_MOVETO) ||
+ (bp->code == NR_MOVETO_OPEN) )
+ {
+ curve->hascpt = true;
+ curve->posSet = true;
+ curve->closed = false;
+ curve->movePos = bp->c(3);
+ curve->end -= 1;
+ }
+ }
+ curve->bpath[curve->end].code = NR_END;
+ }
+}
+
+/* Private methods */
+
+/**
+ * True if all subpaths in bpath array pass consistency check.
+ */
+static bool sp_bpath_good(NArtBpath const bpath[])
+{
+ g_return_val_if_fail(bpath != NULL, FALSE);
+
+ NArtBpath const *bp = bpath;
+ while (bp->code != NR_END) {
+ bp = sp_bpath_check_subpath(bp);
+ if (bp == NULL)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Return copy of a bpath array, discarding any inconsistencies.
+ */
+static NArtBpath *sp_bpath_clean(NArtBpath const bpath[])
+{
+ NArtBpath *new_bpath = nr_new(NArtBpath, sp_bpath_length(bpath));
+
+ NArtBpath const *bp = bpath;
+ NArtBpath *np = new_bpath;
+
+ while (bp->code != NR_END) {
+ if (sp_bpath_check_subpath(bp)) {
+ *np++ = *bp++;
+ while ((bp->code == NR_LINETO) ||
+ (bp->code == NR_CURVETO))
+ *np++ = *bp++;
+ } else {
+ bp++;
+ while ((bp->code == NR_LINETO) ||
+ (bp->code == NR_CURVETO))
+ bp++;
+ }
+ }
+
+ if (np == new_bpath) {
+ nr_free(new_bpath);
+ return NULL;
+ }
+
+ np->code = NR_END;
+ np += 1;
+
+ new_bpath = nr_renew(new_bpath, NArtBpath, np - new_bpath);
+
+ return new_bpath;
+}
+
+/**
+ * Perform consistency check of bpath array.
+ * \return Address of NR_END node or NULL.
+ */
+static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[])
+{
+ g_return_val_if_fail(bpath != NULL, NULL);
+
+ bool closed;
+ if (bpath->code == NR_MOVETO) {
+ closed = true;
+ } else if (bpath->code == NR_MOVETO_OPEN) {
+ closed = false;
+ } else {
+ return NULL;
+ }
+
+ gint len = 0;
+ gint i;
+ /** \todo
+ * effic: consider checking for END/MOVE/MOVETO inside switch block
+ */
+ for (i = 1; (bpath[i].code != NR_END) && (bpath[i].code != NR_MOVETO) && (bpath[i].code != NR_MOVETO_OPEN); i++) {
+ switch (bpath[i].code) {
+ case NR_LINETO:
+ case NR_CURVETO:
+ len++;
+ break;
+ default:
+ return NULL;
+ }
+ }
+
+ if (closed) {
+ if (len < 1)
+ return NULL;
+
+ if ((bpath->x3 != bpath[i-1].x3) || (bpath->y3 != bpath[i-1].y3))
+ return NULL;
+ } else {
+ if (len < 1)
+ return NULL;
+ }
+
+ return bpath + i;
+}
+
+/**
+ * Returns index of first NR_END bpath in array.
+ */
+static unsigned sp_bpath_length(NArtBpath const bpath[])
+{
+ g_return_val_if_fail(bpath != NULL, FALSE);
+
+ unsigned ret = 0;
+ while ( bpath[ret].code != NR_END ) {
+ ++ret;
+ }
+ ++ret;
+
+ return ret;
+}
+
+/**
+ * \brief
+ *
+ * \todo
+ * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate
+ * a closing of the subpath it's nonsense to talk about a path as a whole
+ * being closed, although maybe someone would want that for some other reason?
+ * Oh, also, if the bpath just ends, then it's *open*. I hope nobody is using
+ * this code for anything.
+ */
+static bool sp_bpath_closed(NArtBpath const bpath[])
+{
+ g_return_val_if_fail(bpath != NULL, FALSE);
+
+ for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) {
+ if (bp->code == NR_MOVETO_OPEN) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Returns length of bezier segment.
+ */
+static double
+bezier_len(NR::Point const &c0,
+ NR::Point const &c1,
+ NR::Point const &c2,
+ NR::Point const &c3,
+ double const threshold)
+{
+ /** \todo
+ * The SVG spec claims that a closed form exists, but for the moment I'll
+ * use a stupid algorithm.
+ */
+ double const lbound = L2( c3 - c0 );
+ double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 );
+ double ret;
+ if ( ubound - lbound <= threshold ) {
+ ret = .5 * ( lbound + ubound );
+ } else {
+ NR::Point const a1( .5 * ( c0 + c1 ) );
+ NR::Point const b2( .5 * ( c2 + c3 ) );
+ NR::Point const c12( .5 * ( c1 + c2 ) );
+ NR::Point const a2( .5 * ( a1 + c12 ) );
+ NR::Point const b1( .5 * ( c12 + b2 ) );
+ NR::Point const midpoint( .5 * ( a2 + b1 ) );
+ double const rec_threshold = .625 * threshold;
+ ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold);
+ if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) {
+ using NR::X; using NR::Y;
+ g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}",
+ ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]);
+ }
+ }
+ return ret;
+}
+
+/**
+ * Returns total length of curve, excluding length of closepath segments.
+ */
+static double
+sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[])
+{
+ g_return_val_if_fail(curve != NULL, 0.);
+
+ double ret = 0.0;
+
+ if ( curve->bpath->code == NR_END ) {
+ return ret;
+ }
+
+ NR::Point prev(curve->bpath->c(3));
+ for (gint i = 1; i < curve->end; ++i) {
+ NArtBpath &p = curve->bpath[i];
+ double seg_len = 0;
+ switch (p.code) {
+ case NR_MOVETO_OPEN:
+ case NR_MOVETO:
+ case NR_LINETO:
+ seg_len = L2(p.c(3) - prev);
+ break;
+
+ case NR_CURVETO:
+ seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.);
+ break;
+
+ case NR_END:
+ return ret;
+ }
+ seg2len[i - 1] = seg_len;
+ ret += seg_len;
+ prev = p.c(3);
+ }
+ g_assert(!(ret < 0));
+ return ret;
+}
+
+/**
+ * Like sp_curve_distance_including_space(), but ensures that the
+ * result >= 1e-18: uses 1 per segment if necessary.
+ */
+static double
+sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[])
+{
+ double const real_dist(sp_curve_distance_including_space(curve, seg2len));
+ if (real_dist >= 1e-18) {
+ return real_dist;
+ } else {
+ unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
+ for (unsigned i = 0; i < nSegs; ++i) {
+ seg2len[i] = 1.;
+ }
+ return (double) nSegs;
+ }
+}
+
+void
+sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &new_p0, NR::Point const &new_p1)
+{
+ if (sp_curve_empty(curve)) {
+ return;
+ }
+ g_assert(unsigned(SP_CURVE_LENGTH(curve)) + 1 == sp_bpath_length(curve->bpath));
+ unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
+ g_assert(nSegs != 0);
+ double *const seg2len = new double[nSegs];
+ double const tot_len = sp_curve_nonzero_distance_including_space(curve, seg2len);
+ NR::Point const offset0( new_p0 - sp_curve_first_point(curve) );
+ NR::Point const offset1( new_p1 - sp_curve_last_point(curve) );
+ curve->bpath->setC(3, new_p0);
+ double begin_dist = 0.;
+ for (unsigned si = 0; si < nSegs; ++si) {
+ double const end_dist = begin_dist + seg2len[si];
+ NArtBpath &p = curve->bpath[1 + si];
+ switch (p.code) {
+ case NR_LINETO:
+ case NR_MOVETO:
+ case NR_MOVETO_OPEN:
+ p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1));
+ break;
+
+ case NR_CURVETO:
+ for (unsigned ci = 1; ci <= 3; ++ci) {
+ p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1));
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ begin_dist = end_dist;
+ }
+ g_assert(L1(curve->bpath[nSegs].c(3) - new_p1) < 1.);
+ /* Explicit set for better numerical properties. */
+ curve->bpath[nSegs].setC(3, new_p1);
+ delete [] seg2len;
+}
+
+void
+sp_curve_move_endpoints(SPCurve *curve, NR::Point const &new_p0,
+ NR::Point const &new_p1)
+{
+ if (sp_curve_empty(curve)) {
+ return;
+ }
+ unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
+ g_assert(nSegs != 0);
+
+ curve->bpath->setC(3, new_p0);
+ curve->bpath[nSegs].setC(3, new_p1);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 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.h b/src/display/curve.h
new file mode 100644
index 000000000..d63796140
--- /dev/null
+++ b/src/display/curve.h
@@ -0,0 +1,133 @@
+#ifndef SEEN_DISPLAY_CURVE_H
+#define SEEN_DISPLAY_CURVE_H
+
+/** \file
+ * Wrapper around an array of NArtBpath objects.
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+#include <glib/gtypes.h>
+#include <glib/gslist.h>
+
+#include "libnr/nr-forward.h"
+#include "libnr/nr-point.h"
+
+/// Wrapper around NArtBpath.
+struct SPCurve {
+ gint refcount;
+ NArtBpath *bpath;
+
+ /// Index in bpath[] of NR_END element.
+ gint end;
+
+ /// Allocated size (i.e., capacity) of bpath[] array. Not to be confused
+ /// with the SP_CURVE_LENGTH macro, which returns the logical length of
+ /// the path (i.e., index of NR_END).
+ gint length;
+
+ /// Index in bpath[] of the start (i.e., moveto element) of the last
+ /// subpath in this path.
+ gint substart;
+
+ /// Previous moveto position.
+ /// \note This is used for coalescing moveto's, whereas if we're to
+ /// conform to the SVG spec then we mustn't coalesce movetos if we have
+ /// midpoint markers. Ref:
+ /// http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
+ /// (first subitem of the item about zero-length path segments)
+ NR::Point movePos;
+
+ /// True iff bpath points to read-only, static storage (see callers of
+ /// sp_curve_new_from_static_bpath), in which case we shouldn't free
+ /// bpath and shouldn't write through it.
+ bool sbpath : 1;
+
+ /// True iff current point is defined. Initially false for a new curve;
+ /// becomes true after moveto; becomes false on closepath. Curveto,
+ /// lineto etc. require hascpt; hascpt remains true after lineto/curveto.
+ bool hascpt : 1;
+
+ /// True iff previous was moveto.
+ bool posSet : 1;
+
+ /// True iff bpath end is moving.
+ bool moving : 1;
+
+ /// True iff all subpaths are closed.
+ bool closed : 1;
+};
+
+#define SP_CURVE_LENGTH(c) (((SPCurve const *)(c))->end)
+#define SP_CURVE_BPATH(c) (((SPCurve const *)(c))->bpath)
+#define SP_CURVE_SEGMENT(c,i) (((SPCurve const *)(c))->bpath + (i))
+
+/* Constructors */
+
+SPCurve *sp_curve_new();
+SPCurve *sp_curve_new_sized(gint length);
+SPCurve *sp_curve_new_from_bpath(NArtBpath *bpath);
+SPCurve *sp_curve_new_from_static_bpath(NArtBpath const *bpath);
+SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[]);
+
+SPCurve *sp_curve_ref(SPCurve *curve);
+SPCurve *sp_curve_unref(SPCurve *curve);
+
+SPCurve *sp_curve_copy(SPCurve *curve);
+SPCurve *sp_curve_concat(GSList const *list);
+GSList *sp_curve_split(SPCurve const *curve);
+void sp_curve_transform(SPCurve *curve, NR::Matrix const &);
+void sp_curve_transform(SPCurve *curve, NR::translate const &);
+void sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &, NR::Point const &);
+void sp_curve_move_endpoints(SPCurve *curve, NR::Point const &,
+ NR::Point const &);
+
+/* Methods */
+
+void sp_curve_reset(SPCurve *curve);
+
+void sp_curve_moveto(SPCurve *curve, NR::Point const &p);
+void sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y);
+void sp_curve_lineto(SPCurve *curve, NR::Point const &p);
+void sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y);
+void sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y);
+void sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2);
+void sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2);
+void sp_curve_closepath(SPCurve *curve);
+void sp_curve_closepath_current(SPCurve *curve);
+
+SPCurve *sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance);
+
+#define sp_curve_is_empty sp_curve_empty
+bool sp_curve_empty(SPCurve *curve);
+NArtBpath *sp_curve_last_bpath(SPCurve const *curve);
+NArtBpath *sp_curve_first_bpath(SPCurve const *curve);
+NR::Point sp_curve_first_point(SPCurve const *curve);
+NR::Point sp_curve_last_point(SPCurve const *curve);
+NR::Point sp_curve_second_point(SPCurve const *curve);
+NR::Point sp_curve_penultimate_point(SPCurve const *curve);
+
+void sp_curve_append(SPCurve *curve, SPCurve const *curve2, bool use_lineto);
+SPCurve *sp_curve_reverse(SPCurve const *curve);
+void sp_curve_backspace(SPCurve *curve);
+
+
+#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 :
diff --git a/src/display/display-forward.h b/src/display/display-forward.h
new file mode 100644
index 000000000..2af6f2c44
--- /dev/null
+++ b/src/display/display-forward.h
@@ -0,0 +1,46 @@
+#ifndef SEEN_DISPLAY_DISPLAY_FORWARD_H
+#define SEEN_DISPLAY_DISPLAY_FORWARD_H
+
+#include <glib-object.h>
+
+struct SPCanvas;
+struct SPCanvasClass;
+struct SPCanvasItem;
+struct SPCanvasItemClass;
+struct SPCanvasGroup;
+struct SPCanvasGroupClass;
+struct SPCurve;
+
+
+#define SP_TYPE_CANVAS_ITEM (sp_canvas_item_get_type())
+#define SP_CANVAS_ITEM(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_ITEM, SPCanvasItem))
+#define SP_IS_CANVAS_ITEM(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_ITEM))
+#define SP_CANVAS_ITEM_GET_CLASS(o) (GTK_CHECK_GET_CLASS((o), SP_TYPE_CANVAS_ITEM, SPCanvasItemClass))
+
+GType sp_canvas_item_get_type();
+
+#define SP_TYPE_CANVAS_GROUP (sp_canvas_group_get_type())
+#define SP_CANVAS_GROUP(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_GROUP, SPCanvasGroup))
+#define SP_IS_CANVAS_GROUP(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_GROUP))
+
+GType sp_canvas_group_get_type();
+
+#define SP_TYPE_CANVAS (sp_canvas_get_type())
+#define SP_CANVAS(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS, SPCanvas))
+#define SP_IS_CANVAS(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS))
+
+GType sp_canvas_get_type();
+
+
+#endif /* !SEEN_DISPLAY_DISPLAY_FORWARD_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/gnome-canvas-acetate.cpp b/src/display/gnome-canvas-acetate.cpp
new file mode 100644
index 000000000..1a58c4b19
--- /dev/null
+++ b/src/display/gnome-canvas-acetate.cpp
@@ -0,0 +1,100 @@
+#define __SP_CANVAS_ACETATE_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#include "display-forward.h"
+#include "gnome-canvas-acetate.h"
+
+static void sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass);
+static void sp_canvas_acetate_init (SPCanvasAcetate *acetate);
+static void sp_canvas_acetate_destroy (GtkObject *object);
+
+static void sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static double sp_canvas_acetate_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+
+static SPCanvasItemClass *parent_class;
+
+GtkType
+sp_canvas_acetate_get_type (void)
+{
+ static GtkType acetate_type = 0;
+ if (!acetate_type) {
+ GtkTypeInfo acetate_info = {
+ "SPCanvasAcetate",
+ sizeof (SPCanvasAcetate),
+ sizeof (SPCanvasAcetateClass),
+ (GtkClassInitFunc) sp_canvas_acetate_class_init,
+ (GtkObjectInitFunc) sp_canvas_acetate_init,
+ NULL, NULL, NULL
+ };
+ acetate_type = gtk_type_unique (sp_canvas_item_get_type (), &acetate_info);
+ }
+ return acetate_type;
+}
+
+static void
+sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass)
+{
+ GtkObjectClass *object_class;
+ SPCanvasItemClass *item_class;
+
+ object_class = (GtkObjectClass *) klass;
+ item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
+
+ object_class->destroy = sp_canvas_acetate_destroy;
+
+ item_class->update = sp_canvas_acetate_update;
+ item_class->point = sp_canvas_acetate_point;
+}
+
+static void
+sp_canvas_acetate_init (SPCanvasAcetate *acetate)
+{
+ /* Nothing here */
+}
+
+static void
+sp_canvas_acetate_destroy (GtkObject *object)
+{
+ SPCanvasAcetate *acetate;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_ACETATE (object));
+
+ acetate = SP_CANVAS_ACETATE (object);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix 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, NR::Point p, SPCanvasItem **actual_item)
+{
+ *actual_item = item;
+
+ return 0.0;
+}
+
diff --git a/src/display/gnome-canvas-acetate.h b/src/display/gnome-canvas-acetate.h
new file mode 100644
index 000000000..40574e1bf
--- /dev/null
+++ b/src/display/gnome-canvas-acetate.h
@@ -0,0 +1,41 @@
+#ifndef __SP_CANVAS_ACETATE_H__
+#define __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, read the file 'COPYING' for more information
+ */
+
+#include <glib/gtypes.h>
+#include "display/sp-canvas.h"
+
+
+#define GNOME_TYPE_CANVAS_ACETATE (sp_canvas_acetate_get_type ())
+#define SP_CANVAS_ACETATE(obj) (GTK_CHECK_CAST ((obj), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetate))
+#define SP_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetateClass))
+#define GNOME_IS_CANVAS_ACETATE(obj) (GTK_CHECK_TYPE ((obj), GNOME_TYPE_CANVAS_ACETATE))
+#define GNOME_IS_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ACETATE))
+
+
+struct SPCanvasAcetate {
+ SPCanvasItem item;
+};
+
+struct SPCanvasAcetateClass {
+ SPCanvasItemClass parent_class;
+};
+
+GtkType sp_canvas_acetate_get_type (void);
+
+
+
+#endif
diff --git a/src/display/guideline.cpp b/src/display/guideline.cpp
new file mode 100644
index 000000000..d44ac8ab8
--- /dev/null
+++ b/src/display/guideline.cpp
@@ -0,0 +1,198 @@
+#define __SP_GUIDELINE_C__
+
+/*
+ * Infinite horizontal/vertical line
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <libnr/nr-pixops.h>
+#include "display-forward.h"
+#include "sp-canvas-util.h"
+#include "guideline.h"
+
+static void sp_guideline_class_init(SPGuideLineClass *c);
+static void sp_guideline_init(SPGuideLine *guideline);
+static void sp_guideline_destroy(GtkObject *object);
+
+static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+
+static SPCanvasItemClass *parent_class;
+
+GType sp_guideline_get_type()
+{
+ static GType guideline_type = 0;
+
+ if (!guideline_type) {
+ static const GTypeInfo guideline_info =
+ {
+ sizeof (SPGuideLineClass),
+ NULL, NULL,
+ (GClassInitFunc) sp_guideline_class_init,
+ NULL, NULL,
+ sizeof (SPGuideLine),
+ 16,
+ (GInstanceInitFunc) sp_guideline_init,
+ NULL,
+ };
+
+ guideline_type = g_type_register_static(SP_TYPE_CANVAS_ITEM, "SPGuideLine", &guideline_info, (GTypeFlags) 0);
+ }
+
+ return guideline_type;
+}
+
+static void sp_guideline_class_init(SPGuideLineClass *c)
+{
+ parent_class = (SPCanvasItemClass*) g_type_class_peek_parent(c);
+
+ GtkObjectClass *object_class = (GtkObjectClass *) c;
+ object_class->destroy = sp_guideline_destroy;
+
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) c;
+ 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->vertical = 0;
+ gl->sensitive = 0;
+}
+
+static void sp_guideline_destroy(GtkObject *object)
+{
+ GTK_OBJECT_CLASS(parent_class)->destroy(object);
+}
+
+static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPGuideLine const *gl = SP_GUIDELINE (item);
+
+ sp_canvas_prepare_buffer(buf);
+
+ unsigned int const r = NR_RGBA32_R (gl->rgba);
+ unsigned int const g = NR_RGBA32_G (gl->rgba);
+ unsigned int const b = NR_RGBA32_B (gl->rgba);
+ unsigned int const a = NR_RGBA32_A (gl->rgba);
+
+ int p0, p1, step;
+ unsigned char *d;
+
+ if (gl->vertical) {
+
+ if (gl->position < buf->rect.x0 || gl->position >= buf->rect.x1) {
+ return;
+ }
+
+ p0 = buf->rect.y0;
+ p1 = buf->rect.y1;
+ step = buf->buf_rowstride;
+ d = buf->buf + 3 * (gl->position - buf->rect.x0);
+
+ } else {
+
+ if (gl->position < buf->rect.y0 || gl->position >= buf->rect.y1) {
+ return;
+ }
+
+ p0 = buf->rect.x0;
+ p1 = buf->rect.x1;
+ step = 3;
+ d = buf->buf + (gl->position - buf->rect.y0) * buf->buf_rowstride;
+ }
+
+ for (int p = p0; p < p1; p++) {
+ d[0] = NR_COMPOSEN11(r, a, d[0]);
+ d[1] = NR_COMPOSEN11(g, a, d[1]);
+ d[2] = NR_COMPOSEN11(b, a, d[2]);
+ d += step;
+ }
+}
+
+static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SPGuideLine *gl = SP_GUIDELINE(item);
+
+ if (((SPCanvasItemClass *) parent_class)->update) {
+ ((SPCanvasItemClass *) parent_class)->update(item, affine, flags);
+ }
+
+ if (gl->vertical) {
+ gl->position = (int) (affine[4] + 0.5);
+ sp_canvas_update_bbox (item, gl->position, -1000000, gl->position + 1, 1000000);
+ } else {
+ gl->position = (int) (affine[5] - 0.5);
+ sp_canvas_update_bbox (item, -1000000, gl->position, 1000000, gl->position + 1);
+ }
+}
+
+static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+{
+ SPGuideLine *gl = SP_GUIDELINE (item);
+
+ if (!gl->sensitive) {
+ return NR_HUGE;
+ }
+
+ *actual_item = item;
+
+ if (gl->vertical) {
+ return MAX(fabs(gl->position - p[NR::X])-1, 0);
+ } else {
+ return MAX(fabs(gl->position - p[NR::Y])-1, 0);
+ }
+}
+
+SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical)
+{
+ SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_GUIDELINE, NULL);
+
+ SPGuideLine *gl = SP_GUIDELINE(item);
+
+ gl->vertical = vertical;
+ sp_guideline_set_position(gl, position);
+
+ return item;
+}
+
+void sp_guideline_set_position(SPGuideLine *gl, double position)
+{
+ sp_canvas_item_affine_absolute(SP_CANVAS_ITEM (gl),
+ NR::Matrix(NR::translate(position, position)));
+}
+
+void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba)
+{
+ gl->rgba = rgba;
+
+ sp_canvas_item_request_update(SP_CANVAS_ITEM(gl));
+}
+
+void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive)
+{
+ gl->sensitive = sensitive;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 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/guideline.h b/src/display/guideline.h
new file mode 100644
index 000000000..22f0af69a
--- /dev/null
+++ b/src/display/guideline.h
@@ -0,0 +1,55 @@
+#ifndef __SP_GUIDELINE_H__
+#define __SP_GUIDELINE_H__
+
+/*
+ * Infinite horizontal/vertical line; the visual representation of SPGuide.
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "sp-canvas.h"
+
+#define SP_TYPE_GUIDELINE (sp_guideline_get_type())
+#define SP_GUIDELINE(o) (GTK_CHECK_CAST((o), SP_TYPE_GUIDELINE, SPGuideLine))
+#define SP_IS_GUIDELINE(o) (GTK_CHECK_TYPE((o), SP_TYPE_GUIDELINE))
+
+struct SPGuideLine {
+ SPCanvasItem item;
+
+ guint32 rgba;
+
+ int position;
+
+ unsigned int vertical : 1;
+ unsigned int sensitive : 1;
+};
+
+struct SPGuideLineClass {
+ SPCanvasItemClass parent_class;
+};
+
+GType sp_guideline_get_type();
+
+SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical);
+
+void sp_guideline_set_position(SPGuideLine *gl, double position);
+void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba);
+void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive);
+
+#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/makefile.in b/src/display/makefile.in
new file mode 100644
index 000000000..9d9426809
--- /dev/null
+++ b/src/display/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) display/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) display/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/display/nr-arena-forward.h b/src/display/nr-arena-forward.h
new file mode 100644
index 000000000..67f62a78b
--- /dev/null
+++ b/src/display/nr-arena-forward.h
@@ -0,0 +1,51 @@
+#ifndef __NR_ARENA_FORWARD_H__
+#define __NR_ARENA_FORWARD_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, read the file 'COPYING' for more information
+ */
+
+struct NRArena;
+struct NRArenaClass;
+
+struct NRArenaItem;
+struct NRArenaItemClass;
+
+struct NRArenaGroup;
+struct NRArenaGroupClass;
+
+struct NRArenaShape;
+struct NRArenaShapeClass;
+
+struct NRArenaShapeGroup;
+struct NRArenaShapeGroupClass;
+
+struct NRArenaImage;
+struct NRArenaImageClass;
+
+struct NRArenaGlyphs;
+struct NRArenaGlyphsClass;
+
+struct NRArenaGlyphsGroup;
+struct NRArenaGlyphsGroupClass;
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/nr-arena-glyphs.cpp b/src/display/nr-arena-glyphs.cpp
new file mode 100644
index 000000000..861b8baf8
--- /dev/null
+++ b/src/display/nr-arena-glyphs.cpp
@@ -0,0 +1,679 @@
+#define __NR_ARENA_GLYPHS_C__
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <libnr/nr-blit.h>
+#include <libnr/nr-path.h>
+#include "../style.h"
+#include "nr-arena.h"
+#include "nr-arena-glyphs.h"
+
+#ifdef test_glyph_liv
+#include "../display/canvas-bpath.h"
+#include <libnrtype/font-instance.h>
+#include <libnrtype/raster-glyph.h>
+#include <libnrtype/RasterFont.h>
+
+// defined in nr-arena-shape.cpp
+void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS);
+#endif
+
+static void nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass);
+static void nr_arena_glyphs_init (NRArenaGlyphs *glyphs);
+static void nr_arena_glyphs_finalize (NRObject *object);
+
+static guint nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
+static guint nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+static NRArenaItem *nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+
+static NRArenaItemClass *glyphs_parent_class;
+
+NRType
+nr_arena_glyphs_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ARENA_ITEM,
+ "NRArenaGlyphs",
+ sizeof (NRArenaGlyphsClass),
+ sizeof (NRArenaGlyphs),
+ (void (*) (NRObjectClass *)) nr_arena_glyphs_class_init,
+ (void (*) (NRObject *)) nr_arena_glyphs_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass)
+{
+ NRObjectClass *object_class;
+ NRArenaItemClass *item_class;
+
+ object_class = (NRObjectClass *) klass;
+ item_class = (NRArenaItemClass *) klass;
+
+ glyphs_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
+
+ object_class->finalize = nr_arena_glyphs_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaGlyphs>;
+
+ item_class->update = nr_arena_glyphs_update;
+ item_class->clip = nr_arena_glyphs_clip;
+ item_class->pick = nr_arena_glyphs_pick;
+}
+
+static void
+nr_arena_glyphs_init (NRArenaGlyphs *glyphs)
+{
+ glyphs->style = NULL;
+ nr_matrix_set_identity(&glyphs->g_transform);
+ glyphs->font = NULL;
+ glyphs->glyph = 0;
+
+ glyphs->rfont = NULL;
+ glyphs->sfont = NULL;
+ glyphs->x = glyphs->y = 0.0;
+
+// nr_matrix_set_identity(&glyphs->cached_tr);
+// glyphs->cached_shp=NULL;
+// glyphs->cached_shp_dirty=false;
+// glyphs->cached_style_dirty=false;
+
+// glyphs->stroke_shp=NULL;
+}
+
+static void
+nr_arena_glyphs_finalize (NRObject *object)
+{
+ NRArenaGlyphs *glyphs=static_cast<NRArenaGlyphs *>(object);
+
+// if (glyphs->cached_shp) {
+// delete glyphs->cached_shp;
+// glyphs->cached_shp = NULL;
+// }
+// if (glyphs->stroke_shp) {
+// delete glyphs->stroke_shp;
+// glyphs->stroke_shp = NULL;
+// }
+
+ if (glyphs->rfont) {
+ glyphs->rfont->Unref();
+ glyphs->rfont=NULL;
+ }
+ if (glyphs->sfont) {
+ glyphs->sfont->Unref();
+ glyphs->sfont=NULL;
+ }
+
+ if (glyphs->font) {
+ glyphs->font->Unref();
+ glyphs->font=NULL;
+ }
+
+ if (glyphs->style) {
+ sp_style_unref (glyphs->style);
+ glyphs->style = NULL;
+ }
+
+ ((NRObjectClass *) glyphs_parent_class)->finalize (object);
+}
+
+static guint
+nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset)
+{
+ NRArenaGlyphs *glyphs;
+ raster_font *rfont;
+
+ glyphs = NR_ARENA_GLYPHS (item);
+
+ if (!glyphs->font || !glyphs->style) return NR_ARENA_ITEM_STATE_ALL;
+ if ((glyphs->style->fill.type == SP_PAINT_TYPE_NONE) && (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE)) return NR_ARENA_ITEM_STATE_ALL;
+
+ NRRect bbox;
+ bbox.x0 = bbox.y0 = NR_HUGE;
+ bbox.x1 = bbox.y1 = -NR_HUGE;
+
+ if (glyphs->style->fill.type != SP_PAINT_TYPE_NONE) {
+ NRMatrix t;
+ nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform);
+ glyphs->x = t.c[4];
+ glyphs->y = t.c[5];
+ t.c[4]=0;
+ t.c[5]=0;
+ rfont = glyphs->font->RasterFont(t, 0);
+ if (glyphs->rfont) glyphs->rfont->Unref();
+ glyphs->rfont = rfont;
+
+ if (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE) { // Optimization: do fill bbox only if there's no stroke
+ NRRect narea;
+ if ( glyphs->rfont ) glyphs->rfont->BBox(glyphs->glyph, &narea);
+ bbox.x0 = narea.x0 + glyphs->x;
+ bbox.y0 = narea.y0 + glyphs->y;
+ bbox.x1 = narea.x1 + glyphs->x;
+ bbox.y1 = narea.y1 + glyphs->y;
+ }
+ }
+
+ if (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE) {
+ /* Build state data */
+ NRMatrix t;
+ nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform);
+ glyphs->x = t.c[4];
+ glyphs->y = t.c[5];
+ t.c[4]=0;
+ t.c[5]=0;
+
+ const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform);
+ if ( fabs(glyphs->style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
+ font_style nstyl;
+ nstyl.transform = t;
+ nstyl.stroke_width=MAX (0.125, glyphs->style->stroke_width.computed * scale);
+ if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ) nstyl.stroke_cap=butt_straight;
+ if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ) nstyl.stroke_cap=butt_round;
+ if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ) nstyl.stroke_cap=butt_square;
+ if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ) nstyl.stroke_join=join_pointy;
+ if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ) nstyl.stroke_join=join_round;
+ if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ) nstyl.stroke_join=join_straight;
+ nstyl.stroke_miter_limit = glyphs->style->stroke_miterlimit.value;
+ nstyl.nbDash=0;
+ nstyl.dashes=NULL;
+ if ( glyphs->style->stroke_dash.n_dash > 0 ) {
+ nstyl.nbDash=glyphs->style->stroke_dash.n_dash;
+ nstyl.dashes=(double*)malloc(nstyl.nbDash*sizeof(double));
+ for (int i = 0; i < nstyl.nbDash; i++) nstyl.dashes[i]= glyphs->style->stroke_dash.dash[i] * scale;
+ }
+ rfont = glyphs->font->RasterFont( nstyl);
+ if ( nstyl.dashes ) free(nstyl.dashes);
+ if (glyphs->sfont) glyphs->sfont->Unref();
+ glyphs->sfont = rfont;
+
+ NRRect narea;
+ if ( glyphs->sfont ) glyphs->sfont->BBox(glyphs->glyph, &narea);
+ narea.x0-=nstyl.stroke_width;
+ narea.y0-=nstyl.stroke_width;
+ narea.x1+=nstyl.stroke_width;
+ narea.y1+=nstyl.stroke_width;
+ bbox.x0 = narea.x0 + glyphs->x;
+ bbox.y0 = narea.y0 + glyphs->y;
+ bbox.x1 = narea.x1 + glyphs->x;
+ bbox.y1 = narea.y1 + glyphs->y;
+ }
+ }
+ if (nr_rect_d_test_empty(&bbox)) return NR_ARENA_ITEM_STATE_ALL;
+
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.0);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.0);
+ nr_arena_request_render_rect (item->arena, &item->bbox);
+
+ return NR_ARENA_ITEM_STATE_ALL;
+}
+
+static guint
+nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
+{
+ NRArenaGlyphs *glyphs;
+
+ glyphs = NR_ARENA_GLYPHS (item);
+
+ if (!glyphs->font ) return item->state;
+
+ /* TODO : render to greyscale pixblock provided for clipping */
+
+ return item->state;
+}
+
+static NRArenaItem *
+nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky)
+{
+ NRArenaGlyphs *glyphs;
+
+ glyphs = NR_ARENA_GLYPHS (item);
+
+ if (!glyphs->font ) return NULL;
+ if (!glyphs->style) return NULL;
+
+ const double x = p[NR::X];
+ const double y = p[NR::Y];
+ /* With text we take a simple approach: pick if the point is in a characher bbox */
+ if ((x >= item->bbox.x0) && (y >= item->bbox.y0) && (x <= item->bbox.x1) && (y <= item->bbox.y1)) return item;
+
+/* NR::Point const thePt = p;
+ if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) {
+ if (glyphs->stroke_shp->PtWinding(thePt) > 0 ) return item;
+ }
+ if (delta > 1e-3) {
+ if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) {
+ if ( glyphs->stroke_shp->DistanceLE(thePt, delta)) return item;
+ }
+ }*/
+
+ return NULL;
+}
+
+void
+nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs, SPCurve *curve, unsigned int lieutenant, font_instance *font, gint glyph, const NRMatrix *transform)
+{
+ nr_return_if_fail (glyphs != NULL);
+ nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs));
+
+ nr_arena_item_request_render (NR_ARENA_ITEM (glyphs));
+
+ // glyphs->cached_shp_dirty=true;
+
+ if (transform) {
+ glyphs->g_transform = *transform;
+ } else {
+ nr_matrix_set_identity (&glyphs->g_transform);
+ }
+
+ //printf("glyph_setpath ");
+ if ( font ) font->Ref();
+ if ( glyphs->font ) glyphs->font->Unref();
+ glyphs->font=font;
+ glyphs->glyph = glyph;
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void
+nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style)
+{
+ nr_return_if_fail (glyphs != NULL);
+ nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs));
+
+// glyphs->cached_style_dirty=true;
+
+ if (style) sp_style_ref (style);
+ if (glyphs->style) sp_style_unref (glyphs->style);
+ glyphs->style = style;
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static guint
+nr_arena_glyphs_fill_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m)
+{
+ NRArenaItem *item;
+
+ /* fixme: area == m->area, so merge these */
+
+ item = NR_ARENA_ITEM (glyphs);
+
+ if (glyphs->rfont && nr_rect_l_test_intersect (area, &item->bbox)) {
+ raster_glyph* g=glyphs->rfont->GetGlyph(glyphs->glyph);
+ if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m);
+ }
+
+ return item->state;
+}
+
+static guint
+nr_arena_glyphs_stroke_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m)
+{
+ NRArenaItem *item;
+
+ item = NR_ARENA_ITEM (glyphs);
+ if (glyphs->sfont && nr_rect_l_test_intersect (area, &item->bbox)) {
+ raster_glyph* g=glyphs->sfont->GetGlyph(glyphs->glyph);
+ if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m);
+ }
+/* if (glyphs->stroke_shp && nr_rect_l_test_intersect (area, &item->bbox)) {
+ NRPixBlock gb;
+ gint x, y;
+ nr_pixblock_setup_fast (&gb, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+ // art_gray_svp_aa is just fillung apparently
+ // dunno why it's used here instead of its libnr counterpart
+ nr_pixblock_render_shape_mask_or (gb,glyphs->stroke_shp);
+ for (y = area->y0; y < area->y1; y++) {
+ guchar *d, *s;
+ d = NR_PIXBLOCK_PX (m) + (y - area->y0) * m->rs;
+ s = NR_PIXBLOCK_PX (&gb) + (y - area->y0) * gb.rs;
+ for (x = area->x0; x < area->x1; x++) {
+ *d = (*d) + ((255 - *d) * (*s) / 255);
+ d += 1;
+ s += 1;
+ }
+ }
+ nr_pixblock_release (&gb);
+ m->empty = FALSE;
+ }*/
+
+ return item->state;
+}
+
+static void nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass);
+static void nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group);
+static void nr_arena_glyphs_group_finalize (NRObject *object);
+
+static guint nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
+static unsigned int nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
+static unsigned int nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+static NRArenaItem *nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky);
+
+static NRArenaGroupClass *group_parent_class;
+
+NRType
+nr_arena_glyphs_group_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ARENA_GROUP,
+ "NRArenaGlyphsGroup",
+ sizeof (NRArenaGlyphsGroupClass),
+ sizeof (NRArenaGlyphsGroup),
+ (void (*) (NRObjectClass *)) nr_arena_glyphs_group_class_init,
+ (void (*) (NRObject *)) nr_arena_glyphs_group_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass)
+{
+ NRObjectClass *object_class;
+ NRArenaItemClass *item_class;
+
+ object_class = (NRObjectClass *) klass;
+ item_class = (NRArenaItemClass *) klass;
+
+ group_parent_class = (NRArenaGroupClass *) ((NRObjectClass *) klass)->parent;
+
+ object_class->finalize = nr_arena_glyphs_group_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaGlyphsGroup>;
+
+ item_class->update = nr_arena_glyphs_group_update;
+ item_class->render = nr_arena_glyphs_group_render;
+ item_class->clip = nr_arena_glyphs_group_clip;
+ item_class->pick = nr_arena_glyphs_group_pick;
+}
+
+static void
+nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group)
+{
+ group->style = NULL;
+ group->paintbox.x0 = group->paintbox.y0 = 0.0F;
+ group->paintbox.x1 = group->paintbox.y1 = 1.0F;
+
+ group->fill_painter = NULL;
+ group->stroke_painter = NULL;
+}
+
+static void
+nr_arena_glyphs_group_finalize (NRObject *object)
+{
+ NRArenaGlyphsGroup *group=static_cast<NRArenaGlyphsGroup *>(object);
+
+ if (group->fill_painter) {
+ sp_painter_free (group->fill_painter);
+ group->fill_painter = NULL;
+ }
+
+ if (group->stroke_painter) {
+ sp_painter_free (group->stroke_painter);
+ group->stroke_painter = NULL;
+ }
+
+ if (group->style) {
+ sp_style_unref (group->style);
+ group->style = NULL;
+ }
+
+ ((NRObjectClass *) group_parent_class)->finalize (object);
+}
+
+static guint
+nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset)
+{
+ NRArenaGlyphsGroup *group = NR_ARENA_GLYPHS_GROUP (item);
+
+ if (group->fill_painter) {
+ sp_painter_free (group->fill_painter);
+ group->fill_painter = NULL;
+ }
+
+ if (group->stroke_painter) {
+ sp_painter_free (group->stroke_painter);
+ group->stroke_painter = NULL;
+ }
+
+ item->render_opacity = TRUE;
+ if (group->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) {
+ group->fill_painter = sp_paint_server_painter_new (SP_STYLE_FILL_SERVER (group->style),
+ NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform),
+ &group->paintbox);
+ item->render_opacity = FALSE;
+ }
+
+ if (group->style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
+ group->stroke_painter = sp_paint_server_painter_new (SP_STYLE_STROKE_SERVER (group->style),
+ NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform),
+ &group->paintbox);
+ item->render_opacity = FALSE;
+ }
+
+ if ( item->render_opacity == TRUE && group->style->stroke.type != SP_PAINT_TYPE_NONE && group->style->fill.type != SP_PAINT_TYPE_NONE ) {
+ item->render_opacity=FALSE;
+ }
+
+ if (((NRArenaItemClass *) group_parent_class)->update)
+ return ((NRArenaItemClass *) group_parent_class)->update (item, area, gc, state, reset);
+
+ return NR_ARENA_ITEM_STATE_ALL;
+}
+
+/* This sucks - as soon, as we have inheritable renderprops, do something with that opacity */
+
+static unsigned int
+nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags)
+{
+ NRArenaItem *child;
+
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+ NRArenaGlyphsGroup *ggroup = NR_ARENA_GLYPHS_GROUP (item);
+ SPStyle const *style = ggroup->style;
+
+ guint ret = item->state;
+
+ /* Fill */
+ if (style->fill.type != SP_PAINT_TYPE_NONE || item->arena->rendermode == RENDERMODE_OUTLINE) {
+ NRPixBlock m;
+ nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+
+ /* Render children fill mask */
+ for (child = group->children; child != NULL; child = child->next) {
+ ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, &m);
+ if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) {
+ nr_pixblock_release (&m);
+ return ret;
+ }
+ }
+
+ /* Composite into buffer */
+ if (style->fill.type == SP_PAINT_TYPE_COLOR || item->arena->rendermode == RENDERMODE_OUTLINE) {
+ guint32 rgba;
+ if (item->arena->rendermode == RENDERMODE_OUTLINE) {
+ // In outline mode, render fill only, using outlinecolor
+ rgba = item->arena->outlinecolor;
+ } else if ( item->render_opacity ) {
+ rgba = sp_color_get_rgba32_falpha (&style->fill.value.color,
+ SP_SCALE24_TO_FLOAT (style->fill_opacity.value) *
+ SP_SCALE24_TO_FLOAT (style->opacity.value));
+ } else {
+ rgba = sp_color_get_rgba32_falpha (&style->fill.value.color, SP_SCALE24_TO_FLOAT (style->fill_opacity.value));
+ }
+ nr_blit_pixblock_mask_rgba32 (pb, &m, rgba);
+ pb->empty = FALSE;
+ } else if (style->fill.type == SP_PAINT_TYPE_PAINTSERVER) {
+ if (ggroup->fill_painter) {
+ nr_arena_render_paintserver_fill (pb, area, ggroup->fill_painter, SP_SCALE24_TO_FLOAT (style->fill_opacity.value), &m);
+ }
+ }
+
+ nr_pixblock_release (&m);
+ }
+
+ /* Stroke */
+ if (style->stroke.type != SP_PAINT_TYPE_NONE && !(item->arena->rendermode == RENDERMODE_OUTLINE)) {
+ NRPixBlock m;
+ guint32 rgba;
+ nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+ /* Render children stroke mask */
+ for (child = group->children; child != NULL; child = child->next) {
+ ret = nr_arena_glyphs_stroke_mask (NR_ARENA_GLYPHS (child), area, &m);
+ if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) {
+ nr_pixblock_release (&m);
+ return ret;
+ }
+ }
+ /* Composite into buffer */
+ switch (style->stroke.type) {
+ case SP_PAINT_TYPE_COLOR:
+ if ( item->render_opacity ) {
+ rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color,
+ SP_SCALE24_TO_FLOAT (style->stroke_opacity.value) *
+ SP_SCALE24_TO_FLOAT (style->opacity.value));
+ } else {
+ rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color,
+ SP_SCALE24_TO_FLOAT (style->stroke_opacity.value));
+ }
+ nr_blit_pixblock_mask_rgba32 (pb, &m, rgba);
+ pb->empty = FALSE;
+ break;
+ case SP_PAINT_TYPE_PAINTSERVER:
+ if (ggroup->stroke_painter) {
+ nr_arena_render_paintserver_fill (pb, area, ggroup->stroke_painter, SP_SCALE24_TO_FLOAT (style->stroke_opacity.value), &m);
+ }
+ break;
+ default:
+ break;
+ }
+ nr_pixblock_release (&m);
+ }
+
+ return ret;
+}
+
+static unsigned int
+nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ guint ret = item->state;
+
+ /* Render children fill mask */
+ for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
+ ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, pb);
+ if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) return ret;
+ }
+
+ return ret;
+}
+
+static NRArenaItem *
+nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky)
+{
+ NRArenaItem *picked = NULL;
+
+ if (((NRArenaItemClass *) group_parent_class)->pick)
+ picked = ((NRArenaItemClass *) group_parent_class)->pick (item, p, delta, sticky);
+
+ if (picked) picked = item;
+
+ return picked;
+}
+
+void
+nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *sg)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (sg);
+
+ nr_arena_item_request_render (NR_ARENA_ITEM (group));
+
+ while (group->children) {
+ nr_arena_item_remove_child (NR_ARENA_ITEM (group), group->children);
+ }
+}
+
+void
+nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *sg, font_instance *font, int glyph, const NRMatrix *transform)
+{
+ NRArenaGroup *group;
+ NRBPath bpath;
+
+ group = NR_ARENA_GROUP (sg);
+
+ if ( font ) bpath.path=(NArtBpath*)font->ArtBPath(glyph); else bpath.path=NULL;
+ if ( bpath.path ) {
+
+ nr_arena_item_request_render (NR_ARENA_ITEM (group));
+
+ NRArenaItem *new_arena;
+ new_arena = NRArenaGlyphs::create(group->arena);
+ nr_arena_item_append_child (NR_ARENA_ITEM (group), new_arena);
+ nr_arena_item_unref (new_arena);
+ nr_arena_glyphs_set_path (NR_ARENA_GLYPHS (new_arena), NULL, FALSE, font, glyph, transform);
+ nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (new_arena), sg->style);
+ }
+
+}
+
+void
+nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *sg, SPStyle *style)
+{
+ NRArenaGroup *group;
+ NRArenaItem *child;
+
+ nr_return_if_fail (sg != NULL);
+ nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (sg));
+
+ group = NR_ARENA_GROUP (sg);
+
+ if (style) sp_style_ref (style);
+ if (sg->style) sp_style_unref (sg->style);
+ sg->style = style;
+
+ for (child = group->children; child != NULL; child = child->next) {
+ nr_return_if_fail (NR_IS_ARENA_GLYPHS (child));
+ nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (child), sg->style);
+ }
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (sg), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void
+nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *gg, const NRRect *pbox)
+{
+ nr_return_if_fail (gg != NULL);
+ nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (gg));
+ nr_return_if_fail (pbox != NULL);
+
+ if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) {
+ gg->paintbox.x0 = pbox->x0;
+ gg->paintbox.y0 = pbox->y0;
+ gg->paintbox.x1 = pbox->x1;
+ gg->paintbox.y1 = pbox->y1;
+ } else {
+ /* fixme: We kill warning, although not sure what to do here (Lauris) */
+ gg->paintbox.x0 = gg->paintbox.y0 = 0.0F;
+ gg->paintbox.x1 = gg->paintbox.y1 = 256.0F;
+ }
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (gg), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
diff --git a/src/display/nr-arena-glyphs.h b/src/display/nr-arena-glyphs.h
new file mode 100644
index 000000000..23b74a378
--- /dev/null
+++ b/src/display/nr-arena-glyphs.h
@@ -0,0 +1,109 @@
+#ifndef __NR_ARENA_GLYPHS_H__
+#define __NR_ARENA_GLYPHS_H__
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#define NR_TYPE_ARENA_GLYPHS (nr_arena_glyphs_get_type ())
+#define NR_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS, NRArenaGlyphs))
+#define NR_IS_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS))
+
+#include <libnrtype/nrtype-forward.h>
+
+#include <display/curve.h>
+#include <forward.h>
+#include <sp-paint-server.h>
+#include <display/nr-arena-item.h>
+
+#define test_glyph_liv
+
+class Shape;
+
+NRType nr_arena_glyphs_get_type (void);
+
+struct NRArenaGlyphs : public NRArenaItem {
+ /* Glyphs data */
+ SPStyle *style;
+ NRMatrix g_transform;
+ font_instance *font;
+ gint glyph;
+
+ raster_font *rfont;
+ raster_font *sfont;
+ float x, y;
+
+// NRMatrix cached_tr;
+// Shape *cached_shp;
+// bool cached_shp_dirty;
+// bool cached_style_dirty;
+
+// Shape *stroke_shp;
+
+ static NRArenaGlyphs *create(NRArena *arena) {
+ NRArenaGlyphs *obj=reinterpret_cast<NRArenaGlyphs *>(nr_object_new(NR_TYPE_ARENA_GLYPHS));
+ obj->init(arena);
+ return obj;
+ }
+};
+
+struct NRArenaGlyphsClass {
+ NRArenaItemClass parent_class;
+};
+
+void nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs,
+ SPCurve *curve, unsigned int lieutenant,
+ font_instance *font, int glyph,
+ const NRMatrix *transform);
+void nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style);
+
+/* Integrated group of component glyphss */
+
+typedef struct NRArenaGlyphsGroup NRArenaGlyphsGroup;
+typedef struct NRArenaGlyphsGroupClass NRArenaGlyphsGroupClass;
+
+#include "nr-arena-group.h"
+
+#define NR_TYPE_ARENA_GLYPHS_GROUP (nr_arena_glyphs_group_get_type ())
+#define NR_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS_GROUP, NRArenaGlyphsGroup))
+#define NR_IS_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS_GROUP))
+
+NRType nr_arena_glyphs_group_get_type (void);
+
+struct NRArenaGlyphsGroup : public NRArenaGroup {
+ SPStyle *style;
+ NRRect paintbox;
+ /* State data */
+ SPPainter *fill_painter;
+ SPPainter *stroke_painter;
+
+ static NRArenaGlyphsGroup *create(NRArena *arena) {
+ NRArenaGlyphsGroup *obj=reinterpret_cast<NRArenaGlyphsGroup *>(nr_object_new(NR_TYPE_ARENA_GLYPHS_GROUP));
+ obj->init(arena);
+ return obj;
+ }
+};
+
+struct NRArenaGlyphsGroupClass {
+ NRArenaGroupClass parent_class;
+};
+
+/* Utility functions */
+
+void nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *group);
+
+void nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *group, font_instance *font, int glyph, const NRMatrix *transform);
+
+void nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *group, SPStyle *style);
+
+void nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *group, const NRRect *pbox);
+
+#endif
diff --git a/src/display/nr-arena-group.cpp b/src/display/nr-arena-group.cpp
new file mode 100644
index 000000000..64274202f
--- /dev/null
+++ b/src/display/nr-arena-group.cpp
@@ -0,0 +1,256 @@
+#define __NR_ARENA_GROUP_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#include "nr-arena-group.h"
+
+static void nr_arena_group_class_init (NRArenaGroupClass *klass);
+static void nr_arena_group_init (NRArenaGroup *group);
+
+static NRArenaItem *nr_arena_group_children (NRArenaItem *item);
+static NRArenaItem *nr_arena_group_last_child (NRArenaItem *item);
+static void nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+static void nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child);
+static void nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+
+static unsigned int nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset);
+static unsigned int nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
+static unsigned int nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+static NRArenaItem *nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+
+static NRArenaItemClass *parent_class;
+
+NRType
+nr_arena_group_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ARENA_ITEM,
+ "NRArenaGroup",
+ sizeof (NRArenaGroupClass),
+ sizeof (NRArenaGroup),
+ (void (*) (NRObjectClass *)) nr_arena_group_class_init,
+ (void (*) (NRObject *)) nr_arena_group_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_group_class_init (NRArenaGroupClass *klass)
+{
+ NRObjectClass *object_class;
+ NRArenaItemClass *item_class;
+
+ object_class = (NRObjectClass *) klass;
+ item_class = (NRArenaItemClass *) klass;
+
+ parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
+
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaGroup>;
+
+ item_class->children = nr_arena_group_children;
+ item_class->last_child = nr_arena_group_last_child;
+ item_class->add_child = nr_arena_group_add_child;
+ item_class->set_child_position = nr_arena_group_set_child_position;
+ item_class->remove_child = nr_arena_group_remove_child;
+ item_class->update = nr_arena_group_update;
+ item_class->render = nr_arena_group_render;
+ item_class->clip = nr_arena_group_clip;
+ item_class->pick = nr_arena_group_pick;
+}
+
+static void
+nr_arena_group_init (NRArenaGroup *group)
+{
+ group->transparent = FALSE;
+ group->children = NULL;
+ group->last = NULL;
+ nr_matrix_set_identity (&group->child_transform);
+
+#ifdef arena_item_tile_cache
+ group->skipCaching=true;
+#endif
+
+}
+
+static NRArenaItem *
+nr_arena_group_children (NRArenaItem *item)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ return group->children;
+}
+
+static NRArenaItem *
+nr_arena_group_last_child (NRArenaItem *item)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ return group->last;
+}
+
+static void
+nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ if (!ref) {
+ group->children = nr_arena_item_attach_ref (item, child, NULL, group->children);
+ } else {
+ ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next);
+ }
+
+ if (ref == group->last) group->last = child;
+
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static void
+nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ if (child == group->last) group->last = child->prev;
+
+ if (child->prev) {
+ nr_arena_item_detach_unref (item, child);
+ } else {
+ group->children = nr_arena_item_detach_unref (item, child);
+ }
+
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static void
+nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ if (child == group->last) group->last = child->prev;
+
+ if (child->prev) {
+ nr_arena_item_detach_unref (item, child);
+ } else {
+ group->children = nr_arena_item_detach_unref (item, child);
+ }
+
+ if (!ref) {
+ group->children = nr_arena_item_attach_ref (item, child, NULL, group->children);
+ } else {
+ ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next);
+ }
+
+ if (ref == group->last) group->last = child;
+
+ nr_arena_item_request_render (child);
+}
+
+static unsigned int
+nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset)
+{
+ unsigned int newstate;
+
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ unsigned int beststate = NR_ARENA_ITEM_STATE_ALL;
+
+ for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
+ NRGC cgc(gc);
+ nr_matrix_multiply (&cgc.transform, &group->child_transform, &gc->transform);
+ newstate = nr_arena_item_invoke_update (child, area, &cgc, state, reset);
+ beststate = beststate & newstate;
+ }
+
+ if (beststate & NR_ARENA_ITEM_STATE_BBOX) {
+ nr_rect_l_set_empty (&item->bbox);
+ for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
+ nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox);
+ }
+ }
+
+ return beststate;
+}
+
+static unsigned int
+nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ unsigned int ret = item->state;
+
+ /* Just compose children into parent buffer */
+ for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
+ ret = nr_arena_item_invoke_render (child, area, pb, flags);
+ if (ret & NR_ARENA_ITEM_STATE_INVALID) break;
+ }
+
+ return ret;
+}
+
+static unsigned int
+nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ unsigned int ret = item->state;
+
+ /* Just compose children into parent buffer */
+ for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
+ ret = nr_arena_item_invoke_clip (child, area, pb);
+ if (ret & NR_ARENA_ITEM_STATE_INVALID) break;
+ }
+
+ return ret;
+}
+
+static NRArenaItem *
+nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky)
+{
+ NRArenaGroup *group = NR_ARENA_GROUP (item);
+
+ for (NRArenaItem *child = group->last; child != NULL; child = child->prev) {
+ NRArenaItem *picked = nr_arena_item_invoke_pick (child, p, delta, sticky);
+ if (picked)
+ return (group->transparent) ? picked : item;
+ }
+
+ return NULL;
+}
+
+void
+nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent)
+{
+ nr_return_if_fail (group != NULL);
+ nr_return_if_fail (NR_IS_ARENA_GROUP (group));
+
+ group->transparent = transparent;
+}
+
+void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t)
+{
+ NRMatrix nt(t);
+ nr_arena_group_set_child_transform(group, &nt);
+}
+
+void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t)
+{
+ if (!t) t = &NR_MATRIX_IDENTITY;
+
+ if (!NR_MATRIX_DF_TEST_CLOSE (t, &group->child_transform, NR_EPSILON)) {
+ nr_arena_item_request_render (NR_ARENA_ITEM (group));
+ group->child_transform = *t;
+ nr_arena_item_request_update (NR_ARENA_ITEM (group), NR_ARENA_ITEM_STATE_ALL, TRUE);
+ }
+}
+
+
diff --git a/src/display/nr-arena-group.h b/src/display/nr-arena-group.h
new file mode 100644
index 000000000..b33495362
--- /dev/null
+++ b/src/display/nr-arena-group.h
@@ -0,0 +1,46 @@
+#ifndef __NR_ARENA_GROUP_H__
+#define __NR_ARENA_GROUP_H__
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#define NR_TYPE_ARENA_GROUP (nr_arena_group_get_type ())
+#define NR_ARENA_GROUP(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_GROUP, NRArenaGroup))
+#define NR_IS_ARENA_GROUP(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_GROUP))
+
+#include "nr-arena-item.h"
+
+NRType nr_arena_group_get_type (void);
+
+struct NRArenaGroup : public NRArenaItem{
+ unsigned int transparent : 1;
+ NRArenaItem *children;
+ NRArenaItem *last;
+ NRMatrix child_transform;
+
+ static NRArenaGroup *create(NRArena *arena) {
+ NRArenaGroup *obj=reinterpret_cast<NRArenaGroup *>(nr_object_new(NR_TYPE_ARENA_GROUP));
+ obj->init(arena);
+ return obj;
+ }
+};
+
+struct NRArenaGroupClass {
+ NRArenaItemClass parent_class;
+};
+
+void nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent);
+
+void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t);
+void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t);
+
+#endif
diff --git a/src/display/nr-arena-image.cpp b/src/display/nr-arena-image.cpp
new file mode 100644
index 000000000..ee566d2dc
--- /dev/null
+++ b/src/display/nr-arena-image.cpp
@@ -0,0 +1,258 @@
+#define __NR_ARENA_IMAGE_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#include <libnr/nr-compose-transform.h>
+#include "../prefs-utils.h"
+#include "nr-arena-image.h"
+
+int nr_arena_image_x_sample = 1;
+int nr_arena_image_y_sample = 1;
+
+/*
+ * NRArenaCanvasImage
+ *
+ */
+
+static void nr_arena_image_class_init (NRArenaImageClass *klass);
+static void nr_arena_image_init (NRArenaImage *image);
+static void nr_arena_image_finalize (NRObject *object);
+
+static unsigned int nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset);
+static unsigned int nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
+static NRArenaItem *nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+
+static NRArenaItemClass *parent_class;
+
+NRType
+nr_arena_image_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ARENA_ITEM,
+ "NRArenaImage",
+ sizeof (NRArenaImageClass),
+ sizeof (NRArenaImage),
+ (void (*) (NRObjectClass *)) nr_arena_image_class_init,
+ (void (*) (NRObject *)) nr_arena_image_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_image_class_init (NRArenaImageClass *klass)
+{
+ NRObjectClass *object_class;
+ NRArenaItemClass *item_class;
+
+ object_class = (NRObjectClass *) klass;
+ item_class = (NRArenaItemClass *) klass;
+
+ parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
+
+ object_class->finalize = nr_arena_image_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaImage>;
+
+ item_class->update = nr_arena_image_update;
+ item_class->render = nr_arena_image_render;
+ item_class->pick = nr_arena_image_pick;
+}
+
+static void
+nr_arena_image_init (NRArenaImage *image)
+{
+ image->px = NULL;
+
+ image->pxw = image->pxh = image->pxrs = 0;
+ image->x = image->y = 0.0;
+ image->width = 256.0;
+ image->height = 256.0;
+
+ nr_matrix_set_identity (&image->grid2px);
+}
+
+static void
+nr_arena_image_finalize (NRObject *object)
+{
+ NRArenaImage *image = NR_ARENA_IMAGE (object);
+
+ image->px = NULL;
+
+ ((NRObjectClass *) parent_class)->finalize (object);
+}
+
+static unsigned int
+nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset)
+{
+ NRMatrix grid2px;
+
+ NRArenaImage *image = NR_ARENA_IMAGE (item);
+
+ /* Request render old */
+ nr_arena_item_request_render (item);
+
+ /* Copy affine */
+ nr_matrix_invert (&grid2px, &gc->transform);
+ double hscale, vscale; // todo: replace with NR::scale
+ if (image->px) {
+ hscale = image->pxw / image->width;
+ vscale = image->pxh / image->height;
+ } else {
+ hscale = 1.0;
+ vscale = 1.0;
+ }
+
+ image->grid2px[0] = grid2px.c[0] * hscale;
+ image->grid2px[2] = grid2px.c[2] * hscale;
+ image->grid2px[4] = grid2px.c[4] * hscale;
+ image->grid2px[1] = grid2px.c[1] * vscale;
+ image->grid2px[3] = grid2px.c[3] * vscale;
+ image->grid2px[5] = grid2px.c[5] * vscale;
+
+ image->grid2px[4] -= image->x * hscale;
+ image->grid2px[5] -= image->y * vscale;
+
+ /* Calculate bbox */
+ if (image->px) {
+ NRRect bbox;
+
+ bbox.x0 = image->x;
+ bbox.y0 = image->y;
+ bbox.x1 = image->x + image->width;
+ bbox.y1 = image->y + image->height;
+ nr_rect_d_matrix_transform (&bbox, &bbox, &gc->transform);
+
+ item->bbox.x0 = (int) floor (bbox.x0);
+ item->bbox.y0 = (int) floor (bbox.y0);
+ item->bbox.x1 = (int) ceil (bbox.x1);
+ item->bbox.y1 = (int) ceil (bbox.y1);
+ } else {
+ item->bbox.x0 = (int) gc->transform[4];
+ item->bbox.y0 = (int) gc->transform[5];
+ item->bbox.x1 = item->bbox.x0 - 1;
+ item->bbox.y1 = item->bbox.y0 - 1;
+ }
+
+ nr_arena_item_request_render (item);
+
+ return NR_ARENA_ITEM_STATE_ALL;
+}
+
+#define FBITS 12
+#define b2i (image->grid2px)
+
+static unsigned int
+nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags)
+{
+ nr_arena_image_x_sample = prefs_get_int_attribute ("options.bitmapoversample", "value", 1);
+ nr_arena_image_y_sample = nr_arena_image_x_sample;
+
+ NRArenaImage *image = NR_ARENA_IMAGE (item);
+
+ if (!image->px) return item->state;
+
+ guint32 Falpha = item->opacity;
+ if (Falpha < 1) return item->state;
+
+ unsigned char * dpx = NR_PIXBLOCK_PX (pb);
+ const int drs = pb->rs;
+ const int dw = pb->area.x1 - pb->area.x0;
+ const int dh = pb->area.y1 - pb->area.y0;
+
+ unsigned char * spx = image->px;
+ const int srs = image->pxrs;
+ const int sw = image->pxw;
+ const int sh = image->pxh;
+
+ NR::Matrix d2s;
+
+ d2s[0] = b2i[0];
+ d2s[1] = b2i[1];
+ d2s[2] = b2i[2];
+ d2s[3] = b2i[3];
+ d2s[4] = b2i[0] * pb->area.x0 + b2i[2] * pb->area.y0 + b2i[4];
+ d2s[5] = b2i[1] * pb->area.x0 + b2i[3] * pb->area.y0 + b2i[5];
+
+ if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) {
+ /* fixme: This is not implemented yet (Lauris) */
+ /* nr_R8G8B8_R8G8B8_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); */
+ } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample);
+ } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) {
+
+ //FIXME: The _N_N_N_ version gives a gray border around images, see bug 906376
+ // This mode is only used when exporting, screen rendering always has _P_P_P_, so I decided to simply replace it for now
+ // Feel free to propose a better fix
+
+ //nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample);
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample);
+ }
+
+ pb->empty = FALSE;
+
+ return item->state;
+}
+
+static NRArenaItem *
+nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky)
+{
+ NRArenaImage *image = NR_ARENA_IMAGE (item);
+
+ if (!image->px) return NULL;
+
+ unsigned char * const pixels = image->px;
+ const int width = image->pxw;
+ const int height = image->pxh;
+ const int rowstride = image->pxrs;
+ NR::Point tp = p * image->grid2px;
+ const int ix = (int)(tp[NR::X]);
+ const int iy = (int)(tp[NR::Y]);
+
+ if ((ix < 0) || (iy < 0) || (ix >= width) || (iy >= height))
+ return NULL;
+
+ unsigned char *pix_ptr = pixels + iy * rowstride + ix * 4;
+ // is the alpha not transparent?
+ return (pix_ptr[3] > 0) ? item : NULL;
+}
+
+/* Utility */
+
+void
+nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs)
+{
+ nr_return_if_fail (image != NULL);
+ nr_return_if_fail (NR_IS_ARENA_IMAGE (image));
+
+ image->px = (unsigned char *) px;
+ image->pxw = pxw;
+ image->pxh = pxh;
+ image->pxrs = pxrs;
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void
+nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height)
+{
+ nr_return_if_fail (image != NULL);
+ nr_return_if_fail (NR_IS_ARENA_IMAGE (image));
+
+ image->x = x;
+ image->y = y;
+ image->width = width;
+ image->height = height;
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
diff --git a/src/display/nr-arena-image.h b/src/display/nr-arena-image.h
new file mode 100644
index 000000000..8c5afc55f
--- /dev/null
+++ b/src/display/nr-arena-image.h
@@ -0,0 +1,51 @@
+#ifndef __NR_ARENA_IMAGE_H__
+#define __NR_ARENA_IMAGE_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, read the file 'COPYING' for more information
+ */
+
+#define NR_TYPE_ARENA_IMAGE (nr_arena_image_get_type ())
+#define NR_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_IMAGE, NRArenaImage))
+#define NR_IS_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_IMAGE))
+
+#include <libnr/nr-matrix.h>
+#include "nr-arena-item.h"
+
+NRType nr_arena_image_get_type (void);
+
+struct NRArenaImage : public NRArenaItem {
+ unsigned char *px;
+ unsigned int pxw;
+ unsigned int pxh;
+ unsigned int pxrs;
+
+ double x, y;
+ double width, height;
+
+ /* From GRID to PIXELS */
+ NR::Matrix grid2px;
+
+ static NRArenaImage *create(NRArena *arena) {
+ NRArenaImage *obj=reinterpret_cast<NRArenaImage *>(nr_object_new(NR_TYPE_ARENA_IMAGE));
+ obj->init(arena);
+ return obj;
+ }
+};
+
+struct NRArenaImageClass {
+ NRArenaItemClass parent_class;
+};
+
+void nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs);
+void nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height);
+
+#endif
diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp
new file mode 100644
index 000000000..ccabe7b28
--- /dev/null
+++ b/src/display/nr-arena-item.cpp
@@ -0,0 +1,1155 @@
+#define __NR_ARENA_ITEM_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#define noNR_ARENA_ITEM_VERBOSE
+#define noNR_ARENA_ITEM_DEBUG_CASCADE
+
+
+#include <libnr/nr-blit.h>
+#include <libnr/nr-pixops.h>
+#include "nr-arena.h"
+#include "nr-arena-item.h"
+//#include "nr-arena-group.h"
+
+
+static void nr_arena_item_class_init (NRArenaItemClass *klass);
+static void nr_arena_item_init (NRArenaItem *item);
+static void nr_arena_item_private_finalize (NRObject *object);
+
+#ifdef arena_item_tile_cache
+bool insert_cache(NRArenaItem* owner,int th,int tv,NRPixBlock *ipb,NRPixBlock *mpb,double activity,double duration);
+void remove_caches(NRArenaItem* owner);
+bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask);
+#endif
+
+static NRObjectClass *parent_class;
+
+NRType
+nr_arena_item_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_OBJECT,
+ "NRArenaItem",
+ sizeof (NRArenaItemClass),
+ sizeof (NRArenaItem),
+ (void (*) (NRObjectClass *)) nr_arena_item_class_init,
+ (void (*) (NRObject *)) nr_arena_item_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_item_class_init (NRArenaItemClass *klass)
+{
+ NRObjectClass *object_class;
+
+ object_class = (NRObjectClass *) klass;
+
+ parent_class = ((NRObjectClass *) klass)->parent;
+
+ object_class->finalize = nr_arena_item_private_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaItem>;
+}
+
+NRArenaItem::NRArenaItem() {
+ // clear all reverse-pointing pointers before finalization
+ clearOnceInaccessible(&arena);
+ clearOnceInaccessible(&parent);
+ clearOnceInaccessible(&prev);
+}
+
+static void
+nr_arena_item_init (NRArenaItem *item)
+{
+ item->arena = NULL;
+ item->parent = NULL;
+ item->next = item->prev = NULL;
+
+ item->key = 0;
+
+ item->state = 0;
+ item->sensitive = TRUE;
+ item->visible = TRUE;
+
+ memset(&item->bbox, 0, sizeof(item->bbox));
+ item->transform = NULL;
+ item->opacity = 255;
+ item->render_opacity = FALSE;
+
+#ifdef arena_item_tile_cache
+ item->activity=0.0;
+ item->skipCaching=false;
+#endif
+
+ item->transform = NULL;
+ item->clip = NULL;
+ item->mask = NULL;
+ item->px = NULL;
+ item->data = NULL;
+}
+
+static void
+nr_arena_item_private_finalize (NRObject *object)
+{
+ NRArenaItem *item=static_cast<NRArenaItem *>(object);
+
+#ifdef arena_item_tile_cache
+ remove_caches(item);
+#endif
+
+ if (item->px) {
+ nr_free (item->px);
+ }
+
+ if (item->transform) {
+ nr_free (item->transform);
+ }
+
+ ((NRObjectClass *) (parent_class))->finalize (object);
+}
+
+NRArenaItem *
+nr_arena_item_children (NRArenaItem *item)
+{
+ nr_return_val_if_fail (item != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL);
+
+ if (NR_ARENA_ITEM_VIRTUAL (item, children))
+ return NR_ARENA_ITEM_VIRTUAL (item, children) (item);
+
+ return NULL;
+}
+
+NRArenaItem *
+nr_arena_item_last_child (NRArenaItem *item)
+{
+ nr_return_val_if_fail (item != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL);
+
+ if (NR_ARENA_ITEM_VIRTUAL (item, last_child)) {
+ return NR_ARENA_ITEM_VIRTUAL (item, last_child) (item);
+ } else {
+ NRArenaItem *ref;
+ ref = nr_arena_item_children (item);
+ if (ref) while (ref->next) ref = ref->next;
+ return ref;
+ }
+}
+
+void
+nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (child != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (child));
+ nr_return_if_fail (child->parent == NULL);
+ nr_return_if_fail (child->prev == NULL);
+ nr_return_if_fail (child->next == NULL);
+ nr_return_if_fail (child->arena == item->arena);
+ nr_return_if_fail (child != ref);
+ nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref));
+ nr_return_if_fail (!ref || (ref->parent == item));
+
+ if (NR_ARENA_ITEM_VIRTUAL (item, add_child))
+ NR_ARENA_ITEM_VIRTUAL (item, add_child) (item, child, ref);
+}
+
+void
+nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (child != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (child));
+ nr_return_if_fail (child->parent == item);
+
+ if (NR_ARENA_ITEM_VIRTUAL (item, remove_child))
+ NR_ARENA_ITEM_VIRTUAL (item, remove_child) (item, child);
+}
+
+void
+nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (child != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (child));
+ nr_return_if_fail (child->parent == item);
+ nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref));
+ nr_return_if_fail (!ref || (ref->parent == item));
+
+ if (NR_ARENA_ITEM_VIRTUAL (item, set_child_position))
+ NR_ARENA_ITEM_VIRTUAL (item, set_child_position) (item, child, ref);
+}
+
+NRArenaItem *
+nr_arena_item_ref (NRArenaItem *item)
+{
+ nr_object_ref ((NRObject *) item);
+
+ return item;
+}
+
+NRArenaItem *
+nr_arena_item_unref (NRArenaItem *item)
+{
+ nr_object_unref ((NRObject *) item);
+
+ return NULL;
+}
+
+unsigned int
+nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset)
+{
+ NRGC childgc(gc);
+
+ nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail (!(state & NR_ARENA_ITEM_STATE_INVALID), NR_ARENA_ITEM_STATE_INVALID);
+
+#ifdef NR_ARENA_ITEM_DEBUG_CASCADE
+ printf ("Update %s:%p %x %x %x\n", nr_type_name_from_instance ((GTypeInstance *) item), item, state, item->state, reset);
+#endif
+
+ /* return if in error */
+ if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state;
+ /* Set reset flags according to propagation status */
+ if (item->propagate) {
+ reset |= ~item->state;
+ item->propagate = FALSE;
+ }
+ /* Reset our state */
+ item->state &= ~reset;
+ /* Return if NOP */
+ if (!(~item->state & state)) return item->state;
+ /* Test whether to return immediately */
+ if (area && (item->state & NR_ARENA_ITEM_STATE_BBOX)) {
+ if (!nr_rect_l_test_intersect (area, &item->bbox)) return item->state;
+ }
+
+ /* Reset image cache, if not to be kept */
+ if (!(item->state & NR_ARENA_ITEM_STATE_IMAGE) && (item->px)) {
+ nr_free (item->px);
+ item->px = NULL;
+ }
+#ifdef arena_item_tile_cache
+ remove_caches(item);
+#endif
+
+ /* Set up local gc */
+ childgc = *gc;
+ if (item->transform) {
+ nr_matrix_multiply (&childgc.transform, item->transform, &childgc.transform);
+ }
+
+ /* Invoke the real method */
+ item->state = NR_ARENA_ITEM_VIRTUAL (item, update) (item, area, &childgc, state, reset);
+ if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state;
+ /* Clipping */
+ if (item->clip) {
+ unsigned int newstate;
+ newstate = nr_arena_item_invoke_update (item->clip, area, &childgc, state, reset);
+ if (newstate & NR_ARENA_ITEM_STATE_INVALID) {
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ nr_rect_l_intersect (&item->bbox, &item->bbox, &item->clip->bbox);
+ }
+ /* Masking */
+ if (item->mask) {
+ unsigned int newstate;
+ newstate = nr_arena_item_invoke_update (item->mask, area, &childgc, state, reset);
+ if (newstate & NR_ARENA_ITEM_STATE_INVALID) {
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ nr_rect_l_intersect (&item->bbox, &item->bbox, &item->mask->bbox);
+ }
+
+ return item->state;
+}
+
+/**
+ * Render item to pixblock.
+ *
+ * \return Has NR_ARENA_ITEM_STATE_RENDER set on success.
+ */
+
+unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags)
+{
+ NRRectL carea;
+ NRPixBlock *dpb;
+ NRPixBlock cpb;
+ unsigned int state;
+
+ nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail (item->state & NR_ARENA_ITEM_STATE_BBOX, item->state);
+
+#ifdef NR_ARENA_ITEM_VERBOSE
+ printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1);
+#endif
+
+#ifdef arena_item_tile_cache
+ item->activity*=0.5;
+#endif
+
+ /* If we are outside bbox just return successfully */
+ if (!item->visible) return item->state | NR_ARENA_ITEM_STATE_RENDER;
+ nr_rect_l_intersect (&carea, area, &item->bbox);
+ if (nr_rect_l_test_empty (&carea)) return item->state | NR_ARENA_ITEM_STATE_RENDER;
+
+ if (item->px) {
+ /* Has cache pixblock, render this and return */
+ nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P,
+ /* fixme: This probably cannot overflow, because we render only if visible */
+ /* fixme: and pixel cache is there only for small items */
+ /* fixme: But this still needs extra check (Lauris) */
+ item->bbox.x0, item->bbox.y0,
+ item->bbox.x1, item->bbox.y1,
+ item->px, 4 * (item->bbox.x1 - item->bbox.x0), FALSE, FALSE);
+ nr_blit_pixblock_pixblock (pb, &cpb);
+ nr_pixblock_release (&cpb);
+ pb->empty = FALSE;
+ return item->state | NR_ARENA_ITEM_STATE_RENDER;
+ }
+
+ dpb = pb;
+ bool canCache=false;
+#ifdef arena_item_tile_cache
+ bool checkCache=false;
+ int tile_h=0,tile_v=0;
+#endif
+ /* Setup cache if we can */
+ if ((!(flags & NR_ARENA_ITEM_RENDER_NO_CACHE)) &&
+ (carea.x0 <= item->bbox.x0) && (carea.y0 <= item->bbox.y0) &&
+ (carea.x1 >= item->bbox.x1) && (carea.y1 >= item->bbox.y1) &&
+ (((item->bbox.x1 - item->bbox.x0) * (item->bbox.y1 - item->bbox.y0)) <= 4096)) {
+ // Item bbox is fully in renderable area and size is acceptable
+ carea.x0 = item->bbox.x0;
+ carea.y0 = item->bbox.y0;
+ carea.x1 = item->bbox.x1;
+ carea.y1 = item->bbox.y1;
+ item->px = nr_new (unsigned char, 4 * (carea.x1 - carea.x0) * (carea.y1 - carea.y0));
+ nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P,
+ carea.x0, carea.y0, carea.x1, carea.y1,
+ item->px, 4 * (carea.x1 - carea.x0), TRUE, TRUE);
+ dpb = &cpb;
+ // Set nocache flag for downstream rendering
+ flags |= NR_ARENA_ITEM_RENDER_NO_CACHE;
+ } else {
+#ifdef arena_item_tile_cache
+ if ( item->skipCaching ) {
+ } else {
+ int tl=area->x0&(~127);
+ int tt=area->y0&(~127);
+ if ( area->x1 <= tl+128 && area->y1 <= tt+128 ) {
+ checkCache=true;
+ tile_h=tl/128;
+ tile_v=tt/128;
+ int surf=(area->x1-area->x0)*(area->y1-area->y0);
+ if ( surf >= 4096 ) {
+ canCache=true;
+ carea.x0=tl;
+ carea.y0=tt;
+ carea.x1=tl+128;
+ carea.y1=tt+128;
+ }
+ }
+ }
+#endif
+ }
+
+#ifdef arena_item_tile_cache
+ item->activity+=1.0;
+#endif
+
+#ifdef arena_item_tile_cache
+ if ( checkCache ) {
+ NRPixBlock ipb, mpb;
+ bool hasMask;
+ if ( test_cache(item,tile_h,tile_v,ipb,mpb,hasMask) ) {
+ // youpi! c'etait deja cache
+ if ( hasMask ) {
+ nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb);
+ } else if ( ((item->opacity != 255) && !item->render_opacity) ) {
+ nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity);
+ } else {
+ nr_blit_pixblock_pixblock (pb, &ipb);
+ }
+ pb->empty = FALSE;
+ return item->state | NR_ARENA_ITEM_STATE_RENDER;
+ }
+ }
+#endif
+ if ( canCache ) {
+#ifdef arena_item_tile_cache
+ // nota: exclusif de dpb != pb, donc pas de cas particulier a la fin
+ NRPixBlock ipb, mpb;
+
+ // struct timeval start_time,end_time;
+ // gettimeofday(&start_time,NULL);
+ GTimeVal start_time,end_time;
+ g_get_current_time (&start_time);
+ int duration=0;
+
+ /* Setup and render item buffer */
+ nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ ipb.empty = FALSE;
+
+ if (item->clip || item->mask) {
+ /* Setup mask pixblock */
+ nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ /* Do clip if needed */
+ if (item->clip) {
+ state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&mpb);
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ mpb.empty = FALSE;
+ }
+ /* Do mask if needed */
+ if (item->mask) {
+ NRPixBlock tpb;
+ /* Set up yet another temporary pixblock */
+ nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&tpb);
+ nr_pixblock_release (&mpb);
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ /* Composite with clip */
+ if (item->clip) {
+ int x, y;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *s, *d;
+ s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ unsigned int m;
+ m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255);
+ d[0] = NR_PREMUL (d[0], m);
+ s += 4;
+ d += 1;
+ }
+ }
+ } else {
+ int x, y;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *s, *d;
+ s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ unsigned int m;
+ m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255);
+ d[0] = m;
+ s += 4;
+ d += 1;
+ }
+ }
+ mpb.empty = FALSE;
+ }
+ nr_pixblock_release (&tpb);
+ }
+ /* Multiply with opacity if needed */
+ if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) {
+ int x, y;
+ unsigned int a;
+ a = item->opacity;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *d;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ d[0] = NR_PREMUL (d[0], a);
+ d += 1;
+ }
+ }
+ }
+ /* Compose rendering pixblock int destination */
+ // gettimeofday(&end_time,NULL);
+ g_get_current_time (&end_time);
+ duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000;
+ if ( !(ipb.empty) ) {
+ nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb);
+ if ( insert_cache(item,tile_h,tile_v,&ipb,&mpb,item->activity,(double)duration) ) {
+ } else {
+ nr_pixblock_release (&mpb);
+ nr_pixblock_release (&ipb);
+ }
+ dpb->empty = FALSE;
+ } else {
+ nr_pixblock_release (&ipb);
+ }
+ } else if ( ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) ) {
+ /* Opacity only */
+ // gettimeofday(&end_time,NULL);
+ g_get_current_time (&end_time);
+ duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000;
+ if ( !(ipb.empty) ) {
+ nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity);
+ if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) {
+ } else {
+ nr_pixblock_release (&ipb);
+ }
+ dpb->empty = FALSE;
+ } else {
+ nr_pixblock_release (&ipb);
+ }
+ } else {
+ // gettimeofday(&end_time,NULL);
+ g_get_current_time (&end_time);
+ duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000;
+ if ( !(ipb.empty) ) {
+ nr_blit_pixblock_pixblock (dpb, &ipb);
+ if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) {
+ } else {
+ nr_pixblock_release (&ipb);
+ }
+ dpb->empty = FALSE;
+ } else {
+ nr_pixblock_release (&ipb);
+ }
+ }
+#endif
+ } else {
+ /* Determine, whether we need temporary buffer */
+ if (item->clip || item->mask || ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE)) {
+ NRPixBlock ipb, mpb;
+
+ /* Setup and render item buffer */
+ nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ ipb.empty = FALSE;
+
+ if (item->clip || item->mask) {
+ /* Setup mask pixblock */
+ nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ /* Do clip if needed */
+ if (item->clip) {
+ state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&mpb);
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ mpb.empty = FALSE;
+ }
+ /* Do mask if needed */
+ if (item->mask) {
+ NRPixBlock tpb;
+ /* Set up yet another temporary pixblock */
+ nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE);
+ state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ nr_pixblock_release (&tpb);
+ nr_pixblock_release (&mpb);
+ nr_pixblock_release (&ipb);
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ /* Composite with clip */
+ if (item->clip) {
+ int x, y;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *s, *d;
+ s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ unsigned int m;
+ m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255);
+ d[0] = NR_PREMUL (d[0], m);
+ s += 4;
+ d += 1;
+ }
+ }
+ } else {
+ int x, y;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *s, *d;
+ s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ unsigned int m;
+ m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255);
+ d[0] = m;
+ s += 4;
+ d += 1;
+ }
+ }
+ mpb.empty = FALSE;
+ }
+ nr_pixblock_release (&tpb);
+ }
+ /* Multiply with opacity if needed */
+ if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) {
+ int x, y;
+ unsigned int a;
+ a = item->opacity;
+ for (y = carea.y0; y < carea.y1; y++) {
+ unsigned char *d;
+ d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs;
+ for (x = carea.x0; x < carea.x1; x++) {
+ d[0] = NR_PREMUL (d[0], a);
+ d += 1;
+ }
+ }
+ }
+ /* Compose rendering pixblock int destination */
+ nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb);
+ nr_pixblock_release (&mpb);
+ } else {
+ /* Opacity only */
+ nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity);
+ }
+ nr_pixblock_release (&ipb);
+ dpb->empty = FALSE;
+ } else {
+ /* Just render */
+ state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, dpb, flags);
+ if (state & NR_ARENA_ITEM_STATE_INVALID) {
+ /* Clean up and return error */
+ if (dpb != pb) nr_pixblock_release (dpb);
+ item->state |= NR_ARENA_ITEM_STATE_INVALID;
+ return item->state;
+ }
+ dpb->empty = FALSE;
+ }
+
+ if (dpb != pb) {
+ /* Have to blit from cache */
+ nr_blit_pixblock_pixblock (pb, dpb);
+ nr_pixblock_release (dpb);
+ pb->empty = FALSE;
+ item->state |= NR_ARENA_ITEM_STATE_IMAGE;
+ }
+ }
+ return item->state | NR_ARENA_ITEM_STATE_RENDER;
+}
+
+unsigned int
+nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
+{
+ nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID);
+ /* we originally short-circuited if the object state included
+ * NR_ARENA_ITEM_STATE_CLIP (and showed a warning on the console);
+ * anyone know why we stopped doing so?
+ */
+ nr_return_val_if_fail ((pb->area.x1 - pb->area.x0) >= (area->x1 - area->x0), NR_ARENA_ITEM_STATE_INVALID);
+ nr_return_val_if_fail ((pb->area.y1 - pb->area.y0) >= (area->y1 - area->y0), NR_ARENA_ITEM_STATE_INVALID);
+
+#ifdef NR_ARENA_ITEM_VERBOSE
+ printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1);
+#endif
+
+ if (item->visible && nr_rect_l_test_intersect (area, &item->bbox)) {
+ /* Need render that item */
+ if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->clip)
+ return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS(item))->clip (item, area, pb);
+ }
+
+ return item->state;
+}
+
+NRArenaItem *
+nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky)
+{
+ nr_return_val_if_fail (item != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL);
+
+ // Sometimes there's no BBOX in item->state, reason unknown (bug 992817); I made this not an assert to remove the warning
+ if (!(item->state & NR_ARENA_ITEM_STATE_BBOX) || !(item->state & NR_ARENA_ITEM_STATE_PICK))
+ return NULL;
+
+ if (!sticky && !(item->visible && item->sensitive)) return NULL;
+
+ // TODO: rewrite using NR::Rect
+ const double x = p[NR::X];
+ const double y = p[NR::Y];
+
+ if (((x + delta) >= item->bbox.x0) &&
+ ((x - delta) < item->bbox.x1) &&
+ ((y + delta) >= item->bbox.y0) &&
+ ((y - delta) < item->bbox.y1)) {
+ if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick)
+ return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick (item, p, delta, sticky);
+ }
+
+ return NULL;
+}
+
+void
+nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (!(reset & NR_ARENA_ITEM_STATE_INVALID));
+
+ if (propagate && !item->propagate) item->propagate = TRUE;
+
+ if (item->state & reset) {
+ item->state &= ~reset;
+ if (item->parent) {
+ nr_arena_item_request_update (item->parent, reset, FALSE);
+ } else {
+ nr_arena_request_update (item->arena, item);
+ }
+ }
+}
+
+void
+nr_arena_item_request_render (NRArenaItem *item)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ nr_arena_request_render_rect (item->arena, &item->bbox);
+}
+
+/* Public */
+
+NRArenaItem *
+nr_arena_item_unparent (NRArenaItem *item)
+{
+ nr_return_val_if_fail (item != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL);
+
+ nr_arena_item_request_render (item);
+
+ if (item->parent) {
+ nr_arena_item_remove_child (item->parent, item);
+ }
+
+ return NULL;
+}
+
+void
+nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child)
+{
+ nr_return_if_fail (parent != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (parent));
+ nr_return_if_fail (child != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (child));
+ nr_return_if_fail (parent->arena == child->arena);
+ nr_return_if_fail (child->parent == NULL);
+ nr_return_if_fail (child->prev == NULL);
+ nr_return_if_fail (child->next == NULL);
+
+ nr_arena_item_add_child (parent, child, nr_arena_item_last_child (parent));
+}
+
+void
+nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform)
+{
+ NRMatrix const t(transform);
+ nr_arena_item_set_transform(item, &t);
+}
+
+void
+nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform)
+{
+ const NRMatrix *ms, *md;
+
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ if (!transform && !item->transform) return;
+
+ md = (item->transform) ? item->transform : &NR_MATRIX_IDENTITY;
+ ms = (transform) ? transform : &NR_MATRIX_IDENTITY;
+
+ if (!NR_MATRIX_DF_TEST_CLOSE (md, ms, NR_EPSILON)) {
+ nr_arena_item_request_render (item);
+ if (!transform || nr_matrix_test_identity (transform, NR_EPSILON)) {
+ /* Set to identity affine */
+ if (item->transform) nr_free (item->transform);
+ item->transform = NULL;
+ } else {
+ if (!item->transform) item->transform = nr_new (NRMatrix, 1);
+ *item->transform = *transform;
+ }
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE);
+ }
+}
+
+void
+nr_arena_item_set_opacity (NRArenaItem *item, double opacity)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ nr_arena_item_request_render (item);
+
+ item->opacity = (unsigned int) (opacity * 255.9999);
+}
+
+void
+nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ /* fixme: mess with pick/repick... */
+
+ item->sensitive = sensitive;
+}
+
+void
+nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ item->visible = visible;
+
+ nr_arena_item_request_render (item);
+}
+
+void
+nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (!clip || NR_IS_ARENA_ITEM (clip));
+
+ if (clip != item->clip) {
+ nr_arena_item_request_render (item);
+ if (item->clip) item->clip = nr_arena_item_detach_unref (item, item->clip);
+ if (clip) item->clip = nr_arena_item_attach_ref (item, clip, NULL, NULL);
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE);
+ }
+}
+
+void
+nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask)
+{
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+ nr_return_if_fail (!mask || NR_IS_ARENA_ITEM (mask));
+
+ if (mask != item->mask) {
+ nr_arena_item_request_render (item);
+ if (item->mask) item->mask = nr_arena_item_detach_unref (item, item->mask);
+ if (mask) item->mask = nr_arena_item_attach_ref (item, mask, NULL, NULL);
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE);
+ }
+}
+
+void
+nr_arena_item_set_order (NRArenaItem *item, int order)
+{
+ NRArenaItem *children, *child, *ref;
+ int pos;
+
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ if (!item->parent) return;
+
+ children = nr_arena_item_children (item->parent);
+
+ ref = NULL;
+ pos = 0;
+ for (child = children; child != NULL; child = child->next) {
+ if (pos >= order) break;
+ if (child != item) {
+ ref = child;
+ pos += 1;
+ }
+ }
+
+ nr_arena_item_set_child_position (item->parent, item, ref);
+}
+
+/* Helpers */
+
+NRArenaItem *
+nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next)
+{
+ nr_return_val_if_fail (parent != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL);
+ nr_return_val_if_fail (child != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL);
+ nr_return_val_if_fail (child->parent == NULL, NULL);
+ nr_return_val_if_fail (child->prev == NULL, NULL);
+ nr_return_val_if_fail (child->next == NULL, NULL);
+ nr_return_val_if_fail (!prev || NR_IS_ARENA_ITEM (prev), NULL);
+ nr_return_val_if_fail (!prev || (prev->parent == parent), NULL);
+ nr_return_val_if_fail (!prev || (prev->next == next), NULL);
+ nr_return_val_if_fail (!next || NR_IS_ARENA_ITEM (next), NULL);
+ nr_return_val_if_fail (!next || (next->parent == parent), NULL);
+ nr_return_val_if_fail (!next || (next->prev == prev), NULL);
+
+ child->parent = parent;
+ child->prev = prev;
+ child->next = next;
+
+ if (prev) prev->next = child;
+ if (next) next->prev = child;
+
+ return child;
+}
+
+NRArenaItem *
+nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child)
+{
+ NRArenaItem *prev, *next;
+
+ nr_return_val_if_fail (parent != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL);
+ nr_return_val_if_fail (child != NULL, NULL);
+ nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL);
+ nr_return_val_if_fail (child->parent == parent, NULL);
+
+ prev = child->prev;
+ next = child->next;
+
+ child->parent = NULL;
+ child->prev = NULL;
+ child->next = NULL;
+
+ if (prev) prev->next = next;
+ if (next) next->prev = prev;
+
+ return next;
+}
+
+/*
+ *
+ * caches
+ *
+ */
+
+#ifdef arena_item_tile_cache
+typedef struct cache_entry {
+ int key;
+ double score;
+ NRArenaItem* owner;
+ int th,tv;
+ int prev,next;
+ NRPixBlock ipb;
+ bool hasMask;
+ NRPixBlock mpb;
+} cache_entry;
+
+int hash_max=2048,hash_fill=1024;
+
+int *keys=NULL;
+int nbCch=0;
+
+int nbEnt=0,maxEnt=0;
+cache_entry* entries=NULL;
+
+//#define tile_cache_stats
+#ifdef tile_cache_stats
+double hits=0,misses=0;
+int hitMissCount=0;
+#endif
+
+int hash_that(NRArenaItem* owner,int th,int tv)
+{
+ int res=GPOINTER_TO_INT(owner);
+ res*=17;
+ res+=th;
+ res*=59;
+ res+=tv;
+ res*=217;
+ if ( res < 0 ) res=-res;
+ res%=hash_max;
+ return res;
+}
+
+bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask)
+{
+ if ( keys == NULL ) {
+ hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048);
+ hash_fill=(hash_max*3)/4;
+ keys=(int*)malloc(hash_max*sizeof(int));
+ for (int i=0;i<hash_max;i++) keys[i]=-1;
+ }
+ int key=hash_that(owner,th,tv);
+ if ( keys[key] < 0 ) {
+#ifdef tile_cache_stats
+ misses+=1.0;
+#endif
+ return false;
+ }
+ int cur=keys[key];
+ while ( cur >= 0 && cur < nbEnt ) {
+ if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) {
+ hasMask=entries[cur].hasMask;
+ ipb=entries[cur].ipb;
+ mpb=entries[cur].mpb;
+#ifdef tile_cache_stats
+ hits+=1.0;
+#endif
+ return true;
+ }
+ cur=entries[cur].next;
+ }
+#ifdef tile_cache_stats
+ misses+=1.0;
+#endif
+ return false;
+}
+void remove_one_cache(int no)
+{
+ if ( no < 0 || no >= nbEnt ) return;
+
+ nr_pixblock_release(&entries[no].ipb);
+ if ( entries[no].hasMask ) nr_pixblock_release(&entries[no].mpb);
+
+ if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=entries[no].next;
+ if ( entries[no].next >= 0 ) entries[entries[no].next].prev=entries[no].prev;
+ if ( entries[no].prev < 0 ) keys[entries[no].key]=entries[no].next;
+ entries[no].prev=entries[no].next=entries[no].key=-1;
+
+ if ( no == nbEnt-1 ) {
+ nbEnt--;
+ return;
+ }
+ entries[no]=entries[--nbEnt];
+ if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=no;
+ if ( entries[no].next >= 0 ) entries[entries[no].next].prev=no;
+ if ( entries[no].prev < 0 ) keys[entries[no].key]=no;
+}
+void remove_caches(NRArenaItem* owner)
+{
+ if ( keys == NULL ) {
+ hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048);
+ hash_fill=(hash_max*3)/4;
+ keys=(int*)malloc(hash_max*sizeof(int));
+ for (int i=0;i<hash_max;i++) keys[i]=-1;
+ }
+ for (int i=nbEnt-1;i>=0;i--) {
+ if ( entries[i].owner == owner ) {
+ remove_one_cache(i);
+ }
+ }
+}
+void age_cache(void)
+{
+ for (int i=0;i<nbEnt;i++) entries[i].score*=0.95;
+}
+bool insert_cache(NRArenaItem* owner,int th,int tv,NRPixBlock *ipb,NRPixBlock *mpb,double activity,double duration)
+{
+ if ( keys == NULL ) {
+ hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048);
+ hash_fill=(hash_max*3)/4;
+ keys=(int*)malloc(hash_max*sizeof(int));
+ for (int i=0;i<hash_max;i++) keys[i]=-1;
+ }
+ for (int i=0;i<nbEnt;i++) entries[i].score*=0.95;
+#ifdef tile_cache_stats
+ hits*=0.95;
+ misses*=0.95;
+ hitMissCount++;
+ if ( hitMissCount > 100 ) {
+ hitMissCount=0;
+ printf("hit/miss = %f used/total=%i/%i\n",(misses>0.001)?hits/misses:100000.0,nbEnt,hash_max); // localizing ok
+ }
+#endif
+ int key=hash_that(owner,th,tv);
+ double nScore=/*activity**/duration;
+
+ if ( keys[key] >= 0 ) {
+ int cur=keys[key];
+ while ( cur >= 0 && cur < nbEnt ) {
+ if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) {
+ remove_one_cache(cur);
+ break;
+ }
+ cur=entries[cur].next;
+ }
+ }
+
+ bool doAdd=false;
+ if ( nbEnt < hash_fill ) {
+ doAdd=true;
+ } else {
+ double worstS=entries[0].score;
+ int worstE=0;
+ for (int i=1;i<nbEnt;i++) {
+ if ( entries[i].score < worstS ) {
+ worstS=entries[i].score;
+ worstE=i;
+ }
+ }
+ if ( worstS < nScore ) {
+ doAdd=true;
+ remove_one_cache(worstE);
+ }
+ }
+ if ( doAdd == false ) return false;
+ if ( nbEnt >= maxEnt ) {
+ maxEnt=2*nbEnt+1;
+ entries=(cache_entry*)realloc(entries,maxEnt*sizeof(cache_entry));
+ }
+ entries[nbEnt].key=key;
+ entries[nbEnt].score=nScore;
+ entries[nbEnt].owner=owner;
+ entries[nbEnt].th=th;
+ entries[nbEnt].tv=tv;
+ entries[nbEnt].prev=entries[nbEnt].next=-1;
+ entries[nbEnt].ipb=*ipb;
+ if ( mpb ) {
+ entries[nbEnt].hasMask=true;
+ entries[nbEnt].mpb=*mpb;
+ } else {
+ entries[nbEnt].hasMask=false;
+ }
+ entries[nbEnt].next=keys[key];
+ if ( entries[nbEnt].next >= 0 ) entries[entries[nbEnt].next].prev=nbEnt;
+ keys[key]=nbEnt;
+
+ nbEnt++;
+ return true;
+}
+#endif
+
+
+
+
diff --git a/src/display/nr-arena-item.h b/src/display/nr-arena-item.h
new file mode 100644
index 000000000..f43152ff9
--- /dev/null
+++ b/src/display/nr-arena-item.h
@@ -0,0 +1,188 @@
+#ifndef __NR_ARENA_ITEM_H__
+#define __NR_ARENA_ITEM_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, read the file 'COPYING' for more information
+ */
+
+#define NR_TYPE_ARENA_ITEM (nr_arena_item_get_type ())
+#define NR_ARENA_ITEM(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_ITEM, NRArenaItem))
+#define NR_IS_ARENA_ITEM(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_ITEM))
+
+#define NR_ARENA_ITEM_VIRTUAL(i,m) (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (i))->m)
+
+/*
+ * NRArenaItem state flags
+ */
+
+/*
+ * NR_ARENA_ITEM_STATE_INVALID
+ *
+ * If set or retuned indicates, that given object is in error.
+ * Calling method has to return immediately, with appropriate
+ * error flag.
+ */
+
+#define NR_ARENA_ITEM_STATE_INVALID (1 << 0)
+
+
+#define NR_ARENA_ITEM_STATE_BBOX (1 << 1)
+#define NR_ARENA_ITEM_STATE_COVERAGE (1 << 2)
+#define NR_ARENA_ITEM_STATE_DRAFT (1 << 3)
+#define NR_ARENA_ITEM_STATE_RENDER (1 << 4)
+#define NR_ARENA_ITEM_STATE_CLIP (1 << 5)
+#define NR_ARENA_ITEM_STATE_MASK (1 << 6)
+#define NR_ARENA_ITEM_STATE_PICK (1 << 7)
+#define NR_ARENA_ITEM_STATE_IMAGE (1 << 8)
+
+#define NR_ARENA_ITEM_STATE_NONE 0x0000
+#define NR_ARENA_ITEM_STATE_ALL 0x01fe
+
+#define NR_ARENA_ITEM_STATE(i,s) (NR_ARENA_ITEM (i)->state & (s))
+#define NR_ARENA_ITEM_SET_STATE(i,s) (NR_ARENA_ITEM (i)->state |= (s))
+#define NR_ARENA_ITEM_UNSET_STATE(i,s) (NR_ARENA_ITEM (i)->state &= ~(s))
+
+#define NR_ARENA_ITEM_RENDER_NO_CACHE (1 << 0)
+
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-rect-l.h>
+#include <libnr/nr-pixblock.h>
+#include <libnr/nr-object.h>
+#include "nr-arena-forward.h"
+
+// My testing shows that disabling cache reduces the amount
+// of leaked memory when many documents are loaded one from the other,
+// while there's no noticeable change in speed
+//#define arena_item_tile_cache
+
+struct NRGC {
+ NRGC(NRGC const *p) : parent(p) {}
+ NRGC const *parent;
+ NRMatrix transform;
+};
+
+struct NRArenaItem : public NRObject {
+ NRArenaItem();
+
+ NRArena *arena;
+ NRArenaItem *parent;
+ NRArenaItem *next;
+ NRArenaItem *prev;
+
+ /* Item state */
+ unsigned int state : 16;
+ unsigned int propagate : 1;
+ unsigned int sensitive : 1;
+ unsigned int visible : 1;
+ /* Whether items renders opacity itself */
+ unsigned int render_opacity : 1;
+ /* Opacity itself */
+ unsigned int opacity : 8;
+
+ /* Key for secondary rendering */
+ unsigned int key;
+
+#ifdef arena_item_tile_cache
+ bool skipCaching;
+ double activity;
+#endif
+
+ /* BBox in grid coordinates */
+ NRRectL bbox;
+ /* Our affine */
+ NRMatrix *transform;
+ /* Clip item */
+ NRArenaItem *clip;
+ /* Mask item */
+ NRArenaItem *mask;
+ /* Rendered buffer */
+ unsigned char *px;
+
+ /* Single data member */
+ void *data;
+
+ void init(NRArena *arena) {
+ this->arena = arena;
+ }
+};
+
+struct NRArenaItemClass : public NRObjectClass {
+ NRArenaItem * (* children) (NRArenaItem *item);
+ NRArenaItem * (* last_child) (NRArenaItem *item);
+ void (* add_child) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+ void (* remove_child) (NRArenaItem *item, NRArenaItem *child);
+ void (* set_child_position) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+
+ unsigned int (* update) (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset);
+ unsigned int (* render) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
+ unsigned int (* clip) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+ NRArenaItem * (* pick) (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+};
+
+#define NR_ARENA_ITEM_ARENA(ai) (((NRArenaItem *) (ai))->arena)
+
+NRType nr_arena_item_get_type (void);
+
+NRArenaItem *nr_arena_item_ref (NRArenaItem *item);
+NRArenaItem *nr_arena_item_unref (NRArenaItem *item);
+
+NRArenaItem *nr_arena_item_children (NRArenaItem *item);
+NRArenaItem *nr_arena_item_last_child (NRArenaItem *item);
+void nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+void nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child);
+void nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+
+/*
+ * Invoke update to given state, if item is inside area
+ *
+ * area == NULL is infinite
+ * gc is PARENT gc for invoke, CHILD gc in corresponding virtual method
+ * state - requested to state (bitwise or of requested flags)
+ * reset - reset to state (bitwise or of flags to reset)
+ */
+
+unsigned int nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset);
+
+unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags);
+
+unsigned int nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+NRArenaItem *nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+
+void nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate);
+void nr_arena_item_request_render (NRArenaItem *item);
+
+/* Public */
+
+NRArenaItem *nr_arena_item_unparent (NRArenaItem *item);
+
+void nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child);
+
+void nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform);
+void nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform);
+void nr_arena_item_set_opacity (NRArenaItem *item, double opacity);
+void nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive);
+void nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible);
+void nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip);
+void nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask);
+void nr_arena_item_set_order (NRArenaItem *item, int order);
+
+/* Helpers */
+
+NRArenaItem *nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next);
+NRArenaItem *nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child);
+
+#define NR_ARENA_ITEM_SET_DATA(i,v) (((NRArenaItem *) (i))->data = (v))
+#define NR_ARENA_ITEM_GET_DATA(i) (((NRArenaItem *) (i))->data)
+
+#define NR_ARENA_ITEM_SET_KEY(i,k) (((NRArenaItem *) (i))->key = (k))
+#define NR_ARENA_ITEM_GET_KEY(i) (((NRArenaItem *) (i))->key)
+
+#endif
diff --git a/src/display/nr-arena-shape.cpp b/src/display/nr-arena-shape.cpp
new file mode 100644
index 000000000..4fd381ad4
--- /dev/null
+++ b/src/display/nr-arena-shape.cpp
@@ -0,0 +1,1298 @@
+#define __NR_ARENA_SHAPE_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+
+
+#include <display/nr-arena.h>
+#include <display/nr-arena-shape.h>
+#include <libnr/n-art-bpath.h>
+#include <libnr/nr-path.h>
+#include <libnr/nr-pixops.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-blit.h>
+#include <livarot/Path.h>
+#include <livarot/float-line.h>
+#include <livarot/int-line.h>
+#include <style.h>
+
+//int showRuns=0;
+void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS);
+
+static void nr_arena_shape_class_init (NRArenaShapeClass *klass);
+static void nr_arena_shape_init (NRArenaShape *shape);
+static void nr_arena_shape_finalize (NRObject *object);
+
+static NRArenaItem *nr_arena_shape_children (NRArenaItem *item);
+static void nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+static void nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child);
+static void nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
+
+static guint nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
+static unsigned int nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
+static guint nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
+static NRArenaItem *nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
+
+static NRArenaItemClass *shape_parent_class;
+
+NRType
+nr_arena_shape_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ARENA_ITEM,
+ "NRArenaShape",
+ sizeof (NRArenaShapeClass),
+ sizeof (NRArenaShape),
+ (void (*) (NRObjectClass *)) nr_arena_shape_class_init,
+ (void (*) (NRObject *)) nr_arena_shape_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_shape_class_init (NRArenaShapeClass *klass)
+{
+ NRObjectClass *object_class;
+ NRArenaItemClass *item_class;
+
+ object_class = (NRObjectClass *) klass;
+ item_class = (NRArenaItemClass *) klass;
+
+ shape_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
+
+ object_class->finalize = nr_arena_shape_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaShape>;
+
+ item_class->children = nr_arena_shape_children;
+ item_class->add_child = nr_arena_shape_add_child;
+ item_class->set_child_position = nr_arena_shape_set_child_position;
+ item_class->remove_child = nr_arena_shape_remove_child;
+ item_class->update = nr_arena_shape_update;
+ item_class->render = nr_arena_shape_render;
+ item_class->clip = nr_arena_shape_clip;
+ item_class->pick = nr_arena_shape_pick;
+}
+
+static void
+nr_arena_shape_init (NRArenaShape *shape)
+{
+ shape->curve = NULL;
+ shape->style = NULL;
+ shape->paintbox.x0 = shape->paintbox.y0 = 0.0F;
+ shape->paintbox.x1 = shape->paintbox.y1 = 256.0F;
+
+ nr_matrix_set_identity (&shape->ctm);
+ shape->fill_painter = NULL;
+ shape->stroke_painter = NULL;
+ shape->cached_fill = NULL;
+ shape->cached_stroke = NULL;
+ shape->fill_shp = NULL;
+ shape->stroke_shp = NULL;
+
+ shape->delayed_shp = false;
+
+ shape->approx_bbox.x0 = shape->approx_bbox.y0 = 0;
+ shape->approx_bbox.x1 = shape->approx_bbox.y1 = 0;
+ nr_matrix_set_identity(&shape->cached_fctm);
+ nr_matrix_set_identity(&shape->cached_sctm);
+
+ shape->markers = NULL;
+}
+
+static void
+nr_arena_shape_finalize (NRObject *object)
+{
+ NRArenaShape *shape = (NRArenaShape *) (object);
+
+ if (shape->fill_shp) delete shape->fill_shp;
+ if (shape->stroke_shp) delete shape->stroke_shp;
+ if (shape->cached_fill) delete shape->cached_fill;
+ if (shape->cached_stroke) delete shape->cached_stroke;
+ if (shape->fill_painter) sp_painter_free (shape->fill_painter);
+ if (shape->stroke_painter) sp_painter_free (shape->stroke_painter);
+
+ if (shape->style) sp_style_unref (shape->style);
+ if (shape->curve) sp_curve_unref (shape->curve);
+
+ ((NRObjectClass *) shape_parent_class)->finalize (object);
+}
+
+static NRArenaItem *
+nr_arena_shape_children (NRArenaItem *item)
+{
+ NRArenaShape *shape = (NRArenaShape *) item;
+
+ return shape->markers;
+}
+
+static void
+nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ NRArenaShape *shape = (NRArenaShape *) item;
+
+ if (!ref) {
+ shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers);
+ } else {
+ ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next);
+ }
+
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static void
+nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child)
+{
+ NRArenaShape *shape = (NRArenaShape *) item;
+
+ if (child->prev) {
+ nr_arena_item_detach_unref (item, child);
+ } else {
+ shape->markers = nr_arena_item_detach_unref (item, child);
+ }
+
+ nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static void
+nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
+{
+ NRArenaShape *shape = (NRArenaShape *) item;
+
+ if (child->prev) {
+ nr_arena_item_detach_unref (item, child);
+ } else {
+ shape->markers = nr_arena_item_detach_unref (item, child);
+ }
+
+ if (!ref) {
+ shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers);
+ } else {
+ ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next);
+ }
+
+ nr_arena_item_request_render (child);
+}
+
+void nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc);
+void nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc);
+void nr_arena_shape_add_bboxes(NRArenaShape* shape,NRRect &bbox);
+
+static guint
+nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset)
+{
+ NRRect bbox;
+
+ NRArenaShape *shape = NR_ARENA_SHAPE (item);
+
+ unsigned int beststate = NR_ARENA_ITEM_STATE_ALL;
+
+ unsigned int newstate;
+ for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
+ newstate = nr_arena_item_invoke_update (child, area, gc, state, reset);
+ beststate = beststate & newstate;
+ }
+
+ if (!(state & NR_ARENA_ITEM_STATE_RENDER)) {
+ /* We do not have to create rendering structures */
+ shape->ctm = gc->transform;
+ if (state & NR_ARENA_ITEM_STATE_BBOX) {
+ if (shape->curve) {
+ NRBPath bp;
+ /* fixme: */
+ bbox.x0 = bbox.y0 = NR_HUGE;
+ bbox.x1 = bbox.y1 = -NR_HUGE;
+ bp.path = shape->curve->bpath;
+ nr_path_matrix_bbox_union(&bp, gc->transform, &bbox);
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.9999F);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.9999F);
+ }
+ if (beststate & NR_ARENA_ITEM_STATE_BBOX) {
+ for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
+ nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox);
+ }
+ }
+ }
+ return (state | item->state);
+ }
+
+ shape->delayed_shp=true;
+ shape->ctm = gc->transform;
+ bbox.x0 = bbox.y0 = NR_HUGE;
+ bbox.x1 = bbox.y1 = -NR_HUGE;
+
+ if (shape->curve) {
+ NRBPath bp;
+ /* fixme: */
+ bbox.x0 = bbox.y0 = NR_HUGE;
+ bbox.x1 = bbox.y1 = -NR_HUGE;
+ bp.path = shape->curve->bpath;
+ nr_path_matrix_bbox_union(&bp, gc->transform, &bbox);
+ if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) {
+ float width, scale;
+ scale = NR_MATRIX_DF_EXPANSION (&gc->transform);
+ width = MAX (0.125, shape->_stroke.width * scale);
+ if ( fabs(shape->_stroke.width * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
+ bbox.x0-=width;
+ bbox.x1+=width;
+ bbox.y0-=width;
+ bbox.y1+=width;
+ }
+ // those pesky miters, now
+ float miterMax=width*shape->_stroke.mitre_limit;
+ if ( miterMax > 0.01 ) {
+ // grunt mode. we should compute the various miters instead (one for each point on the curve)
+ bbox.x0-=miterMax;
+ bbox.x1+=miterMax;
+ bbox.y0-=miterMax;
+ bbox.y1+=miterMax;
+ }
+ }
+ } else {
+ }
+ shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F);
+ shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F);
+ if ( area && nr_rect_l_test_intersect (area, &shape->approx_bbox) ) shape->delayed_shp=false;
+
+ /* Release state data */
+ if (TRUE || !nr_matrix_test_transform_equal (&gc->transform, &shape->ctm, NR_EPSILON)) {
+ /* Concept test */
+ if (shape->fill_shp) {
+ delete shape->fill_shp;
+ shape->fill_shp = NULL;
+ }
+ }
+ if (shape->stroke_shp) {
+ delete shape->stroke_shp;
+ shape->stroke_shp = NULL;
+ }
+ if (shape->fill_painter) {
+ sp_painter_free (shape->fill_painter);
+ shape->fill_painter = NULL;
+ }
+ if (shape->stroke_painter) {
+ sp_painter_free (shape->stroke_painter);
+ shape->stroke_painter = NULL;
+ }
+
+ if (!shape->curve || !shape->style) return NR_ARENA_ITEM_STATE_ALL;
+ if (sp_curve_is_empty (shape->curve)) return NR_ARENA_ITEM_STATE_ALL;
+ if ( ( shape->_fill.paint.type() == NRArenaShape::Paint::NONE ) &&
+ ( shape->_stroke.paint.type() == NRArenaShape::Paint::NONE ) )
+ {
+ return NR_ARENA_ITEM_STATE_ALL;
+ }
+
+ /* Build state data */
+ if ( shape->delayed_shp ) {
+ item->bbox=shape->approx_bbox;
+ } else {
+ nr_arena_shape_update_stroke(shape,gc);
+ nr_arena_shape_update_fill(shape,gc);
+
+ bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0;
+ nr_arena_shape_add_bboxes(shape,bbox);
+
+ shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F);
+ shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F);
+ }
+
+ if (nr_rect_d_test_empty (&bbox)) return NR_ARENA_ITEM_STATE_ALL;
+
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.0F);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.0F);
+ nr_arena_request_render_rect (item->arena, &item->bbox);
+
+ item->render_opacity = TRUE;
+ if ( shape->_fill.paint.type() == NRArenaShape::Paint::SERVER ) {
+ if (gc && gc->parent) {
+ shape->fill_painter = sp_paint_server_painter_new (shape->_fill.paint.server(),
+ NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform),
+ &shape->paintbox);
+ }
+ item->render_opacity = FALSE;
+ }
+ if ( shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER ) {
+ if (gc && gc->parent) {
+ shape->stroke_painter = sp_paint_server_painter_new (shape->_stroke.paint.server(),
+ NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform),
+ &shape->paintbox);
+ }
+ item->render_opacity = FALSE;
+ }
+ if ( item->render_opacity == TRUE &&
+ shape->_fill.paint.type() != NRArenaShape::Paint::NONE &&
+ shape->_stroke.paint.type() != NRArenaShape::Paint::NONE )
+ {
+ // don't merge item opacity with paint opacity if there is a stroke on the fill
+ item->render_opacity = FALSE;
+ }
+
+ if (beststate & NR_ARENA_ITEM_STATE_BBOX) {
+ for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
+ nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox);
+ }
+ }
+
+ return NR_ARENA_ITEM_STATE_ALL;
+}
+
+int matrix_is_isometry(NR::Matrix p) {
+ NR::Matrix tp;
+ // transposition
+ tp[0]=p[0];
+ tp[1]=p[2];
+ tp[2]=p[1];
+ tp[3]=p[3];
+ for(int i = 4; i < 6; i++) // shut valgrind up :)
+ tp[i] = p[i] = 0;
+ NR::Matrix isom = tp*p; // A^T * A = adjunct?
+ // Is the adjunct nearly an identity function?
+ if (isom.is_translation(0.01)) {
+ // the transformation is an isometry -> no need to recompute
+ // the uncrossed polygon
+ if ( p.det() < 0 )
+ return -1;
+ else
+ return 1;
+ }
+ return 0;
+}
+
+void
+nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc)
+{
+ shape->delayed_shp = false;
+ if ((shape->_fill.paint.type() != NRArenaShape::Paint::NONE) &&
+ ((shape->curve->end > 2) || (shape->curve->bpath[1].code == NR_CURVETO)) ) {
+ if (TRUE || !shape->fill_shp) {
+ NR::Matrix cached_to_new;
+ int isometry = 0;
+ if ( shape->cached_fill ) {
+ cached_to_new = shape->cached_fctm.inverse()*gc->transform;
+ isometry = matrix_is_isometry(cached_to_new);
+ }
+ if ( isometry == 0 ) {
+ if ( shape->cached_fill == NULL ) shape->cached_fill=new Shape;
+ shape->cached_fill->Reset();
+
+ Path* thePath=new Path;
+ Shape* theShape=new Shape;
+ {
+ NR::Matrix tempMat(gc->transform);
+ thePath->LoadArtBPath(shape->curve->bpath,tempMat,true);
+ }
+
+ thePath->Convert(1.0);
+
+ thePath->Fill(theShape, 0);
+
+ if ( shape->_fill.rule == NRArenaShape::EVEN_ODD ) {
+ shape->cached_fill->ConvertToShape(theShape, fill_oddEven);
+ // alternatively, this speeds up rendering of oddeven shapes but disables AA :(
+ //shape->cached_fill->Copy(theShape);
+ } else {
+ shape->cached_fill->ConvertToShape(theShape, fill_nonZero);
+ }
+ shape->cached_fctm=gc->transform;
+ delete theShape;
+ delete thePath;
+ if ( shape->fill_shp == NULL )
+ shape->fill_shp = new Shape;
+
+ shape->fill_shp->Copy(shape->cached_fill);
+
+ } else {
+
+ if ( shape->fill_shp == NULL )
+ shape->fill_shp=new Shape;
+ shape->fill_shp->Reset(shape->cached_fill->numberOfPoints(),
+ shape->cached_fill->numberOfEdges());
+ for (int i = 0; i < shape->cached_fill->numberOfPoints(); i++)
+ shape->fill_shp->AddPoint(shape->cached_fill->getPoint(i).x * cached_to_new);
+ if ( isometry == 1 ) {
+ for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++)
+ shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).st,
+ shape->cached_fill->getEdge(i).en);
+ } else if ( isometry == -1 ) { // need to flip poly.
+ for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++)
+ shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).en,
+ shape->cached_fill->getEdge(i).st);
+ }
+ shape->fill_shp->ForceToPolygon();
+ shape->fill_shp->needPointsSorting();
+ shape->fill_shp->needEdgesSorting();
+ }
+ }
+ }
+}
+
+void
+nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc)
+{
+ SPStyle* style = shape->style;
+
+ shape->delayed_shp = false;
+
+ const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform);
+
+ if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE ||
+ ((shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) &&
+ ( fabs(shape->_stroke.width * scale) > 0.01 ))) { // sinon c'est 0=oon veut pas de bord
+
+ float width = MAX (0.125, shape->_stroke.width * scale);
+ if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE)
+ width = 0.5; // 1 pixel wide, independent of zoom
+
+ NR::Matrix cached_to_new;
+
+ int isometry = 0;
+ if ( shape->cached_stroke ) {
+ cached_to_new = shape->cached_sctm.inverse() * gc->transform;
+ isometry = matrix_is_isometry(cached_to_new);
+ }
+
+ if ( isometry == 0 ) {
+ if ( shape->cached_stroke == NULL ) shape->cached_stroke=new Shape;
+ shape->cached_stroke->Reset();
+ Path* thePath = new Path;
+ Shape* theShape = new Shape;
+ {
+ NR::Matrix tempMat(gc->transform);
+ thePath->LoadArtBPath(shape->curve->bpath, tempMat, true);
+ }
+
+ if (NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE)
+ thePath->Convert(1.0);
+ else
+ thePath->Convert(4.0); // slightly rougher & faster
+
+ if (style->stroke_dash.n_dash && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) {
+ double dlen = 0.0;
+ for (int i = 0; i < style->stroke_dash.n_dash; i++) {
+ dlen += style->stroke_dash.dash[i] * scale;
+ }
+ if (dlen >= 1.0) {
+ NRVpathDash dash;
+ dash.offset = style->stroke_dash.offset * scale;
+ dash.n_dash = style->stroke_dash.n_dash;
+ dash.dash = g_new (double, dash.n_dash);
+ for (int i = 0; i < dash.n_dash; i++) {
+ dash.dash[i] = style->stroke_dash.dash[i] * scale;
+ }
+ int nbD=dash.n_dash;
+ float *dashs=(float*)malloc((nbD+1)*sizeof(float));
+ while ( dash.offset >= dlen ) dash.offset-=dlen;
+ dashs[0]=dash.dash[0];
+ for (int i=1; i<nbD; i++) {
+ dashs[i]=dashs[i-1]+dash.dash[i];
+ }
+ // modulo dlen
+ thePath->DashPolyline(0.0,0.0,dlen,nbD,dashs,true,dash.offset);
+ free(dashs);
+ g_free (dash.dash);
+ }
+ }
+ ButtType butt=butt_straight;
+ switch(shape->_stroke.cap) {
+ case NRArenaShape::BUTT_CAP:
+ butt = butt_straight;
+ break;
+ case NRArenaShape::ROUND_CAP:
+ butt = butt_round;
+ break;
+ case NRArenaShape::SQUARE_CAP:
+ butt = butt_square;
+ break;
+ }
+ JoinType join=join_straight;
+ switch(shape->_stroke.join) {
+ case NRArenaShape::MITRE_JOIN:
+ join = join_pointy;
+ break;
+ case NRArenaShape::ROUND_JOIN:
+ join = join_round;
+ break;
+ case NRArenaShape::BEVEL_JOIN:
+ join = join_straight;
+ break;
+ }
+
+ if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) {
+ butt = butt_straight;
+ join = join_straight;
+ }
+
+ thePath->Stroke(theShape, false, 0.5*width, join, butt,
+ 0.5*width*shape->_stroke.mitre_limit);
+
+
+ if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) {
+ // speeds it up, but uses evenodd for the stroke shape (which does not matter for 1-pixel wide outline)
+ shape->cached_stroke->Copy(theShape);
+ } else {
+ shape->cached_stroke->ConvertToShape(theShape, fill_nonZero);
+ }
+
+ shape->cached_sctm=gc->transform;
+ delete thePath;
+ delete theShape;
+ if ( shape->stroke_shp == NULL ) shape->stroke_shp=new Shape;
+
+ shape->stroke_shp->Copy(shape->cached_stroke);
+
+ } else {
+
+ if ( shape->stroke_shp == NULL )
+ shape->stroke_shp=new Shape;
+ shape->stroke_shp->Reset(shape->cached_stroke->numberOfPoints(), shape->cached_stroke->numberOfEdges());
+ for (int i = 0; i < shape->cached_stroke->numberOfPoints(); i++)
+ shape->stroke_shp->AddPoint(shape->cached_stroke->getPoint(i).x * cached_to_new);
+ if ( isometry == 1 ) {
+ for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++)
+ shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).st,
+ shape->cached_stroke->getEdge(i).en);
+ } else if ( isometry == -1 ) {
+ for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++)
+ shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).en,
+ shape->cached_stroke->getEdge(i).st);
+ }
+ shape->stroke_shp->ForceToPolygon();
+ shape->stroke_shp->needPointsSorting();
+ shape->stroke_shp->needEdgesSorting();
+ }
+ }
+}
+
+
+void
+nr_arena_shape_add_bboxes(NRArenaShape* shape, NRRect &bbox)
+{
+ if ( shape->stroke_shp ) {
+ shape->stroke_shp->CalcBBox();
+ shape->stroke_shp->leftX=floor(shape->stroke_shp->leftX);
+ shape->stroke_shp->rightX=ceil(shape->stroke_shp->rightX);
+ shape->stroke_shp->topY=floor(shape->stroke_shp->topY);
+ shape->stroke_shp->bottomY=ceil(shape->stroke_shp->bottomY);
+ if ( bbox.x0 >= bbox.x1 ) {
+ if ( shape->stroke_shp->leftX < shape->stroke_shp->rightX ) {
+ bbox.x0=shape->stroke_shp->leftX;
+ bbox.x1=shape->stroke_shp->rightX;
+ }
+ } else {
+ if ( shape->stroke_shp->leftX < bbox.x0 )
+ bbox.x0=shape->stroke_shp->leftX;
+ if ( shape->stroke_shp->rightX > bbox.x1 )
+ bbox.x1=shape->stroke_shp->rightX;
+ }
+ if ( bbox.y0 >= bbox.y1 ) {
+ if ( shape->stroke_shp->topY < shape->stroke_shp->bottomY ) {
+ bbox.y0=shape->stroke_shp->topY;
+ bbox.y1=shape->stroke_shp->bottomY;
+ }
+ } else {
+ if ( shape->stroke_shp->topY < bbox.y0 )
+ bbox.y0=shape->stroke_shp->topY;
+ if ( shape->stroke_shp->bottomY > bbox.y1 )
+ bbox.y1=shape->stroke_shp->bottomY;
+ }
+ }
+ if ( shape->fill_shp ) {
+ shape->fill_shp->CalcBBox();
+ shape->fill_shp->leftX=floor(shape->fill_shp->leftX);
+ shape->fill_shp->rightX=ceil(shape->fill_shp->rightX);
+ shape->fill_shp->topY=floor(shape->fill_shp->topY);
+ shape->fill_shp->bottomY=ceil(shape->fill_shp->bottomY);
+ if ( bbox.x0 >= bbox.x1 ) {
+ if ( shape->fill_shp->leftX < shape->fill_shp->rightX ) {
+ bbox.x0=shape->fill_shp->leftX;
+ bbox.x1=shape->fill_shp->rightX;
+ }
+ } else {
+ if ( shape->fill_shp->leftX < bbox.x0 ) bbox.x0=shape->fill_shp->leftX;
+ if ( shape->fill_shp->rightX > bbox.x1 ) bbox.x1=shape->fill_shp->rightX;
+ }
+ if ( bbox.y0 >= bbox.y1 ) {
+ if ( shape->fill_shp->topY < shape->fill_shp->bottomY ) {
+ bbox.y0=shape->fill_shp->topY;
+ bbox.y1=shape->fill_shp->bottomY;
+ }
+ } else {
+ if ( shape->fill_shp->topY < bbox.y0 ) bbox.y0=shape->fill_shp->topY;
+ if ( shape->fill_shp->bottomY > bbox.y1 ) bbox.y1=shape->fill_shp->bottomY;
+ }
+ }
+}
+static unsigned int
+nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags)
+{
+ NRArenaShape *shape = NR_ARENA_SHAPE (item);
+
+ if (!shape->curve) return item->state;
+ if (!shape->style) return item->state;
+
+ if ( shape->delayed_shp ) {
+ if ( nr_rect_l_test_intersect (area, &item->bbox) ) {
+ NRGC tempGC(NULL);
+ tempGC.transform=shape->ctm;
+ nr_arena_shape_update_stroke(shape,&tempGC);
+ nr_arena_shape_update_fill(shape,&tempGC);
+/* NRRect bbox;
+ bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0;
+ nr_arena_shape_add_bboxes(shape,bbox);
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.0F);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.0F);
+ shape->approx_bbox=item->bbox;*/
+ }
+ }
+
+ SPStyle const *style = shape->style;
+ if ( shape->fill_shp && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) {
+ NRPixBlock m;
+ guint32 rgba;
+
+ nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+ nr_pixblock_render_shape_mask_or (m,shape->fill_shp);
+ m.empty = FALSE;
+
+ if (shape->_fill.paint.type() == NRArenaShape::Paint::NONE) {
+ // do not render fill in any way
+ } else if (shape->_fill.paint.type() == NRArenaShape::Paint::COLOR) {
+ if ( item->render_opacity ) {
+ rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(),
+ shape->_fill.opacity *
+ SP_SCALE24_TO_FLOAT (style->opacity.value));
+ } else {
+ rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(),
+ shape->_fill.opacity);
+ }
+ nr_blit_pixblock_mask_rgba32 (pb, &m, rgba);
+ pb->empty = FALSE;
+ } else if (shape->_fill.paint.type() == NRArenaShape::Paint::SERVER) {
+ if (shape->fill_painter) {
+ nr_arena_render_paintserver_fill (pb, area, shape->fill_painter, shape->_fill.opacity, &m);
+ }
+ }
+
+ nr_pixblock_release (&m);
+ }
+
+ if ( shape->stroke_shp ) {
+ NRPixBlock m;
+ guint32 rgba;
+
+ nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+ nr_pixblock_render_shape_mask_or (m, shape->stroke_shp);
+ m.empty = FALSE;
+
+ if (shape->_stroke.paint.type() == NRArenaShape::Paint::COLOR ||
+ NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) {
+ if ( NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) {
+ rgba = NR_ARENA_ITEM(shape)->arena->outlinecolor;
+ } else if ( item->render_opacity ) {
+ rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(),
+ shape->_stroke.opacity *
+ SP_SCALE24_TO_FLOAT (style->opacity.value));
+ } else {
+ rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(),
+ shape->_stroke.opacity);
+ }
+ nr_blit_pixblock_mask_rgba32 (pb, &m, rgba);
+ pb->empty = FALSE;
+ } else if (shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER) {
+ if (shape->stroke_painter) {
+ nr_arena_render_paintserver_fill (pb, area, shape->stroke_painter, shape->_stroke.opacity, &m);
+ }
+ }
+
+ nr_pixblock_release (&m);
+ }
+
+ /* Just compose children into parent buffer */
+ for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
+ unsigned int ret;
+ ret = nr_arena_item_invoke_render (child, area, pb, flags);
+ if (ret & NR_ARENA_ITEM_STATE_INVALID) return ret;
+ }
+
+ return item->state;
+}
+
+static guint
+nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
+{
+ NRArenaShape *shape = NR_ARENA_SHAPE (item);
+
+ if (!shape->curve) return item->state;
+
+ if ( shape->delayed_shp ) {
+ if ( nr_rect_l_test_intersect (area, &item->bbox) ) {
+ NRGC tempGC(NULL);
+ tempGC.transform=shape->ctm;
+ nr_arena_shape_update_stroke(shape,&tempGC);
+ nr_arena_shape_update_fill(shape,&tempGC);
+ /* NRRect bbox;
+ bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0;
+ nr_arena_shape_add_bboxes(shape,bbox);
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.0F);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.0F);
+ shape->approx_bbox=item->bbox;*/
+ }
+ }
+
+ if ( shape->fill_shp ) {
+ NRPixBlock m;
+
+ /* fixme: We can OR in one step (Lauris) */
+ nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
+ nr_pixblock_render_shape_mask_or (m,shape->fill_shp);
+
+ for (int y = area->y0; y < area->y1; y++) {
+ unsigned char *s, *d;
+ s = NR_PIXBLOCK_PX (&m) + (y - area->y0) * m.rs;
+ d = NR_PIXBLOCK_PX (pb) + (y - area->y0) * pb->rs;
+ for (int x = area->x0; x < area->x1; x++) {
+ *d = NR_A7_NORMALIZED(*s,*d);
+ d ++;
+ s ++;
+ }
+ }
+ nr_pixblock_release (&m);
+ pb->empty = FALSE;
+ }
+
+ return item->state;
+}
+
+static NRArenaItem *
+nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int /*sticky*/)
+{
+ NRArenaShape *shape = NR_ARENA_SHAPE (item);
+
+ if (!shape->curve) return NULL;
+ if (!shape->style) return NULL;
+ if ( shape->delayed_shp ) {
+ NRRectL area;
+ area.x0=(int)floor(p[NR::X]);
+ area.x1=(int)ceil(p[NR::X]);
+ area.y0=(int)floor(p[NR::Y]);
+ area.y1=(int)ceil(p[NR::Y]);
+ int idelta = (int)ceil(delta) + 1;
+ // njh: inset rect
+ area.x0-=idelta;
+ area.x1+=idelta;
+ area.y0-=idelta;
+ area.y1+=idelta;
+ if ( nr_rect_l_test_intersect (&area, &item->bbox) ) {
+ NRGC tempGC(NULL);
+ tempGC.transform=shape->ctm;
+ nr_arena_shape_update_stroke(shape,&tempGC);
+ nr_arena_shape_update_fill(shape,&tempGC);
+ /* NRRect bbox;
+ bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0;
+ nr_arena_shape_add_bboxes(shape,bbox);
+ item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
+ item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
+ item->bbox.x1 = (gint32)(bbox.x1 + 1.0F);
+ item->bbox.y1 = (gint32)(bbox.y1 + 1.0F);
+ shape->approx_bbox=item->bbox;*/
+ }
+ }
+
+ if (item->state & NR_ARENA_ITEM_STATE_RENDER) {
+ if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) {
+ if (shape->fill_shp->PtWinding(p) > 0 ) return item;
+ }
+ if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) {
+ if (shape->stroke_shp->PtWinding(p) > 0 ) return item;
+ }
+ if (delta > 1e-3) {
+ if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) {
+ if (distanceLessThanOrEqual(shape->fill_shp, p, delta)) return item;
+ }
+ if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) {
+ if (distanceLessThanOrEqual(shape->stroke_shp, p, delta)) return item;
+ }
+ }
+ } else {
+ NRBPath bp;
+ bp.path = shape->curve->bpath;
+ double dist = NR_HUGE;
+ int wind = 0;
+ nr_path_matrix_point_bbox_wind_distance(&bp, shape->ctm, p, NULL, &wind, &dist, NR_EPSILON);
+ if (shape->_fill.paint.type() != NRArenaShape::Paint::NONE) {
+ if (!shape->style->fill_rule.computed) {
+ if (wind != 0) return item;
+ } else {
+ if (wind & 0x1) return item;
+ }
+ }
+ if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) {
+ /* fixme: We do not take stroke width into account here (Lauris) */
+ if (dist < delta) return item;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ *
+ * Requests a render of the shape, then if the shape is already a curve it
+ * unrefs the old curve; if the new curve is valid it creates a copy of the
+ * curve and adds it to the shape. Finally, it requests an update of the
+ * arena for the shape.
+ */
+void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve,bool justTrans)
+{
+ g_return_if_fail (shape != NULL);
+ g_return_if_fail (NR_IS_ARENA_SHAPE (shape));
+
+ if ( justTrans == false ) {
+ // dirty cached versions
+ if ( shape->cached_fill ) {
+ delete shape->cached_fill;
+ shape->cached_fill=NULL;
+ }
+ if ( shape->cached_stroke ) {
+ delete shape->cached_stroke;
+ shape->cached_stroke=NULL;
+ }
+ }
+
+ nr_arena_item_request_render (NR_ARENA_ITEM (shape));
+
+ if (shape->curve) {
+ sp_curve_unref (shape->curve);
+ shape->curve = NULL;
+ }
+
+ if (curve) {
+ shape->curve = curve;
+ sp_curve_ref (curve);
+ }
+
+ nr_arena_item_request_update (NR_ARENA_ITEM (shape), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void NRArenaShape::setFill(SPPaintServer *server) {
+ _fill.paint.set(server);
+ _invalidateCachedFill();
+}
+
+void NRArenaShape::setFill(SPColor const &color) {
+ _fill.paint.set(color);
+ _invalidateCachedFill();
+}
+
+void NRArenaShape::setFillOpacity(double opacity) {
+ _fill.opacity = opacity;
+ _invalidateCachedFill();
+}
+
+void NRArenaShape::setFillRule(NRArenaShape::FillRule rule) {
+ _fill.rule = rule;
+ _invalidateCachedFill();
+}
+
+void NRArenaShape::setStroke(SPPaintServer *server) {
+ _stroke.paint.set(server);
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setStroke(SPColor const &color) {
+ _stroke.paint.set(color);
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setStrokeOpacity(double opacity) {
+ _stroke.opacity = opacity;
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setStrokeWidth(double width) {
+ _stroke.width = width;
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setMitreLimit(double limit) {
+ _stroke.mitre_limit = limit;
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setLineCap(NRArenaShape::CapType cap) {
+ _stroke.cap = cap;
+ _invalidateCachedStroke();
+}
+
+void NRArenaShape::setLineJoin(NRArenaShape::JoinType join) {
+ _stroke.join = join;
+ _invalidateCachedStroke();
+}
+
+/** nr_arena_shape_set_style
+ *
+ * Unrefs any existing style and ref's to the given one, then requests an update of the arena
+ */
+void
+nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style)
+{
+ g_return_if_fail (shape != NULL);
+ g_return_if_fail (NR_IS_ARENA_SHAPE (shape));
+
+ if (style) sp_style_ref (style);
+ if (shape->style) sp_style_unref (shape->style);
+ shape->style = style;
+
+ switch (style->fill.type) {
+ case SP_PAINT_TYPE_NONE: {
+ shape->setFill(NULL);
+ break;
+ }
+ case SP_PAINT_TYPE_COLOR: {
+ shape->setFill(style->fill.value.color);
+ break;
+ }
+ case SP_PAINT_TYPE_PAINTSERVER: {
+ shape->setFill(style->fill.value.paint.server);
+ break;
+ }
+ default: {
+ g_assert_not_reached();
+ }
+ }
+ shape->setFillOpacity(SP_SCALE24_TO_FLOAT(style->fill_opacity.value));
+ switch (style->fill_rule.computed) {
+ case SP_WIND_RULE_EVENODD: {
+ shape->setFillRule(NRArenaShape::EVEN_ODD);
+ break;
+ }
+ case SP_WIND_RULE_NONZERO: {
+ shape->setFillRule(NRArenaShape::NONZERO);
+ break;
+ }
+ default: {
+ g_assert_not_reached();
+ }
+ }
+
+ switch (style->stroke.type) {
+ case SP_PAINT_TYPE_NONE: {
+ shape->setStroke(NULL);
+ break;
+ }
+ case SP_PAINT_TYPE_COLOR: {
+ shape->setStroke(style->stroke.value.color);
+ break;
+ }
+ case SP_PAINT_TYPE_PAINTSERVER: {
+ shape->setStroke(style->stroke.value.paint.server);
+ break;
+ }
+ default: {
+ g_assert_not_reached();
+ }
+ }
+ shape->setStrokeWidth(style->stroke_width.computed);
+ shape->setStrokeOpacity(SP_SCALE24_TO_FLOAT(style->stroke_opacity.value));
+ switch (style->stroke_linecap.computed) {
+ case SP_STROKE_LINECAP_ROUND: {
+ shape->setLineCap(NRArenaShape::ROUND_CAP);
+ break;
+ }
+ case SP_STROKE_LINECAP_SQUARE: {
+ shape->setLineCap(NRArenaShape::SQUARE_CAP);
+ break;
+ }
+ case SP_STROKE_LINECAP_BUTT: {
+ shape->setLineCap(NRArenaShape::BUTT_CAP);
+ break;
+ }
+ default: {
+ g_assert_not_reached();
+ }
+ }
+ switch (style->stroke_linejoin.computed) {
+ case SP_STROKE_LINEJOIN_ROUND: {
+ shape->setLineJoin(NRArenaShape::ROUND_JOIN);
+ break;
+ }
+ case SP_STROKE_LINEJOIN_BEVEL: {
+ shape->setLineJoin(NRArenaShape::BEVEL_JOIN);
+ break;
+ }
+ case SP_STROKE_LINEJOIN_MITER: {
+ shape->setLineJoin(NRArenaShape::MITRE_JOIN);
+ break;
+ }
+ default: {
+ g_assert_not_reached();
+ }
+ }
+ shape->setMitreLimit(style->stroke_miterlimit.value);
+
+ nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void
+nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox)
+{
+ g_return_if_fail (shape != NULL);
+ g_return_if_fail (NR_IS_ARENA_SHAPE (shape));
+ g_return_if_fail (pbox != NULL);
+
+ if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) {
+ shape->paintbox = *pbox;
+ } else {
+ /* fixme: We kill warning, although not sure what to do here (Lauris) */
+ shape->paintbox.x0 = shape->paintbox.y0 = 0.0F;
+ shape->paintbox.x1 = shape->paintbox.y1 = 256.0F;
+ }
+
+ nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void NRArenaShape::setPaintBox(NR::Rect const &pbox)
+{
+ paintbox.x0 = pbox.min()[NR::X];
+ paintbox.y0 = pbox.min()[NR::Y];
+ paintbox.x1 = pbox.max()[NR::X];
+ paintbox.y1 = pbox.max()[NR::Y];
+
+ nr_arena_item_request_update(this, NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+static void
+shape_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven)
+{
+ if ( st >= en ) return;
+ if ( vst < 0 ) vst=0;
+ if ( vst > 1 ) vst=1;
+ if ( ven < 0 ) ven=0;
+ if ( ven > 1 ) ven=1;
+ float sv=vst;
+ float dv=ven-vst;
+ int len=en-st;
+ unsigned char* d=(unsigned char*)dest.buffer;
+ d+=(st-dest.startPix);
+ if ( fabs(dv) < 0.001 ) {
+ if ( vst > 0.999 ) {
+ /* Simple copy */
+ while (len > 0) {
+ d[0] = 255;
+ d += 1;
+ len -= 1;
+ }
+ } else {
+ sv*=256;
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ while (len > 0) {
+ unsigned int da;
+ /* Draw */
+ da = NR_A7(c0_24,d[0]);
+ d[0] = NR_PREMUL_SINGLE(da);
+ d += 1;
+ len -= 1;
+ }
+ }
+ } else {
+ if ( en <= st+1 ) {
+ sv=0.5*(vst+ven);
+ sv*=256;
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ unsigned int da;
+ /* Draw */
+ da = NR_A7(c0_24,d[0]);
+ d[0] = NR_PREMUL_SINGLE(da);
+ } else {
+ dv/=len;
+ sv+=0.5*dv; // correction trapezoidale
+ sv*=16777216;
+ dv*=16777216;
+ int c0_24 = static_cast<int>(CLAMP(sv, 0, 16777216));
+ int s0_24 = static_cast<int>(dv);
+ while (len > 0) {
+ unsigned int ca, da;
+ /* Draw */
+ ca = c0_24 >> 16;
+ if ( ca > 255 ) ca=255;
+ da = NR_A7(ca,d[0]);
+ d[0] = NR_PREMUL_SINGLE(da);
+ d += 1;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+ }
+}
+
+void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS)
+{
+
+ theS->CalcBBox();
+ float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY;
+ int il,ir,it,ib;
+ il=(int)floor(l);
+ ir=(int)ceil(r);
+ it=(int)floor(t);
+ ib=(int)ceil(b);
+
+ if ( il >= m.area.x1 || ir <= m.area.x0 || it >= m.area.y1 || ib <= m.area.y0 ) return;
+ if ( il < m.area.x0 ) il=m.area.x0;
+ if ( it < m.area.y0 ) it=m.area.y0;
+ if ( ir > m.area.x1 ) ir=m.area.x1;
+ if ( ib > m.area.y1 ) ib=m.area.y1;
+
+ // version par FloatLigne
+ int curPt;
+ float curY;
+ theS->BeginQuickRaster(curY, curPt);
+
+ FloatLigne* theI=new FloatLigne();
+ IntLigne* theIL=new IntLigne();
+
+ theS->DirectQuickScan(curY,curPt,(float)(it),true,1.0);
+
+ char* mdata=(char*)m.data.px;
+ if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p;
+ uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0))));
+ for (int y=it;y<ib;y++) {
+ theI->Reset();
+ theS->QuickScan(curY,curPt,((float)(y+1)),theI,1.0);
+ theI->Flatten();
+ theIL->Copy(theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,NULL,shape_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+m.rs));
+ }
+ theS->EndQuickRaster();
+ delete theI;
+ delete theIL;
+
+ /* // version par BitLigne
+ int curPt;
+ float curY;
+ theS->BeginRaster(curY,curPt,1.0);
+
+ BitLigne* theI[4];
+ for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir);
+ IntLigne* theIL=new IntLigne();
+
+ theS->Scan(curY,curPt,(float)(it),0.25);
+
+ char* mdata=(char*)m.data.px;
+ if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p;
+ uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0))));
+ for (int y=it;y<ib;y++) {
+ for (int i=0;i<4;i++) theI[i]->Reset();
+ if ( y&0x00000003 ) {
+ theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25);
+ theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25);
+ theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25);
+ theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25);
+ } else {
+ theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25);
+ theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25);
+ theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25);
+ theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25);
+ }
+ theIL->Copy(4,theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,NULL,shape_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+m.rs));
+ }
+ theS->EndRaster();
+ for (int i=0;i<4;i++) delete theI[i];
+ delete theIL;*/
+
+/* // version par BitLigne directe
+ int curPt;
+ float curY;
+ theS->BeginQuickRaster(curY,curPt,1.0);
+
+ BitLigne* theI[4];
+ for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir);
+ IntLigne* theIL=new IntLigne();
+
+ theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25);
+
+ char* mdata=(char*)m.data.px;
+ if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p;
+ uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0))));
+ for (int y=it;y<ib;y++) {
+ for (int i=0;i<4;i++) theI[i]->Reset();
+ if ( y&0x00000003 ) {
+ theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25);
+ } else {
+ theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25);
+ theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25);
+ }
+ theIL->Copy(4,theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,NULL,shape_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+m.rs));
+ }
+ theS->EndQuickRaster();
+ for (int i=0;i<4;i++) delete theI[i];
+ delete theIL;*/
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/display/nr-arena-shape.h b/src/display/nr-arena-shape.h
new file mode 100644
index 000000000..f7991bf4d
--- /dev/null
+++ b/src/display/nr-arena-shape.h
@@ -0,0 +1,213 @@
+#ifndef __NR_ARENA_SHAPE_H__
+#define __NR_ARENA_SHAPE_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, read the file 'COPYING' for more information
+ */
+
+#define NR_TYPE_ARENA_SHAPE (nr_arena_shape_get_type ())
+#define NR_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_SHAPE, NRArenaShape))
+#define NR_IS_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_SHAPE))
+
+//#include <libnr/nr-svp.h>
+
+#include "display/curve.h"
+#include "display/canvas-bpath.h"
+#include "forward.h"
+#include "sp-paint-server.h"
+#include "nr-arena-item.h"
+
+#include "../color.h"
+
+#include "../livarot/Shape.h"
+
+NRType nr_arena_shape_get_type (void);
+
+struct NRArenaShape : public NRArenaItem {
+ class Paint {
+ public:
+ enum Type {
+ NONE,
+ COLOR,
+ SERVER
+ };
+
+ Paint() : _type(NONE), _server(NULL) {
+ sp_color_set_rgb_rgba32(&_color, 0);
+ }
+ Paint(Paint const &p) { _assign(p); }
+ ~Paint() { clear(); }
+
+ Type type() const { return _type; }
+ SPPaintServer *server() const { return _server; }
+ SPColor const &color() const { return _color; }
+
+ Paint &operator=(Paint const &p) {
+ set(p);
+ return *this;
+ }
+
+ void set(Paint const &p) {
+ clear();
+ _assign(p);
+ }
+ void set(SPColor const &color) {
+ clear();
+ _type = COLOR;
+ sp_color_copy(&_color, &color);
+ }
+ void set(SPPaintServer *server) {
+ clear();
+ if (server) {
+ _type = SERVER;
+ _server = server;
+ sp_object_ref(_server, NULL);
+ }
+ }
+ void clear() {
+ if ( _type == SERVER ) {
+ sp_object_unref(_server, NULL);
+ _server = NULL;
+ }
+ _type = NONE;
+ }
+
+ private:
+ Type _type;
+ SPColor _color;
+ SPPaintServer *_server;
+
+ void _assign(Paint const &p) {
+ _type = p._type;
+ _server = p._server;
+ sp_color_copy(&_color, &p._color);
+ if (_server) {
+ sp_object_ref(_server, NULL);
+ }
+ }
+ };
+
+ enum FillRule {
+ EVEN_ODD,
+ NONZERO
+ };
+
+ enum CapType {
+ ROUND_CAP,
+ SQUARE_CAP,
+ BUTT_CAP
+ };
+
+ enum JoinType {
+ ROUND_JOIN,
+ BEVEL_JOIN,
+ MITRE_JOIN
+ };
+
+ /* Shape data */
+ SPCurve *curve;
+ SPStyle *style;
+ NRRect paintbox;
+ /* State data */
+ NR::Matrix ctm;
+
+ SPPainter *fill_painter;
+ SPPainter *stroke_painter;
+ // the 2 cached polygons, for rasterizations uses
+ Shape *fill_shp;
+ Shape *stroke_shp;
+ // delayed_shp=true means the *_shp polygons are not computed yet
+ // they'll be computed on demand in *_render(), *_pick() or *_clip()
+ // the goal is to not uncross polygons that are outside the viewing region
+ bool delayed_shp;
+ // approximate bounding box, for the case when the polygons have been delayed
+ NRRectL approx_bbox;
+ // cache for transformations: cached_fill and cached_stroke are
+ // polygons computed for the cached_fctm and cache_sctm respectively
+ // when the transformation changes interactively (tracked by the
+ // SP_OBJECT_USER_MODIFIED_FLAG_B), we check if it's an isometry wrt
+ // the cached ctm. if it's an isometry, just apply it to the cached
+ // polygon to get the *_shp polygon. Otherwise, recompute so this
+ // works fine for translation and rotation, but not scaling and
+ // skewing
+ NR::Matrix cached_fctm;
+ NR::Matrix cached_sctm;
+
+ Shape *cached_fill;
+ Shape *cached_stroke;
+ /* Markers */
+ NRArenaItem *markers;
+
+ static NRArenaShape *create(NRArena *arena) {
+ NRArenaShape *obj=reinterpret_cast<NRArenaShape *>(nr_object_new(NR_TYPE_ARENA_SHAPE));
+ obj->init(arena);
+ return obj;
+ }
+
+ void setFill(SPPaintServer *server);
+ void setFill(SPColor const &color);
+ void setFillOpacity(double opacity);
+ void setFillRule(FillRule rule);
+
+ void setStroke(SPPaintServer *server);
+ void setStroke(SPColor const &color);
+ void setStrokeOpacity(double opacity);
+ void setStrokeWidth(double width);
+ void setLineCap(CapType cap);
+ void setLineJoin(JoinType join);
+ void setMitreLimit(double limit);
+
+ void setPaintBox(NR::Rect const &pbox);
+
+ void _invalidateCachedFill() {
+ if (cached_fill) {
+ delete cached_fill;
+ cached_fill = NULL;
+ }
+ }
+ void _invalidateCachedStroke() {
+ if (cached_stroke) {
+ delete cached_stroke;
+ cached_stroke = NULL;
+ }
+ }
+
+ struct Style {
+ Style() : opacity(0.0) {}
+ Paint paint;
+ double opacity;
+ };
+ struct FillStyle : public Style {
+ FillStyle() : rule(EVEN_ODD) {}
+ FillRule rule;
+ } _fill;
+ struct StrokeStyle : public Style {
+ StrokeStyle()
+ : cap(ROUND_CAP), join(ROUND_JOIN),
+ width(0.0), mitre_limit(0.0)
+ {}
+
+ CapType cap;
+ JoinType join;
+ double width;
+ double mitre_limit;
+ } _stroke;
+};
+
+struct NRArenaShapeClass {
+ NRArenaItemClass parent_class;
+};
+
+void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve, bool justTrans);
+void nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style);
+void nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox);
+
+#endif
diff --git a/src/display/nr-arena.cpp b/src/display/nr-arena.cpp
new file mode 100644
index 000000000..f54bfbb90
--- /dev/null
+++ b/src/display/nr-arena.cpp
@@ -0,0 +1,131 @@
+#define __NR_ARENA_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+#include "nr-arena-item.h"
+#include "nr-arena.h"
+#include <libnr/nr-blit.h>
+
+static void nr_arena_class_init (NRArenaClass *klass);
+static void nr_arena_init (NRArena *arena);
+static void nr_arena_finalize (NRObject *object);
+
+static NRActiveObjectClass *parent_class;
+
+NRType
+nr_arena_get_type (void)
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_ACTIVE_OBJECT,
+ "NRArena",
+ sizeof (NRArenaClass),
+ sizeof (NRArena),
+ (void (*) (NRObjectClass *)) nr_arena_class_init,
+ (void (*) (NRObject *)) nr_arena_init);
+ }
+ return type;
+}
+
+static void
+nr_arena_class_init (NRArenaClass *klass)
+{
+ NRObjectClass *object_class = (NRObjectClass *) klass;
+
+ parent_class = (NRActiveObjectClass *) (((NRObjectClass *) klass)->parent);
+
+ object_class->finalize = nr_arena_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRArena>;
+}
+
+static void
+nr_arena_init (NRArena *arena)
+{
+ arena->delta = 0; // to be set by desktop from prefs
+ arena->rendermode = RENDERMODE_NORMAL; // default is normal render
+ arena->outlinecolor = 0xff; // black; to be set by desktop from bg color
+}
+
+static void
+nr_arena_finalize (NRObject *object)
+{
+ ((NRObjectClass *) (parent_class))->finalize (object);
+}
+
+void
+nr_arena_request_update (NRArena *arena, NRArenaItem *item)
+{
+ NRActiveObject *aobject = (NRActiveObject *) arena;
+
+ nr_return_if_fail (arena != NULL);
+ nr_return_if_fail (NR_IS_ARENA (arena));
+ nr_return_if_fail (item != NULL);
+ nr_return_if_fail (NR_IS_ARENA_ITEM (item));
+
+ if (aobject->callbacks) {
+ for (unsigned int i = 0; i < aobject->callbacks->length; i++) {
+ NRObjectListener *listener = aobject->callbacks->listeners + i;
+ NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector;
+ if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_update) {
+ avector->request_update (arena, item, listener->data);
+ }
+ }
+ }
+}
+
+void
+nr_arena_request_render_rect (NRArena *arena, NRRectL *area)
+{
+ NRActiveObject *aobject = (NRActiveObject *) arena;
+
+ nr_return_if_fail (arena != NULL);
+ nr_return_if_fail (NR_IS_ARENA (arena));
+ nr_return_if_fail (area != NULL);
+
+ if (aobject->callbacks && area && !nr_rect_l_test_empty (area)) {
+ for (unsigned int i = 0; i < aobject->callbacks->length; i++) {
+ NRObjectListener *listener = aobject->callbacks->listeners + i;
+ NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector;
+ if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_render) {
+ avector->request_render (arena, area, listener->data);
+ }
+ }
+ }
+}
+
+void
+nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask)
+{
+ NRPixBlock cb, cb_opa;
+ nr_pixblock_setup_fast (&cb, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE);
+ nr_pixblock_setup_fast (&cb_opa, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE);
+
+ /* Need separate gradient buffer (lauris)*/
+ // do the filling
+ painter->fill (painter, &cb);
+ cb.empty = FALSE;
+
+ // do the fill-opacity and mask composite
+ if (opacity < 1.0) {
+ nr_blit_pixblock_pixblock_alpha (&cb_opa, &cb, (int) floor (255 * opacity));
+ cb_opa.empty = FALSE;
+ nr_blit_pixblock_pixblock_mask (pb, &cb_opa, mask);
+ } else {
+ nr_blit_pixblock_pixblock_mask (pb, &cb, mask);
+ }
+
+ pb->empty = FALSE;
+
+ nr_pixblock_release (&cb);
+ nr_pixblock_release (&cb_opa);
+}
diff --git a/src/display/nr-arena.h b/src/display/nr-arena.h
new file mode 100644
index 000000000..245ce14db
--- /dev/null
+++ b/src/display/nr-arena.h
@@ -0,0 +1,57 @@
+#ifndef __NR_ARENA_H__
+#define __NR_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, read the file 'COPYING' for more information
+ */
+
+#define NR_TYPE_ARENA (nr_arena_get_type ())
+#define NR_ARENA(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA, NRArena))
+#define NR_IS_ARENA(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA))
+
+#include <libnr/nr-forward.h>
+#include <libnr/nr-object.h>
+#include "nr-arena-forward.h"
+#include "sp-paint-server.h"
+
+NRType nr_arena_get_type (void);
+
+struct NRArenaEventVector {
+ NRObjectEventVector parent;
+ void (* request_update) (NRArena *arena, NRArenaItem *item, void *data);
+ void (* request_render) (NRArena *arena, NRRectL *area, void *data);
+};
+
+enum {
+ RENDERMODE_NORMAL,
+ RENDERMODE_NOAA,
+ RENDERMODE_OUTLINE
+};
+
+struct NRArena : public NRActiveObject {
+ static NRArena *create() {
+ return reinterpret_cast<NRArena *>(nr_object_new(NR_TYPE_ARENA));
+ }
+
+ double delta;
+ int rendermode;
+ guint32 outlinecolor;
+};
+
+struct NRArenaClass : public NRActiveObjectClass {
+};
+
+void nr_arena_request_update (NRArena *arena, NRArenaItem *item);
+void nr_arena_request_render_rect (NRArena *arena, NRRectL *area);
+
+void nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask);
+
+#endif
diff --git a/src/display/nr-gradient-gpl.cpp b/src/display/nr-gradient-gpl.cpp
new file mode 100644
index 000000000..54a33cfdb
--- /dev/null
+++ b/src/display/nr-gradient-gpl.cpp
@@ -0,0 +1,306 @@
+#define __NR_GRADIENT_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <libnr/nr-pixops.h>
+#include <libnr/nr-pixblock-pixel.h>
+
+#include "nr-gradient-gpl.h"
+
+#define noNR_USE_GENERIC_RENDERER
+
+#define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1)
+#define NRG_2MASK ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1)
+
+static void nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
+static void nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs);
+static void nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs);
+static void nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs);
+static void nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb);
+
+NRRenderer *
+nr_lgradient_renderer_setup (NRLGradientRenderer *lgr,
+ const unsigned char *cv,
+ unsigned int spread,
+ const NRMatrix *gs2px,
+ float x0, float y0,
+ float x1, float y1)
+{
+ NRMatrix n2gs, n2px, px2n;
+
+ lgr->renderer.render = nr_lgradient_render_block;
+
+ lgr->vector = cv;
+ lgr->spread = spread;
+
+ n2gs.c[0] = x1 - x0;
+ n2gs.c[1] = y1 - y0;
+ n2gs.c[2] = y1 - y0;
+ n2gs.c[3] = x0 - x1;
+ n2gs.c[4] = x0;
+ n2gs.c[5] = y0;
+
+ nr_matrix_multiply (&n2px, &n2gs, gs2px);
+ nr_matrix_invert (&px2n, &n2px);
+
+ lgr->x0 = n2px.c[4] - 0.5;
+ lgr->y0 = n2px.c[5] - 0.5;
+ lgr->dx = px2n.c[0] * NR_GRADIENT_VECTOR_LENGTH;
+ lgr->dy = px2n.c[2] * NR_GRADIENT_VECTOR_LENGTH;
+
+ return (NRRenderer *) lgr;
+}
+
+static void
+nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
+{
+ NRLGradientRenderer *lgr;
+ int width, height;
+
+ lgr = (NRLGradientRenderer *) r;
+
+ width = pb->area.x1 - pb->area.x0;
+ height = pb->area.y1 - pb->area.y0;
+
+#ifdef NR_USE_GENERIC_RENDERER
+ nr_lgradient_render_generic (lgr, pb);
+#else
+ if (pb->empty) {
+ switch (pb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ nr_lgradient_render_generic (lgr, pb);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ nr_lgradient_render_generic (lgr, pb);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ nr_lgradient_render_R8G8B8A8N_EMPTY (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ nr_lgradient_render_generic (lgr, pb);
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (pb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ nr_lgradient_render_generic (lgr, pb);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ nr_lgradient_render_R8G8B8 (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ nr_lgradient_render_R8G8B8A8N (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ nr_lgradient_render_generic (lgr, pb);
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+}
+
+static void
+nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs)
+{
+ int x, y;
+ double pos;
+
+ for (y = 0; y < height; y++) {
+ const unsigned char *s;
+ unsigned char *d;
+ int idx;
+ d = px + y * rs;
+ pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
+ if (lgr->spread == NR_GRADIENT_SPREAD_PAD) {
+ for (x = 0; x < width; x++) {
+ idx = (int) CLAMP (pos, 0, (double) NRG_MASK);
+ s = lgr->vector + 4 * idx;
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ d += 4;
+ pos += lgr->dx;
+ }
+ } else if (lgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
+ for (x = 0; x < width; x++) {
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ s = lgr->vector + 4 * idx;
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ d += 4;
+ pos += lgr->dx;
+ }
+ } else {
+ for (x = 0; x < width; x++) {
+ idx = (int) ((long long) pos & NRG_MASK);
+ s = lgr->vector + 4 * idx;
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ d += 4;
+ pos += lgr->dx;
+ }
+ }
+ }
+}
+
+static void
+nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs)
+{
+ int x, y;
+ unsigned char *d;
+ double pos;
+
+ for (y = 0; y < height; y++) {
+ d = px + y * rs;
+ pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
+ for (x = 0; x < width; x++) {
+ int idx;
+ unsigned int ca;
+ const unsigned char *s;
+ switch (lgr->spread) {
+ case NR_GRADIENT_SPREAD_PAD:
+ idx = (int) CLAMP (pos, 0, (double) NRG_MASK);
+ break;
+ case NR_GRADIENT_SPREAD_REFLECT:
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ break;
+ case NR_GRADIENT_SPREAD_REPEAT:
+ idx = (int) ((long long) pos & NRG_MASK);
+ break;
+ default:
+ idx = 0;
+ break;
+ }
+ /* Full composition */
+ s = lgr->vector + 4 * idx;
+ if (s[3] == 255) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = 255;
+ } else if (s[3] != 0) {
+ ca = NR_A7(s[3],d[3]);
+ d[0] = NR_COMPOSENNN_A7 (s[0], s[3], d[0], d[3], ca);
+ d[1] = NR_COMPOSENNN_A7 (s[1], s[3], d[1], d[3], ca);
+ d[2] = NR_COMPOSENNN_A7 (s[2], s[3], d[2], d[3], ca);
+ d[3] = NR_PREMUL_SINGLE(ca);
+ }
+ d += 4;
+ pos += lgr->dx;
+ }
+ }
+}
+
+static void
+nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs)
+{
+ int x, y;
+ unsigned char *d;
+ double pos;
+
+ for (y = 0; y < height; y++) {
+ d = px + y * rs;
+ pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
+ for (x = 0; x < width; x++) {
+ int idx;
+ const unsigned char *s;
+ switch (lgr->spread) {
+ case NR_GRADIENT_SPREAD_PAD:
+ idx = (int) CLAMP (pos, 0, (double) NRG_MASK);
+ break;
+ case NR_GRADIENT_SPREAD_REFLECT:
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ break;
+ case NR_GRADIENT_SPREAD_REPEAT:
+ idx = (int) ((long long) pos & NRG_MASK);
+ break;
+ default:
+ idx = 0;
+ break;
+ }
+ /* Full composition */
+ s = lgr->vector + 4 * idx;
+ d[0] = NR_COMPOSEN11 (s[0], s[3], d[0]);
+ d[1] = NR_COMPOSEN11 (s[1], s[3], d[1]);
+ d[2] = NR_COMPOSEN11 (s[2], s[3], d[2]);
+ d += 3;
+ pos += lgr->dx;
+ }
+ }
+}
+
+static void
+nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb)
+{
+ int x, y;
+ unsigned char *d;
+ double pos;
+ int bpp;
+ NRPixBlock spb;
+ int x0, y0, width, height, rs;
+
+ x0 = pb->area.x0;
+ y0 = pb->area.y0;
+ width = pb->area.x1 - pb->area.x0;
+ height = pb->area.y1 - pb->area.y0;
+ rs = pb->rs;
+
+ nr_pixblock_setup_extern (&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
+ (unsigned char *) lgr->vector,
+ 4 * NR_GRADIENT_VECTOR_LENGTH,
+ 0, 0);
+ bpp = (pb->mode == NR_PIXBLOCK_MODE_A8) ? 1 : (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4;
+
+ for (y = 0; y < height; y++) {
+ d = NR_PIXBLOCK_PX (pb) + y * rs;
+ pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
+ for (x = 0; x < width; x++) {
+ int idx;
+ const unsigned char *s;
+ switch (lgr->spread) {
+ case NR_GRADIENT_SPREAD_PAD:
+ idx = (int) CLAMP (pos, 0, (double) NRG_MASK);
+ break;
+ case NR_GRADIENT_SPREAD_REFLECT:
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ break;
+ case NR_GRADIENT_SPREAD_REPEAT:
+ idx = (int) ((long long) pos & NRG_MASK);
+ break;
+ default:
+ idx = 0;
+ break;
+ }
+ s = lgr->vector + 4 * idx;
+ nr_compose_pixblock_pixblock_pixel (pb, d, &spb, s);
+ d += bpp;
+ pos += lgr->dx;
+ }
+ }
+
+ nr_pixblock_release (&spb);
+}
+
diff --git a/src/display/nr-gradient-gpl.h b/src/display/nr-gradient-gpl.h
new file mode 100644
index 000000000..db7d9bb54
--- /dev/null
+++ b/src/display/nr-gradient-gpl.h
@@ -0,0 +1,41 @@
+#ifndef __NR_GRADIENT_GPL_H__
+#define __NR_GRADIENT_GPL_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+/*
+ * Here is GPL code, unlike other libnr wihich is public domain
+ */
+
+#include <libnr/nr-gradient.h>
+
+/* Linear */
+
+struct NRLGradientRenderer {
+ NRRenderer renderer;
+ const unsigned char *vector;
+ unsigned int spread;
+ double x0, y0;
+ double dx, dy;
+};
+
+NRRenderer *nr_lgradient_renderer_setup (NRLGradientRenderer *lgr,
+ const unsigned char *cv,
+ unsigned int spread,
+ const NRMatrix *gs2px,
+ float x0, float y0,
+ float x1, float y1);
+
+
+
+#endif
diff --git a/src/display/nr-plain-stuff-gdk.cpp b/src/display/nr-plain-stuff-gdk.cpp
new file mode 100644
index 000000000..d5b43f4ea
--- /dev/null
+++ b/src/display/nr-plain-stuff-gdk.cpp
@@ -0,0 +1,46 @@
+#define __NR_PLAIN_STUFF_GDK_C__
+
+/*
+ * Miscellaneous simple rendering utilities
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ */
+
+#include <libnr/nr-pixblock-pattern.h>
+#include "nr-plain-stuff.h"
+#include "nr-plain-stuff-gdk.h"
+
+void
+nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba)
+{
+ NRPixBlock pb;
+
+ nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, w, h, FALSE);
+
+ nr_render_rgba32_rgb (NR_PIXBLOCK_PX (&pb), w, h, pb.rs, x, y, rgba);
+ gdk_draw_rgb_image (drawable, gc, x, y, w, h, GDK_RGB_DITHER_MAX, NR_PIXBLOCK_PX (&pb), pb.rs);
+
+ nr_pixblock_release (&pb);
+}
+
+void
+nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h)
+{
+ for (gint yy = y; yy < y + h; yy += 64) {
+ for (gint xx = x; xx < x + w; xx += 64) {
+ NRPixBlock pb;
+ gint ex = MIN (xx + 64, x + w);
+ gint ey = MIN (yy + 64, y + h);
+ nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8, xx, yy, ex, ey, FALSE);
+ nr_pixblock_render_gray_noise (&pb, NULL);
+ gdk_draw_rgb_image (drawable, gc, xx, yy, ex - xx, ey - yy, GDK_RGB_DITHER_NONE, NR_PIXBLOCK_PX (&pb), pb.rs);
+ nr_pixblock_release (&pb);
+ }
+ }
+}
+
diff --git a/src/display/nr-plain-stuff-gdk.h b/src/display/nr-plain-stuff-gdk.h
new file mode 100644
index 000000000..7c83792a8
--- /dev/null
+++ b/src/display/nr-plain-stuff-gdk.h
@@ -0,0 +1,32 @@
+#ifndef __NR_PLAIN_STUFF_GDK_H__
+#define __NR_PLAIN_STUFF_GDK_H__
+
+/*
+ * Miscellaneous simple rendering utilities
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ */
+
+#include <gdk/gdk.h>
+
+void nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba);
+
+void nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h);
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/nr-plain-stuff.cpp b/src/display/nr-plain-stuff.cpp
new file mode 100644
index 000000000..af6e002ec
--- /dev/null
+++ b/src/display/nr-plain-stuff.cpp
@@ -0,0 +1,94 @@
+#define __NR_PLAIN_STUFF_C__
+
+/*
+ * Miscellaneous simple rendering utilities
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ */
+
+#include <glib/gmessages.h>
+#include <libnr/nr-pixops.h>
+#include "nr-plain-stuff.h"
+
+#define NR_DEFAULT_CHECKERSIZEP2 2
+#define NR_DEFAULT_CHECKERCOLOR0 0xbfbfbfff
+#define NR_DEFAULT_CHECKERCOLOR1 0x808080ff
+
+void
+nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff)
+{
+ g_return_if_fail (px != NULL);
+
+ nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, NR_DEFAULT_CHECKERCOLOR0, NR_DEFAULT_CHECKERCOLOR1, NR_DEFAULT_CHECKERSIZEP2);
+}
+
+void
+nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2)
+{
+ gint x, y, m;
+ guint r0, g0, b0;
+ guint r1, g1, b1;
+
+ g_return_if_fail (px != NULL);
+ g_return_if_fail (sizep2 >= 0);
+ g_return_if_fail (sizep2 <= 8);
+
+ xoff &= 0x1ff;
+ yoff &= 0x1ff;
+ m = 0x1 << sizep2;
+ r0 = NR_RGBA32_R (c0);
+ g0 = NR_RGBA32_G (c0);
+ b0 = NR_RGBA32_B (c0);
+ r1 = NR_RGBA32_R (c1);
+ g1 = NR_RGBA32_G (c1);
+ b1 = NR_RGBA32_B (c1);
+
+ for (y = 0; y < h; y++) {
+ guchar *p;
+ p = px;
+ for (x = 0; x < w; x++) {
+ if (((x + xoff) ^ (y + yoff)) & m) {
+ *p++ = r0;
+ *p++ = g0;
+ *p++ = b0;
+ } else {
+ *p++ = r1;
+ *p++ = g1;
+ *p++ = b1;
+ }
+ }
+ px += rs;
+ }
+}
+
+void
+nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c)
+{
+ guint32 c0, c1;
+ gint a, r, g, b, cr, cg, cb;
+
+ g_return_if_fail (px != NULL);
+
+ r = NR_RGBA32_R (c);
+ g = NR_RGBA32_G (c);
+ b = NR_RGBA32_B (c);
+ a = NR_RGBA32_A (c);
+
+ cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR0));
+ cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR0));
+ cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR0));
+ c0 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff;
+
+ cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR1));
+ cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR1));
+ cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR1));
+ c1 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff;
+
+ nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, c0, c1, NR_DEFAULT_CHECKERSIZEP2);
+}
+
diff --git a/src/display/nr-plain-stuff.h b/src/display/nr-plain-stuff.h
new file mode 100644
index 000000000..c568f38a6
--- /dev/null
+++ b/src/display/nr-plain-stuff.h
@@ -0,0 +1,33 @@
+#ifndef __NR_PLAIN_STUFF_H__
+#define __NR_PLAIN_STUFF_H__
+
+/*
+ * Miscellaneous simple rendering utilities
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * Released under GNU GPL
+ */
+
+#include <glib/gtypes.h>
+
+void nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff);
+void nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2);
+
+void nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c);
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sodipodi-ctrl.cpp b/src/display/sodipodi-ctrl.cpp
new file mode 100644
index 000000000..71786fd96
--- /dev/null
+++ b/src/display/sodipodi-ctrl.cpp
@@ -0,0 +1,494 @@
+#define INKSCAPE_CTRL_C
+
+/*
+ * SPCtrl
+ *
+ * We render it by hand to reduce allocing/freeing svps & to get clean
+ * (non-aa) images
+ *
+ */
+
+#include "sp-canvas-util.h"
+#include "display-forward.h"
+#include "sodipodi-ctrl.h"
+
+enum {
+ ARG_0,
+ ARG_SHAPE,
+ ARG_MODE,
+ ARG_ANCHOR,
+ ARG_SIZE,
+ ARG_FILLED,
+ ARG_FILL_COLOR,
+ ARG_STROKED,
+ ARG_STROKE_COLOR,
+ ARG_PIXBUF
+};
+
+
+static void sp_ctrl_class_init (SPCtrlClass *klass);
+static void sp_ctrl_init (SPCtrl *ctrl);
+static void sp_ctrl_destroy (GtkObject *object);
+static void sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+
+static void sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static double sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+
+
+static SPCanvasItemClass *parent_class;
+
+GtkType
+sp_ctrl_get_type (void)
+{
+ static GtkType ctrl_type = 0;
+ if (!ctrl_type) {
+ static const GTypeInfo ctrl_info = {
+ sizeof (SPCtrlClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) sp_ctrl_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (SPCtrl),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) sp_ctrl_init,
+ NULL
+ };
+ ctrl_type = g_type_register_static (SP_TYPE_CANVAS_ITEM, "SPCtrl", &ctrl_info, (GTypeFlags)0);
+ }
+ return ctrl_type;
+}
+
+static void
+sp_ctrl_class_init (SPCtrlClass *klass)
+{
+ GtkObjectClass *object_class;
+ SPCanvasItemClass *item_class;
+
+ object_class = (GtkObjectClass *) klass;
+ item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass *)gtk_type_class (sp_canvas_item_get_type ());
+
+ gtk_object_add_arg_type ("SPCtrl::shape", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_SHAPE);
+ gtk_object_add_arg_type ("SPCtrl::mode", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_MODE);
+ gtk_object_add_arg_type ("SPCtrl::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR);
+ gtk_object_add_arg_type ("SPCtrl::size", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_SIZE);
+ gtk_object_add_arg_type ("SPCtrl::pixbuf", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_PIXBUF);
+ gtk_object_add_arg_type ("SPCtrl::filled", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_FILLED);
+ gtk_object_add_arg_type ("SPCtrl::fill_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_FILL_COLOR);
+ gtk_object_add_arg_type ("SPCtrl::stroked", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_STROKED);
+ gtk_object_add_arg_type ("SPCtrl::stroke_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_STROKE_COLOR);
+
+ object_class->destroy = sp_ctrl_destroy;
+ object_class->set_arg = sp_ctrl_set_arg;
+
+ item_class->update = sp_ctrl_update;
+ item_class->render = sp_ctrl_render;
+ item_class->point = sp_ctrl_point;
+}
+
+static void
+sp_ctrl_init (SPCtrl *ctrl)
+{
+ ctrl->shape = SP_CTRL_SHAPE_SQUARE;
+ ctrl->mode = SP_CTRL_MODE_COLOR;
+ ctrl->anchor = GTK_ANCHOR_CENTER;
+ ctrl->span = 3;
+ ctrl->defined = TRUE;
+ ctrl->shown = FALSE;
+ ctrl->build = FALSE;
+ ctrl->filled = 1;
+ ctrl->stroked = 0;
+ ctrl->fill_color = 0x000000ff;
+ ctrl->stroke_color = 0x000000ff;
+
+ ctrl->box.x0 = ctrl->box.y0 = ctrl->box.x1 = ctrl->box.y1 = 0;
+ ctrl->cache = NULL;
+ ctrl->pixbuf = NULL;
+}
+
+static void
+sp_ctrl_destroy (GtkObject *object)
+{
+ SPCtrl *ctrl;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (SP_IS_CTRL (object));
+
+ ctrl = SP_CTRL (object);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ SPCanvasItem *item;
+ SPCtrl *ctrl;
+ GdkPixbuf * pixbuf = NULL;
+
+ item = SP_CANVAS_ITEM (object);
+ ctrl = SP_CTRL (object);
+
+ switch (arg_id) {
+ case ARG_SHAPE:
+ ctrl->shape = (SPCtrlShapeType)(GTK_VALUE_INT (*arg));
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_MODE:
+ ctrl->mode = (SPCtrlModeType)(GTK_VALUE_INT (*arg));
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_ANCHOR:
+ ctrl->anchor = (GtkAnchorType)(GTK_VALUE_INT (*arg));
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_SIZE:
+ ctrl->span = (gint) ((GTK_VALUE_DOUBLE (*arg) - 1.0) / 2.0 + 0.5);
+ ctrl->defined = (ctrl->span > 0);
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_FILLED:
+ ctrl->filled = GTK_VALUE_BOOL (*arg);
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_FILL_COLOR:
+ ctrl->fill_color = GTK_VALUE_INT (*arg);
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_STROKED:
+ ctrl->stroked = GTK_VALUE_BOOL (*arg);
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_STROKE_COLOR:
+ ctrl->stroke_color = GTK_VALUE_INT (*arg);
+ ctrl->build = FALSE;
+ sp_canvas_item_request_update (item);
+ break;
+ case ARG_PIXBUF:
+ pixbuf = (GdkPixbuf*)(GTK_VALUE_POINTER (*arg));
+ if (gdk_pixbuf_get_has_alpha (pixbuf)) {
+ ctrl->pixbuf = pixbuf;
+ } else {
+ ctrl->pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ gdk_pixbuf_unref (pixbuf);
+ }
+ ctrl->build = FALSE;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SPCtrl *ctrl;
+ gint x, y;
+
+ ctrl = SP_CTRL (item);
+
+ if (((SPCanvasItemClass *) parent_class)->update)
+ (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ if (ctrl->shown) {
+ sp_canvas_request_redraw (item->canvas, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1);
+ }
+
+ if (!ctrl->defined) return;
+
+ x = (gint) ((affine[4] > 0) ? (affine[4] + 0.5) : (affine[4] - 0.5)) - ctrl->span;
+ y = (gint) ((affine[5] > 0) ? (affine[5] + 0.5) : (affine[5] - 0.5)) - ctrl->span;
+
+ switch (ctrl->anchor) {
+ case GTK_ANCHOR_N:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_S:
+ break;
+ case GTK_ANCHOR_NW:
+ case GTK_ANCHOR_W:
+ case GTK_ANCHOR_SW:
+ x += ctrl->span;
+ break;
+ case GTK_ANCHOR_NE:
+ case GTK_ANCHOR_E:
+ case GTK_ANCHOR_SE:
+ x -= (ctrl->span + 1);
+ break;
+ }
+
+ switch (ctrl->anchor) {
+ case GTK_ANCHOR_W:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_E:
+ break;
+ case GTK_ANCHOR_NW:
+ case GTK_ANCHOR_N:
+ case GTK_ANCHOR_NE:
+ y += ctrl->span;
+ break;
+ case GTK_ANCHOR_SW:
+ case GTK_ANCHOR_S:
+ case GTK_ANCHOR_SE:
+ y -= (ctrl->span + 1);
+ break;
+ }
+
+ ctrl->box.x0 = x;
+ ctrl->box.y0 = y;
+ ctrl->box.x1 = ctrl->box.x0 + 2 * ctrl->span;
+ ctrl->box.y1 = ctrl->box.y0 + 2 * ctrl->span;
+
+ sp_canvas_update_bbox (item, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1);
+
+}
+
+static double
+sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+{
+ SPCtrl *ctrl = SP_CTRL (item);
+
+ *actual_item = item;
+
+ const double x = p[NR::X];
+ const double y = p[NR::Y];
+
+ if ((x >= ctrl->box.x0) && (x <= ctrl->box.x1) && (y >= ctrl->box.y0) && (y <= ctrl->box.y1)) return 0.0;
+
+ return 1e18;
+}
+
+static void
+sp_ctrl_build_cache (SPCtrl *ctrl)
+{
+ guchar * p, *q;
+ gint size, x, y, z, s, a, side, c;
+ guint8 fr, fg, fb, fa, sr, sg, sb, sa;
+
+ if (ctrl->filled) {
+ fr = (ctrl->fill_color >> 24) & 0xff;
+ fg = (ctrl->fill_color >> 16) & 0xff;
+ fb = (ctrl->fill_color >> 8) & 0xff;
+ fa = (ctrl->fill_color) & 0xff;
+ } else { fr = 0x00; fg = 0x00; fb = 0x00; fa = 0x00; }
+ if (ctrl->stroked) {
+ sr = (ctrl->stroke_color >> 24) & 0xff;
+ sg = (ctrl->stroke_color >> 16) & 0xff;
+ sb = (ctrl->stroke_color >> 8) & 0xff;
+ sa = (ctrl->stroke_color) & 0xff;
+ } else { sr = fr; sg = fg; sb = fb; sa = fa; }
+
+
+ side = (ctrl->span * 2 +1);
+ c = ctrl->span ;
+ g_free (ctrl->cache);
+ size = (side) * (side) * 4;
+ ctrl->cache = (guchar*)g_malloc (size);
+ if (side < 2) return;
+
+ switch (ctrl->shape) {
+ case SP_CTRL_SHAPE_SQUARE:
+ p = ctrl->cache;
+ for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; }
+ for (y = 2; y < side; y++) {
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa;
+ for (x=2; x < side; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; }
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa;
+ }
+ for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; }
+ ctrl->build = TRUE;
+ break;
+ case SP_CTRL_SHAPE_DIAMOND:
+ p = ctrl->cache;
+ for (y = 0; y < side; y++) {
+ z = abs (c - y);
+ for (x = 0; x < z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;}
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;
+ for (; x < side - z -1; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; }
+ if (z != c) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;}
+ for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;}
+ }
+ break;
+ case SP_CTRL_SHAPE_CIRCLE:
+ p = ctrl->cache;
+ q = p + size -1;
+ s = -1;
+ for (y = 0; y <= c ; y++) {
+ a = abs (c - y);
+ z = (gint)(0.0 + sqrt ((c+.4)*(c+.4) - a*a));
+ x = 0;
+ while (x < c-z) {
+ *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
+ *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00;
+ x++;
+ }
+ do {
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa;
+ *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr;
+ x++;
+ } while (x < c-s);
+ while (x < MIN(c+s+1, c+z)) {
+ *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa;
+ *q-- = fa; *q-- = fb; *q-- = fg; *q-- = fr;
+ x++;
+ }
+ do {
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa;
+ *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr;
+ x++;
+ } while (x <= c+z);
+ while (x < side) {
+ *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
+ *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00;
+ x++;
+ }
+ s = z;
+ }
+ ctrl->build = TRUE;
+ break;
+ case SP_CTRL_SHAPE_CROSS:
+ p = ctrl->cache;
+ for (y = 0; y < side; y++) {
+ z = abs (c - y);
+ for (x = 0; x < c-z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;}
+ *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;
+ for (; x < c + z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;}
+ if (z != 0) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;}
+ for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;}
+ }
+ ctrl->build = TRUE;
+ break;
+ case SP_CTRL_SHAPE_BITMAP:
+ if (ctrl->pixbuf) {
+ unsigned char *px;
+ unsigned int rs;
+ px = gdk_pixbuf_get_pixels (ctrl->pixbuf);
+ rs = gdk_pixbuf_get_rowstride (ctrl->pixbuf);
+ for (y = 0; y < side; y++){
+ unsigned char *s, *d;
+ s = px + y * rs;
+ d = ctrl->cache + 4 * side * y;
+ for (x = 0; x < side; x++) {
+ if (s[3] < 0x80) {
+ d[0] = 0x00;
+ d[1] = 0x00;
+ d[2] = 0x00;
+ d[3] = 0x00;
+ } else if (s[0] < 0x80) {
+ d[0] = sr;
+ d[1] = sg;
+ d[2] = sb;
+ d[3] = sa;
+ } else {
+ d[0] = fr;
+ d[1] = fg;
+ d[2] = fb;
+ d[3] = fa;
+ }
+ s += 4;
+ d += 4;
+ }
+ }
+ } else {
+ g_print ("control has no pixmap\n");
+ }
+ ctrl->build = TRUE;
+ break;
+ case SP_CTRL_SHAPE_IMAGE:
+ if (ctrl->pixbuf) {
+ guint r = gdk_pixbuf_get_rowstride (ctrl->pixbuf);
+ guchar * pix;
+ q = gdk_pixbuf_get_pixels (ctrl->pixbuf);
+ p = ctrl->cache;
+ for (y = 0; y < side; y++){
+ pix = q + (y * r);
+ for (x = 0; x < side; x++) {
+ *p++ = *pix++;
+ *p++ = *pix++;
+ *p++ = *pix++;
+ *p++ = *pix++;
+ }
+ }
+ } else { g_print ("control has no pixmap\n"); }
+ ctrl->build = TRUE;
+ break;
+ default:
+ break;
+ }
+
+}
+
+// composite background, foreground, alpha for xor mode
+#define COMPOSE_X(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff )
+// composite background, foreground, alpha for color mode
+#define COMPOSE_N(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) f) * ((guchar) a) ) / 0xff )
+
+static void
+sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ gint y0, y1, y, x0,x1,x;
+ guchar * p, * q, a;
+
+ SPCtrl *ctrl = SP_CTRL (item);
+
+ if (!ctrl->defined) return;
+ if ((!ctrl->filled) && (!ctrl->stroked)) return;
+
+ sp_canvas_prepare_buffer (buf);
+
+ // the control-image is rendered into ctrl->cache
+ if (!ctrl->build) sp_ctrl_build_cache (ctrl);
+
+ // then we render from ctrl->cache
+ y0 = MAX (ctrl->box.y0, buf->rect.y0);
+ y1 = MIN (ctrl->box.y1, buf->rect.y1 - 1);
+ x0 = MAX (ctrl->box.x0, buf->rect.x0);
+ x1 = MIN (ctrl->box.x1, buf->rect.x1 - 1);
+
+ bool colormode;
+
+ for (y = y0; y <= y1; y++) {
+ p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
+ q = ctrl->cache + ((y - ctrl->box.y0) * (ctrl->span*2+1) + (x0 - ctrl->box.x0)) * 4;
+ for (x = x0; x <= x1; x++) {
+ a = *(q + 3);
+ // 00000000 is the only way to get invisible; all other colors with alpha 00 are treated as mode_color with alpha ff
+ colormode = false;
+ if (a == 0x00 && !(q[0] == 0x00 && q[1] == 0x00 && q[2] == 0x00)) {
+ a = 0xff;
+ colormode = true;
+ }
+ if (ctrl->mode == SP_CTRL_MODE_COLOR || colormode) {
+ p[0] = COMPOSE_N (p[0], q[0], a);
+ p[1] = COMPOSE_N (p[1], q[1], a);
+ p[2] = COMPOSE_N (p[2], q[2], a);
+ q += 4;
+ p += 3;
+ } else if (ctrl->mode == SP_CTRL_MODE_XOR) {
+ p[0] = COMPOSE_X (p[0], q[0], a);
+ p[1] = COMPOSE_X (p[1], q[1], a);
+ p[2] = COMPOSE_X (p[2], q[2], a);
+ q += 4;
+ p += 3;
+ }
+ }
+ }
+ ctrl->shown = TRUE;
+}
+
+void SPCtrl::moveto (NR::Point const p) {
+ sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (this), NR::Matrix(NR::translate (p)));
+}
diff --git a/src/display/sodipodi-ctrl.h b/src/display/sodipodi-ctrl.h
new file mode 100644
index 000000000..c0e584ce2
--- /dev/null
+++ b/src/display/sodipodi-ctrl.h
@@ -0,0 +1,65 @@
+#ifndef INKSCAPE_CTRL_H
+#define INKSCAPE_CTRL_H
+
+/* sodipodi-ctrl
+ *
+ * It is simply small square, which does not scale nor rotate
+ *
+ */
+
+#include <gtk/gtkenums.h>
+#include "sp-canvas.h"
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libnr/nr-rect-l.h>
+
+
+
+#define SP_TYPE_CTRL (sp_ctrl_get_type ())
+#define SP_CTRL(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRL, SPCtrl))
+#define SP_CTRL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CTRL, SPCtrlClass))
+#define SP_IS_CTRL(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRL))
+#define SP_IS_CTRL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRL))
+
+typedef enum {
+ SP_CTRL_SHAPE_SQUARE,
+ SP_CTRL_SHAPE_DIAMOND,
+ SP_CTRL_SHAPE_CIRCLE,
+ SP_CTRL_SHAPE_CROSS,
+ SP_CTRL_SHAPE_BITMAP,
+ SP_CTRL_SHAPE_IMAGE
+} SPCtrlShapeType;
+
+
+typedef enum {
+ SP_CTRL_MODE_COLOR,
+ SP_CTRL_MODE_XOR
+} SPCtrlModeType;
+
+struct SPCtrl : public SPCanvasItem{
+ SPCtrlShapeType shape;
+ SPCtrlModeType mode;
+ GtkAnchorType anchor;
+ gint span;
+ guint defined : 1;
+ guint shown : 1;
+ guint build : 1;
+ guint filled : 1;
+ guint stroked : 1;
+ guint32 fill_color;
+ guint32 stroke_color;
+
+ NRRectL box; /* NB! x1 & y1 are included */
+ guchar *cache;
+ GdkPixbuf * pixbuf;
+
+ void moveto(NR::Point const p);
+};
+
+struct SPCtrlClass : public SPCanvasItemClass{
+};
+
+
+
+/* Standard Gtk function */
+GtkType sp_ctrl_get_type (void);
+#endif
diff --git a/src/display/sodipodi-ctrlrect.cpp b/src/display/sodipodi-ctrlrect.cpp
new file mode 100644
index 000000000..05449f2bb
--- /dev/null
+++ b/src/display/sodipodi-ctrlrect.cpp
@@ -0,0 +1,330 @@
+#define __INKSCAPE_CTRLRECT_C__
+
+/*
+ * Simple non-transformed rectangle, usable for rubberband
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Carl Hetherington <inkscape@carlh.net>
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#include "display-forward.h"
+#include "sp-canvas-util.h"
+#include "sodipodi-ctrlrect.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_class_init(SPCtrlRectClass *c);
+static void sp_ctrlrect_init(CtrlRect *ctrlrect);
+static void sp_ctrlrect_destroy(GtkObject *object);
+
+static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf);
+
+static SPCanvasItemClass *parent_class;
+
+static const guint DASH_LENGTH = 4;
+
+GtkType sp_ctrlrect_get_type()
+{
+ static GtkType ctrlrect_type = 0;
+
+ if (!ctrlrect_type) {
+ GtkTypeInfo ctrlrect_info = {
+ "SPCtrlRect",
+ sizeof(CtrlRect),
+ sizeof(SPCtrlRectClass),
+ (GtkClassInitFunc) sp_ctrlrect_class_init,
+ (GtkObjectInitFunc) sp_ctrlrect_init,
+ NULL, NULL, NULL
+ };
+ ctrlrect_type = gtk_type_unique(SP_TYPE_CANVAS_ITEM, &ctrlrect_info);
+ }
+ return ctrlrect_type;
+}
+
+static void sp_ctrlrect_class_init(SPCtrlRectClass *c)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) c;
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) c;
+
+ parent_class = (SPCanvasItemClass*) gtk_type_class(sp_canvas_item_get_type());
+
+ object_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(GtkObject *object)
+{
+ if (GTK_OBJECT_CLASS(parent_class)->destroy) {
+ (* GTK_OBJECT_CLASS(parent_class)->destroy)(object);
+ }
+}
+
+/* FIXME: use definitions from somewhere else */
+#define RGBA_R(v) ((v) >> 24)
+#define RGBA_G(v) (((v) >> 16) & 0xff)
+#define RGBA_B(v) (((v) >> 8) & 0xff)
+#define RGBA_A(v) ((v) & 0xff)
+#define COMPOSE(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff )
+
+static void sp_ctrlrect_hline(SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba, guint dashed)
+{
+ if (y >= buf->rect.y0 && y < buf->rect.y1) {
+ guint const r = RGBA_R(rgba);
+ guint const g = RGBA_G(rgba);
+ guint const b = RGBA_B(rgba);
+ guint const a = RGBA_A(rgba);
+ gint const x0 = MAX(buf->rect.x0, xs);
+ gint const x1 = MIN(buf->rect.x1, xe + 1);
+ guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
+ for (gint x = x0; x < x1; x++) {
+ if (!dashed || ((x / DASH_LENGTH) % 2)) {
+ p[0] = COMPOSE(p[0], r, a);
+ p[1] = COMPOSE(p[1], g, a);
+ p[2] = COMPOSE(p[2], b, a);
+ }
+ p += 3;
+ }
+ }
+}
+
+static void sp_ctrlrect_vline(SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba, guint dashed)
+{
+ if (x >= buf->rect.x0 && x < buf->rect.x1) {
+ guint const r = RGBA_R(rgba);
+ guint const g = RGBA_G(rgba);
+ guint const b = RGBA_B(rgba);
+ guint const a = RGBA_A(rgba);
+ gint const y0 = MAX(buf->rect.y0, ys);
+ gint const y1 = MIN(buf->rect.y1, ye + 1);
+ guchar *p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
+ for (gint y = y0; y < y1; y++) {
+ if (!dashed || ((y / DASH_LENGTH) % 2)) {
+ p[0] = COMPOSE(p[0], r, a);
+ p[1] = COMPOSE(p[1], g, a);
+ p[2] = COMPOSE(p[2], b, a);
+ }
+ p += buf->buf_rowstride;
+ }
+ }
+}
+
+/** Fills the pixels in [xs, xe)*[ys,ye) clipped to the tile with rgb * a. */
+static void sp_ctrlrect_area(SPCanvasBuf *buf, gint xs, gint ys, gint xe, gint ye, guint32 rgba)
+{
+ guint const r = RGBA_R(rgba);
+ guint const g = RGBA_G(rgba);
+ guint const b = RGBA_B(rgba);
+ guint const a = RGBA_A(rgba);
+ gint const x0 = MAX(buf->rect.x0, xs);
+ gint const x1 = MIN(buf->rect.x1, xe + 1);
+ gint const y0 = MAX(buf->rect.y0, ys);
+ gint const y1 = MIN(buf->rect.y1, ye + 1);
+ for (gint y = y0; y < y1; y++) {
+ guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3;
+ for (gint x = x0; x < x1; x++) {
+ p[0] = COMPOSE(p[0], r, a);
+ p[1] = COMPOSE(p[1], g, a);
+ p[2] = COMPOSE(p[2], b, a);
+ p += 3;
+ }
+ }
+}
+
+static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SP_CTRLRECT(item)->render(buf);
+}
+
+
+static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ SP_CTRLRECT(item)->update(affine, flags);
+}
+
+
+
+void CtrlRect::init()
+{
+ _has_fill = false;
+ _dashed = false;
+ _shadow = 0;
+
+ _area.x0 = _area.y0 = 0;
+ _area.x1 = _area.y1 = -1;
+
+ _shadow_size = 0;
+
+ _border_color = 0x000000ff;
+ _fill_color = 0xffffffff;
+ _shadow_color = 0x000000ff;
+}
+
+
+void CtrlRect::render(SPCanvasBuf *buf)
+{
+ if ((_area.x0 < buf->rect.x1) &&
+ (_area.y0 < buf->rect.y1) &&
+ ((_area.x1 + _shadow_size) >= buf->rect.x0) &&
+ ((_area.y1 + _shadow_size) >= buf->rect.y0)) {
+ sp_canvas_prepare_buffer(buf);
+
+ /* Top */
+ sp_ctrlrect_hline(buf, _area.y0, _area.x0, _area.x1, _border_color, _dashed);
+ /* Bottom */
+ sp_ctrlrect_hline(buf, _area.y1, _area.x0, _area.x1, _border_color, _dashed);
+ /* Left */
+ sp_ctrlrect_vline(buf, _area.x0, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed);
+ /* Right */
+ sp_ctrlrect_vline(buf, _area.x1, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed);
+ if (_shadow_size > 0) {
+ /* Right shadow */
+ sp_ctrlrect_area(buf, _area.x1 + 1, _area.y0 + _shadow_size,
+ _area.x1 + _shadow_size, _area.y1 + _shadow_size, _shadow_color);
+ /* Bottom shadow */
+ sp_ctrlrect_area(buf, _area.x0 + _shadow_size, _area.y1 + 1,
+ _area.x1, _area.y1 + _shadow_size, _shadow_color);
+ }
+ if (_has_fill) {
+ /* Fill */
+ sp_ctrlrect_area(buf, _area.x0 + 1, _area.y0 + 1,
+ _area.x1 - 1, _area.y1 - 1, _fill_color);
+ }
+ }
+}
+
+
+void CtrlRect::update(NR::Matrix const &affine, unsigned int flags)
+{
+ if (((SPCanvasItemClass *) parent_class)->update) {
+ ((SPCanvasItemClass *) parent_class)->update(this, affine, flags);
+ }
+
+ sp_canvas_item_reset_bounds(this);
+
+ /* Request redraw old */
+ if (!_has_fill) {
+ /* Top */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x1 + 1, _area.y0 + 1);
+ /* Left */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x0 + 1, _area.y1 + 1);
+ /* Right */
+ sp_canvas_request_redraw(canvas,
+ _area.x1 - 1, _area.y0 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ /* Bottom */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y1 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ } else {
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ }
+
+ NR::Rect bbox(_rect.min() * affine, _rect.max() * affine);
+
+ _area.x0 = (int) floor(bbox.min()[NR::X] + 0.5);
+ _area.y0 = (int) floor(bbox.min()[NR::Y] + 0.5);
+ _area.x1 = (int) floor(bbox.max()[NR::X] + 0.5);
+ _area.y1 = (int) floor(bbox.max()[NR::Y] + 0.5);
+
+ _shadow_size = _shadow;
+
+ /* Request redraw new */
+ if (!_has_fill) {
+ /* Top */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x1 + 1, _area.y0 + 1);
+ /* Left */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x0 + 1, _area.y1 + 1);
+ /* Right */
+ sp_canvas_request_redraw(canvas,
+ _area.x1 - 1, _area.y0 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ /* Bottom */
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y1 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ } else {
+ sp_canvas_request_redraw(canvas,
+ _area.x0 - 1, _area.y0 - 1,
+ _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1);
+ }
+
+ x1 = _area.x0 - 1;
+ y1 = _area.y0 - 1;
+ x2 = _area.x1 + _shadow_size + 1;
+ y2 = _area.y1 + _shadow_size + 1;
+}
+
+
+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 = s;
+ _shadow_color = c;
+ _requestUpdate();
+}
+
+void CtrlRect::setRectangle(NR::Rect const &r)
+{
+ _rect = r;
+ _requestUpdate();
+}
+
+void CtrlRect::setDashed(bool d)
+{
+ _dashed = 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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sodipodi-ctrlrect.h b/src/display/sodipodi-ctrlrect.h
new file mode 100644
index 000000000..dc931b7dc
--- /dev/null
+++ b/src/display/sodipodi-ctrlrect.h
@@ -0,0 +1,70 @@
+#ifndef __INKSCAPE_CTRLRECT_H__
+#define __INKSCAPE_CTRLRECT_H__
+
+/**
+ * \file sodipodi-ctrlrect.h
+ * \brief Simple non-transformed rectangle, usable for rubberband
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ * Carl Hetherington <inkscape@carlh.net>
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL
+ *
+ */
+
+#include <glib/gtypes.h>
+#include "sp-canvas.h"
+
+#define SP_TYPE_CTRLRECT (sp_ctrlrect_get_type ())
+#define SP_CTRLRECT(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CTRLRECT, CtrlRect))
+#define SP_CTRLRECT_CLASS(c) (GTK_CHECK_CLASS_CAST((c), SP_TYPE_CTRLRECT, SPCtrlRectClass))
+#define SP_IS_CTRLRECT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLRECT))
+#define SP_IS_CTRLRECT_CLASS(klass) (GTK_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 setRectangle(NR::Rect const &r);
+ void setDashed(bool d);
+
+ void render(SPCanvasBuf *buf);
+ void update(NR::Matrix const &affine, unsigned int flags);
+
+private:
+ void _requestUpdate();
+
+ NR::Rect _rect;
+ bool _has_fill;
+ bool _dashed;
+ NRRectL _area;
+ gint _shadow_size;
+ guint32 _border_color;
+ guint32 _fill_color;
+ guint32 _shadow_color;
+ int _shadow;
+};
+
+struct SPCtrlRectClass : public SPCanvasItemClass {};
+
+GtkType sp_ctrlrect_get_type();
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp
new file mode 100644
index 000000000..e4f2c7b75
--- /dev/null
+++ b/src/display/sp-canvas-util.cpp
@@ -0,0 +1,307 @@
+#define __SP_CANVAS_UTILS_C__
+
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+
+
+#include "libnr/nr-matrix-div.h"
+#include "libnr/nr-pixops.h"
+#include "sp-canvas-util.h"
+
+#include <livarot/Shape.h>
+#include <livarot/int-line.h>
+#include <livarot/BitLigne.h>
+
+void
+sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2)
+{
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+ item->x1 = x1;
+ item->y1 = y1;
+ item->x2 = x2;
+ item->y2 = y2;
+ sp_canvas_request_redraw (item->canvas, (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 *buf)
+{
+ if (buf->is_empty) {
+ sp_canvas_clear_buffer(buf);
+ buf->is_empty = false;
+ }
+}
+
+void
+sp_canvas_clear_buffer (SPCanvasBuf *buf)
+{
+ unsigned char r, g, b;
+
+ r = (buf->bg_color >> 16) & 0xff;
+ g = (buf->bg_color >> 8) & 0xff;
+ b = buf->bg_color & 0xff;
+
+ if ((r != g) || (r != b)) {
+ int x, y;
+ for (y = buf->rect.y0; y < buf->rect.y1; y++) {
+ unsigned char *p;
+ p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride;
+ for (x = buf->rect.x0; x < buf->rect.x1; x++) {
+ *p++ = r;
+ *p++ = g;
+ *p++ = b;
+ }
+ }
+ } else {
+ int y;
+ for (y = buf->rect.y0; y < buf->rect.y1; y++) {
+ memset (buf->buf + (y - buf->rect.y0) * buf->buf_rowstride, r, 3 * (buf->rect.x1 - buf->rect.x0));
+ }
+ }
+}
+
+NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item)
+{
+ g_assert (item != NULL); // this may be overly zealous - it is
+ // plausible that this gets called
+ // with item == 0
+
+ return item->xform;
+}
+
+NR::Matrix sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to)
+{
+ g_assert (from != NULL);
+ g_assert (to != NULL);
+
+ return sp_canvas_item_i2w_affine(from) / sp_canvas_item_i2w_affine(to);
+}
+
+void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, NR::Matrix const &i2w)
+{
+ g_assert (item != NULL);
+
+ sp_canvas_item_affine_absolute(item, i2w / sp_canvas_item_i2w_affine(item->parent));
+}
+
+void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z)
+{
+ g_assert (item != NULL);
+
+ 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);
+
+ sp_canvas_item_lower (item, current_z - z);
+}
+
+gint
+sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b)
+{
+ const gint o_a = sp_canvas_item_order (a);
+ const gint o_b = sp_canvas_item_order (b);
+
+ if (o_a > o_b) return -1;
+ if (o_a < o_b) return 1;
+
+ return 0;
+}
+
+// These two functions are used by canvasitems that use livarot (currently ctrlline and ctrlquadr)
+
+void
+ctrl_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven)
+{
+ union {
+ uint8_t comp[4];
+ uint32_t col;
+ } tempCol;
+ if ( st >= en ) return;
+ tempCol.col=*(uint32_t*)data;
+
+ unsigned int r, g, b, a;
+ r = NR_RGBA32_R (tempCol.col);
+ g = NR_RGBA32_G (tempCol.col);
+ b = NR_RGBA32_B (tempCol.col);
+ a = NR_RGBA32_A (tempCol.col);
+ if (a == 0) return;
+
+ vst*=a;
+ ven*=a;
+
+ if ( vst < 0 ) vst=0;
+ if ( vst > 255 ) vst=255;
+ if ( ven < 0 ) ven=0;
+ if ( ven > 255 ) ven=255;
+ float sv=vst;
+ float dv=ven-vst;
+ int len=en-st;
+ uint8_t* d=(uint8_t*)dest.buffer;
+
+ d+=3*(st-dest.startPix);
+ if ( fabs(dv) < 0.001 ) {
+ if ( sv > 249.999 ) {
+ /* Simple copy */
+ while (len > 0) {
+ d[0] = INK_COMPOSE (r, 255, d[0]);
+ d[1] = INK_COMPOSE (g, 255, d[1]);
+ d[2] = INK_COMPOSE (b, 255, d[2]);
+ d += 3;
+ len -= 1;
+ }
+ } else {
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ while (len > 0) {
+ d[0] = INK_COMPOSE (r, c0_24, d[0]);
+ d[1] = INK_COMPOSE (g, c0_24, d[1]);
+ d[2] = INK_COMPOSE (b, c0_24, d[2]);
+ d += 3;
+ len -= 1;
+ }
+ }
+ } else {
+ if ( en <= st+1 ) {
+ sv=0.5*(vst+ven);
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ d[0] = INK_COMPOSE (r, c0_24, d[0]);
+ d[1] = INK_COMPOSE (g, c0_24, d[1]);
+ d[2] = INK_COMPOSE (b, c0_24, d[2]);
+ } else {
+ dv/=len;
+ sv+=0.5*dv; // correction trapezoidale
+ sv*=65536;
+ dv*=65536;
+ int c0_24 = static_cast<int>(CLAMP(sv, 0, 16777216));
+ int s0_24 = static_cast<int>(dv);
+ while (len > 0) {
+ unsigned int ca;
+ /* Draw */
+ ca = c0_24 >> 16;
+ if ( ca > 255 ) ca=255;
+ d[0] = INK_COMPOSE (r, ca, d[0]);
+ d[1] = INK_COMPOSE (g, ca, d[1]);
+ d[2] = INK_COMPOSE (b, ca, d[2]);
+ d += 3;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+ }
+}
+
+void nr_pixblock_render_ctrl_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride)
+{
+
+ theS->CalcBBox();
+ float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY;
+ int il,ir,it,ib;
+ il=(int)floor(l);
+ ir=(int)ceil(r);
+ it=(int)floor(t);
+ ib=(int)ceil(b);
+
+// printf("bbox %i %i %i %i render %i %i %i %i\n",il,it,ir,ib,area.x0,area.y0,area.x1,area.y1);
+
+ if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return;
+ if ( il < area.x0 ) il=area.x0;
+ if ( it < area.y0 ) it=area.y0;
+ if ( ir > area.x1 ) ir=area.x1;
+ if ( ib > area.y1 ) ib=area.y1;
+
+/* // version par FloatLigne
+ int curPt;
+ float curY;
+ theS->BeginRaster(curY,curPt,1.0);
+
+ FloatLigne* theI=new FloatLigne();
+ IntLigne* theIL=new IntLigne();
+
+ theS->Scan(curY,curPt,(float)(it),1.0);
+
+ char* mdata=(char*)destBuf;
+ uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0))));
+ for (int y=it;y<ib;y++) {
+ theI->Reset();
+ if ( y&0x00000003 ) {
+ theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0);
+ } else {
+ theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0);
+ }
+ theI->Flatten();
+ theIL->Copy(theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,&color,bpath_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+stride));
+ }
+ theS->EndRaster();
+ delete theI;
+ delete theIL; */
+
+ // version par BitLigne directe
+ int curPt;
+ float curY;
+ theS->BeginQuickRaster(curY, curPt);
+
+ BitLigne* theI[4];
+ for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir);
+ IntLigne* theIL=new IntLigne();
+
+ theS->QuickScan(curY,curPt,(float)(it),true,0.25);
+
+ char* mdata=(char*)destBuf;
+ uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0))));
+ for (int y=it;y<ib;y++) {
+ for (int i=0;i<4;i++) theI[i]->Reset();
+ theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],0.25);
+ theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],0.25);
+ theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],0.25);
+ theIL->Copy(4,theI);
+
+ raster_info dest;
+ dest.startPix=il;
+ dest.endPix=ir;
+ dest.sth=il;
+ dest.stv=y;
+ dest.buffer=ligStart;
+ theIL->Raster(dest,&color,ctrl_run_A8_OR);
+ ligStart=((uint32_t*)(((char*)ligStart)+stride));
+ }
+ theS->EndQuickRaster();
+ for (int i=0;i<4;i++) delete theI[i];
+ delete theIL;
+}
diff --git a/src/display/sp-canvas-util.h b/src/display/sp-canvas-util.h
new file mode 100644
index 000000000..b592ba1d0
--- /dev/null
+++ b/src/display/sp-canvas-util.h
@@ -0,0 +1,61 @@
+#ifndef __SP_CANVAS_UTILS_H__
+#define __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, read the file 'COPYING' for more information
+ */
+
+#include "sp-canvas.h"
+
+/* 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);
+
+/* fill buffer with background color */
+
+void
+sp_canvas_clear_buffer (SPCanvasBuf * buf);
+
+/* get i2p (item to parent) affine transformation as general 6-element array */
+
+NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item);
+
+/* get i2i (item to item) affine transformation as general 6-element array */
+
+NR::Matrix 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, NR::Matrix const & aff);
+
+void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z);
+
+gint sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b);
+
+class Shape;
+class raster_info;
+void ctrl_run_A8_OR (raster_info &dest, void *data, int st, float vst, int en, float ven);
+void nr_pixblock_render_ctrl_rgba (Shape* theS, uint32_t color, NRRectL &area, char* destBuf, int stride);
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp
new file mode 100644
index 000000000..d1d7221f0
--- /dev/null
+++ b/src/display/sp-canvas.cpp
@@ -0,0 +1,2074 @@
+#define __SP_CANVAS_C__
+
+/** \file
+ * Port of GnomeCanvas for Inkscape needs
+ *
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * fred
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <libnr/nr-pixblock.h>
+
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
+
+#include <helper/sp-marshal.h>
+#include <display/sp-canvas.h>
+#include "display-forward.h"
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-convex-hull.h>
+
+enum {
+ RENDERMODE_NORMAL,
+ RENDERMODE_NOAA,
+ RENDERMODE_OUTLINE
+};
+
+const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE;
+
+#define SP_CANVAS_WINDOW(c) (((GtkWidget *) (c))->window)
+
+enum {
+ SP_CANVAS_ITEM_VISIBLE = 1 << 7,
+ SP_CANVAS_ITEM_NEED_UPDATE = 1 << 8,
+ SP_CANVAS_ITEM_NEED_AFFINE = 1 << 9
+};
+
+/**
+ * A group of Items.
+ */
+struct SPCanvasGroup {
+ SPCanvasItem item;
+
+ GList *items, *last;
+};
+
+/**
+ * The SPCanvasGroup vtable.
+ */
+struct SPCanvasGroupClass {
+ SPCanvasItemClass parent_class;
+};
+
+/**
+ * The SPCanvas vtable.
+ */
+struct SPCanvasClass {
+ GtkWidgetClass parent_class;
+};
+
+static void group_add (SPCanvasGroup *group, SPCanvasItem *item);
+static void group_remove (SPCanvasGroup *group, SPCanvasItem *item);
+
+/* SPCanvasItem */
+
+enum {ITEM_EVENT, ITEM_LAST_SIGNAL};
+
+
+static void sp_canvas_request_update (SPCanvas *canvas);
+
+static void sp_canvas_item_class_init (SPCanvasItemClass *klass);
+static void sp_canvas_item_init (SPCanvasItem *item);
+static void sp_canvas_item_dispose (GObject *object);
+static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args);
+
+static int emit_event (SPCanvas *canvas, GdkEvent *event);
+
+static guint item_signals[ITEM_LAST_SIGNAL] = { 0 };
+
+static GtkObjectClass *item_parent_class;
+
+/**
+ * Registers the SPCanvasItem class with Glib and returns its type number.
+ */
+GType
+sp_canvas_item_get_type (void)
+{
+ static GType type = 0;
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof (SPCanvasItemClass),
+ NULL, NULL,
+ (GClassInitFunc) sp_canvas_item_class_init,
+ NULL, NULL,
+ sizeof (SPCanvasItem),
+ 0,
+ (GInstanceInitFunc) sp_canvas_item_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_OBJECT, "SPCanvasItem", &info, (GTypeFlags)0);
+ }
+
+ return type;
+}
+
+/**
+ * Initializes the SPCanvasItem vtable and the "event" signal.
+ */
+static void
+sp_canvas_item_class_init (SPCanvasItemClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ /* fixme: Derive from GObject */
+ item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT);
+
+ item_signals[ITEM_EVENT] = g_signal_new ("event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SPCanvasItemClass, event),
+ NULL, NULL,
+ sp_marshal_BOOLEAN__POINTER,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT);
+
+ object_class->dispose = sp_canvas_item_dispose;
+}
+
+/**
+ * Callback for initialization of SPCanvasItem.
+ */
+static void
+sp_canvas_item_init (SPCanvasItem *item)
+{
+ item->flags |= SP_CANVAS_ITEM_VISIBLE;
+ item->xform = NR::Matrix(NR::identity());
+}
+
+/**
+ * Constructs new SPCanvasItem on SPCanvasGroup.
+ */
+SPCanvasItem *
+sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...)
+{
+ va_list args;
+
+ g_return_val_if_fail (parent != NULL, NULL);
+ g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL);
+ g_return_val_if_fail (gtk_type_is_a (type, sp_canvas_item_get_type ()), NULL);
+
+ SPCanvasItem *item = SP_CANVAS_ITEM (gtk_type_new (type));
+
+ va_start (args, first_arg_name);
+ sp_canvas_item_construct (item, parent, first_arg_name, args);
+ va_end (args);
+
+ return item;
+}
+
+/**
+ * Sets up the newly created SPCanvasItem.
+ *
+ * We make it static for encapsulation reasons since it was nowhere used.
+ */
+static void
+sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args)
+{
+ g_return_if_fail (SP_IS_CANVAS_GROUP (parent));
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ item->parent = SP_CANVAS_ITEM (parent);
+ item->canvas = item->parent->canvas;
+
+ g_object_set_valist (G_OBJECT (item), first_arg_name, args);
+
+ group_add (SP_CANVAS_GROUP (item->parent), item);
+
+ sp_canvas_item_request_update (item);
+ sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+ item->canvas->need_repick = TRUE;
+}
+
+/**
+ * Helper function that requests redraw only if item's visible flag is set.
+ */
+static void
+redraw_if_visible (SPCanvasItem *item)
+{
+ if (item->flags & SP_CANVAS_ITEM_VISIBLE) {
+ sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+ }
+}
+
+/**
+ * Callback that removes item from all referers and destroys it.
+ */
+static void
+sp_canvas_item_dispose (GObject *object)
+{
+ SPCanvasItem *item = SP_CANVAS_ITEM (object);
+
+ redraw_if_visible (item);
+ item->flags &= ~SP_CANVAS_ITEM_VISIBLE;
+
+ if (item == item->canvas->current_item) {
+ item->canvas->current_item = NULL;
+ item->canvas->need_repick = TRUE;
+ }
+
+ if (item == item->canvas->new_current_item) {
+ item->canvas->new_current_item = NULL;
+ item->canvas->need_repick = TRUE;
+ }
+
+ if (item == item->canvas->grabbed_item) {
+ item->canvas->grabbed_item = NULL;
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ }
+
+ if (item == item->canvas->focused_item)
+ item->canvas->focused_item = NULL;
+
+ if (item->parent) {
+ group_remove (SP_CANVAS_GROUP (item->parent), item);
+ }
+
+ G_OBJECT_CLASS (item_parent_class)->dispose (object);
+}
+
+/**
+ * Helper function to update item and its children.
+ *
+ * NB! affine is parent2canvas.
+ */
+static void
+sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ /* Apply the child item's transform */
+ NR::Matrix child_affine = item->xform * affine;
+
+ /* apply object flags to child flags */
+ int child_flags = flags & ~SP_CANVAS_UPDATE_REQUESTED;
+
+ if (item->flags & SP_CANVAS_ITEM_NEED_UPDATE)
+ child_flags |= SP_CANVAS_UPDATE_REQUESTED;
+
+ if (item->flags & SP_CANVAS_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);
+ }
+
+ GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_UPDATE);
+ GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_AFFINE);
+}
+
+/**
+ * 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 inverse of the item's transform,
+ * maintaining the affine invariant.
+ */
+static double
+sp_canvas_item_invoke_point (SPCanvasItem *item, NR::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 NR_HUGE;
+}
+
+/**
+ * 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, NR::Matrix const& affine)
+{
+ item->xform = affine;
+
+ if (!(item->flags & SP_CANVAS_ITEM_NEED_AFFINE)) {
+ item->flags |= SP_CANVAS_ITEM_NEED_AFFINE;
+ if (item->parent != NULL) {
+ sp_canvas_item_request_update (item->parent);
+ } else {
+ sp_canvas_request_update (item->canvas);
+ }
+ }
+
+ item->canvas->need_repick = TRUE;
+}
+
+/**
+ * Convenience function to reorder items in a group's child list.
+ *
+ * This puts the specified link after the "before" link.
+ */
+static void
+put_item_after (GList *link, GList *before)
+{
+ if (link == before)
+ return;
+
+ SPCanvasGroup *parent = SP_CANVAS_GROUP (SP_CANVAS_ITEM (link->data)->parent);
+
+ if (before == NULL) {
+ if (link == parent->items) return;
+
+ link->prev->next = link->next;
+
+ if (link->next) {
+ link->next->prev = link->prev;
+ } else {
+ parent->last = link->prev;
+ }
+
+ link->prev = before;
+ link->next = parent->items;
+ link->next->prev = link;
+ parent->items = link;
+ } else {
+ if ((link == parent->last) && (before == parent->last->prev))
+ return;
+
+ if (link->next)
+ link->next->prev = link->prev;
+
+ if (link->prev)
+ link->prev->next = link->next;
+ else {
+ parent->items = link->next;
+ parent->items->prev = NULL;
+ }
+
+ link->prev = before;
+ link->next = before->next;
+
+ link->prev->next = link;
+
+ if (link->next)
+ link->next->prev = link;
+ else
+ parent->last = link;
+ }
+}
+
+
+/**
+ * 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 != NULL);
+ 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);
+ GList *link = g_list_find (parent->items, item);
+ g_assert (link != NULL);
+
+ GList *before;
+ for (before = link; positions && before; positions--)
+ before = before->next;
+
+ if (!before)
+ before = parent->last;
+
+ put_item_after (link, before);
+
+ 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 != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (positions >= 1);
+
+ if (!item->parent || positions == 0)
+ return;
+
+ SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent);
+ GList *link = g_list_find (parent->items, item);
+ g_assert (link != NULL);
+
+ GList *before;
+ if (link->prev)
+ for (before = link->prev; positions && before; positions--)
+ before = before->prev;
+ else
+ before = NULL;
+
+ put_item_after (link, before);
+
+ redraw_if_visible (item);
+ item->canvas->need_repick = TRUE;
+}
+
+/**
+ * Sets visible flag on item and requests a redraw.
+ */
+void
+sp_canvas_item_show (SPCanvasItem *item)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (item->flags & SP_CANVAS_ITEM_VISIBLE)
+ return;
+
+ item->flags |= SP_CANVAS_ITEM_VISIBLE;
+
+ sp_canvas_request_redraw (item->canvas, (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 != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
+ return;
+
+ item->flags &= ~SP_CANVAS_ITEM_VISIBLE;
+
+ sp_canvas_request_redraw (item->canvas, (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 != NULL, -1);
+ g_return_val_if_fail (SP_IS_CANVAS_ITEM (item), -1);
+ g_return_val_if_fail (GTK_WIDGET_MAPPED (item->canvas), -1);
+
+ if (item->canvas->grabbed_item)
+ return -1;
+
+ if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
+ return -1;
+
+ /* fixme: Top hack (Lauris) */
+ /* fixme: If we add key masks to event mask, Gdk will abort (Lauris) */
+ /* fixme: But Canvas actualle does get key events, so all we need is routing these here */
+ gdk_pointer_grab (SP_CANVAS_WINDOW (item->canvas), FALSE,
+ (GdkEventMask)(event_mask & (~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK))),
+ NULL, cursor, etime);
+
+ 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.
+ * \param etime The timestamp for ungrabbing the mouse.
+ */
+void
+sp_canvas_item_ungrab (SPCanvasItem *item, guint32 etime)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+
+ if (item->canvas->grabbed_item != item)
+ return;
+
+ item->canvas->grabbed_item = NULL;
+
+ gdk_pointer_ungrab (etime);
+}
+
+/**
+ * Returns the product of all transformation matrices from the root item down
+ * to the item.
+ */
+NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item)
+{
+ g_assert (SP_IS_CANVAS_ITEM (item)); // should we get this?
+
+ NR::Matrix affine = NR::identity();
+
+ while (item) {
+ affine *= item->xform;
+ item = item->parent;
+ }
+ return affine;
+}
+
+/**
+ * Helper that returns true iff item is descendant of parent.
+ */
+static bool is_descendant(SPCanvasItem const *item, SPCanvasItem const *parent)
+{
+ while (item) {
+ if (item == parent)
+ return true;
+ item = item->parent;
+ }
+
+ return false;
+}
+
+/**
+ * Focus canvas, and item under cursor if it is not already focussed.
+ */
+void
+sp_canvas_item_grab_focus (SPCanvasItem *item)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (SP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (GTK_WIDGET_CAN_FOCUS (GTK_WIDGET (item->canvas)));
+
+ SPCanvasItem *focused_item = item->canvas->focused_item;
+
+ if (focused_item) {
+ GdkEvent ev;
+ ev.focus_change.type = GDK_FOCUS_CHANGE;
+ ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas);
+ ev.focus_change.send_event = FALSE;
+ ev.focus_change.in = FALSE;
+
+ emit_event (item->canvas, &ev);
+ }
+
+ item->canvas->focused_item = item;
+ gtk_widget_grab_focus (GTK_WIDGET (item->canvas));
+
+ if (focused_item) {
+ GdkEvent ev;
+ ev.focus_change.type = GDK_FOCUS_CHANGE;
+ ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas);
+ ev.focus_change.send_event = FALSE;
+ ev.focus_change.in = TRUE;
+
+ emit_event (item->canvas, &ev);
+ }
+}
+
+/**
+ * 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->flags & SP_CANVAS_ITEM_NEED_UPDATE)
+ return;
+
+ item->flags |= SP_CANVAS_ITEM_NEED_UPDATE;
+
+ if (item->parent != NULL) {
+ /* 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. */
+ sp_canvas_request_update (item->canvas);
+ }
+}
+
+/**
+ * Returns position of item in group.
+ */
+gint sp_canvas_item_order (SPCanvasItem * item)
+{
+ return g_list_index (SP_CANVAS_GROUP (item->parent)->items, item);
+}
+
+/* SPCanvasGroup */
+
+static void sp_canvas_group_class_init (SPCanvasGroupClass *klass);
+static void sp_canvas_group_init (SPCanvasGroup *group);
+static void sp_canvas_group_destroy (GtkObject *object);
+
+static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static double sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+static void sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static SPCanvasItemClass *group_parent_class;
+
+/**
+ * Registers SPCanvasGroup class with Gtk and returns its type number.
+ */
+GtkType
+sp_canvas_group_get_type (void)
+{
+ static GtkType group_type = 0;
+
+ if (!group_type) {
+ static const GtkTypeInfo group_info = {
+ "SPCanvasGroup",
+ sizeof (SPCanvasGroup),
+ sizeof (SPCanvasGroupClass),
+ (GtkClassInitFunc) sp_canvas_group_class_init,
+ (GtkObjectInitFunc) sp_canvas_group_init,
+ NULL, NULL, NULL
+ };
+
+ group_type = gtk_type_unique (sp_canvas_item_get_type (), &group_info);
+ }
+
+ return group_type;
+}
+
+/**
+ * Class initialization function for SPCanvasGroupClass
+ */
+static void
+sp_canvas_group_class_init (SPCanvasGroupClass *klass)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) klass;
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ group_parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());
+
+ object_class->destroy = sp_canvas_group_destroy;
+
+ item_class->update = sp_canvas_group_update;
+ item_class->render = sp_canvas_group_render;
+ item_class->point = sp_canvas_group_point;
+}
+
+/**
+ * Callback. Empty.
+ */
+static void
+sp_canvas_group_init (SPCanvasGroup */*group*/)
+{
+ /* Nothing here */
+}
+
+/**
+ * Callback that destroys all items in group and calls group's virtual
+ * destroy() function.
+ */
+static void
+sp_canvas_group_destroy (GtkObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (SP_IS_CANVAS_GROUP (object));
+
+ const SPCanvasGroup *group = SP_CANVAS_GROUP (object);
+
+ GList *list = group->items;
+ while (list) {
+ SPCanvasItem *child = (SPCanvasItem *)list->data;
+ list = list->next;
+
+ gtk_object_destroy (GTK_OBJECT (child));
+ }
+
+ if (GTK_OBJECT_CLASS (group_parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (group_parent_class)->destroy) (object);
+}
+
+/**
+ * Update handler for canvas groups
+ */
+static void
+sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
+ NR::ConvexHull corners(NR::Point(0, 0));
+ bool empty=true;
+
+ for (GList *list = group->items; list; list = list->next) {
+ SPCanvasItem *i = (SPCanvasItem *)list->data;
+
+ sp_canvas_item_invoke_update (i, affine, flags);
+
+ if ( i->x2 > i->x1 && i->y2 > i->y1 ) {
+ if (empty) {
+ corners = NR::ConvexHull(NR::Point(i->x1, i->y1));
+ empty = false;
+ } else {
+ corners.add(NR::Point(i->x1, i->y1));
+ }
+ corners.add(NR::Point(i->x2, i->y2));
+ }
+ }
+
+ NR::Rect const &bounds = corners.bounds();
+ item->x1 = bounds.min()[NR::X];
+ item->y1 = bounds.min()[NR::Y];
+ item->x2 = bounds.max()[NR::X];
+ item->y2 = bounds.max()[NR::Y];
+}
+
+/**
+ * Point handler for canvas groups.
+ */
+static double
+sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+{
+ const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
+ const double x = p[NR::X];
+ const double y = p[NR::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 = NULL;
+
+ double dist = 0.0;
+
+ for (GList *list = group->items; list; list = list->next) {
+ SPCanvasItem *child = (SPCanvasItem *)list->data;
+
+ if ((child->x1 <= x2) && (child->y1 <= y2) && (child->x2 >= x1) && (child->y2 >= y1)) {
+ SPCanvasItem *point_item = NULL; /* cater for incomplete item implementations */
+
+ int has_point;
+ if ((child->flags & SP_CANVAS_ITEM_VISIBLE) && SP_CANVAS_ITEM_GET_CLASS (child)->point) {
+ dist = sp_canvas_item_invoke_point (child, p, &point_item);
+ has_point = TRUE;
+ } else
+ has_point = FALSE;
+
+ if (has_point && point_item && ((int) (dist + 0.5) <= item->canvas->close_enough)) {
+ best = dist;
+ *actual_item = point_item;
+ }
+ }
+ }
+
+ return best;
+}
+
+/**
+ * Renders all visible canvas group items in buf rectangle.
+ */
+static void
+sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
+
+ for (GList *list = group->items; list; list = list->next) {
+ SPCanvasItem *child = (SPCanvasItem *)list->data;
+ if (child->flags & SP_CANVAS_ITEM_VISIBLE) {
+ if ((child->x1 < buf->rect.x1) &&
+ (child->y1 < buf->rect.y1) &&
+ (child->x2 > buf->rect.x0) &&
+ (child->y2 > buf->rect.y0)) {
+ if (SP_CANVAS_ITEM_GET_CLASS (child)->render)
+ SP_CANVAS_ITEM_GET_CLASS (child)->render (child, buf);
+ }
+ }
+ }
+}
+
+/**
+ * Adds an item to a canvas group.
+ */
+static void
+group_add (SPCanvasGroup *group, SPCanvasItem *item)
+{
+ gtk_object_ref (GTK_OBJECT (item));
+ gtk_object_sink (GTK_OBJECT (item));
+
+ if (!group->items) {
+ group->items = g_list_append (group->items, item);
+ group->last = group->items;
+ } else {
+ group->last = g_list_append (group->last, item)->next;
+ }
+
+ sp_canvas_item_request_update (item);
+}
+
+/**
+ * Removes an item from a canvas group
+ */
+static void
+group_remove (SPCanvasGroup *group, SPCanvasItem *item)
+{
+ g_return_if_fail (group != NULL);
+ g_return_if_fail (SP_IS_CANVAS_GROUP (group));
+ g_return_if_fail (item != NULL);
+
+ for (GList *children = group->items; children; children = children->next) {
+ if (children->data == item) {
+
+ /* Unparent the child */
+
+ item->parent = NULL;
+ gtk_object_unref (GTK_OBJECT (item));
+
+ /* Remove it from the list */
+
+ if (children == group->last) group->last = children->prev;
+
+ group->items = g_list_remove_link (group->items, children);
+ g_list_free (children);
+ break;
+ }
+ }
+}
+
+/* SPCanvas */
+
+static void sp_canvas_class_init (SPCanvasClass *klass);
+static void sp_canvas_init (SPCanvas *canvas);
+static void sp_canvas_destroy (GtkObject *object);
+
+static void sp_canvas_realize (GtkWidget *widget);
+static void sp_canvas_unrealize (GtkWidget *widget);
+
+static void sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req);
+static void sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+
+static gint sp_canvas_button (GtkWidget *widget, GdkEventButton *event);
+static gint sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event);
+static gint sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event);
+static gint sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event);
+static gint sp_canvas_key (GtkWidget *widget, GdkEventKey *event);
+static gint sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event);
+static gint sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event);
+static gint sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event);
+
+static GtkWidgetClass *canvas_parent_class;
+
+void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb);
+void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb);
+
+/**
+ * Registers the SPCanvas class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * \return The type ID of the SPCanvas class.
+ **/
+GtkType
+sp_canvas_get_type (void)
+{
+ static GtkType canvas_type = 0;
+
+ if (!canvas_type) {
+ static const GtkTypeInfo canvas_info = {
+ "SPCanvas",
+ sizeof (SPCanvas),
+ sizeof (SPCanvasClass),
+ (GtkClassInitFunc) sp_canvas_class_init,
+ (GtkObjectInitFunc) sp_canvas_init,
+ NULL, NULL, NULL
+ };
+
+ canvas_type = gtk_type_unique (GTK_TYPE_WIDGET, &canvas_info);
+ }
+
+ return canvas_type;
+}
+
+/**
+ * Class initialization function for SPCanvasClass.
+ */
+static void
+sp_canvas_class_init (SPCanvasClass *klass)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ canvas_parent_class = (GtkWidgetClass *)gtk_type_class (GTK_TYPE_WIDGET);
+
+ object_class->destroy = sp_canvas_destroy;
+
+ widget_class->realize = sp_canvas_realize;
+ widget_class->unrealize = sp_canvas_unrealize;
+ widget_class->size_request = sp_canvas_size_request;
+ widget_class->size_allocate = sp_canvas_size_allocate;
+ widget_class->button_press_event = sp_canvas_button;
+ widget_class->button_release_event = sp_canvas_button;
+ widget_class->motion_notify_event = sp_canvas_motion;
+ widget_class->scroll_event = sp_canvas_scroll;
+ widget_class->expose_event = sp_canvas_expose;
+ widget_class->key_press_event = sp_canvas_key;
+ widget_class->key_release_event = sp_canvas_key;
+ widget_class->enter_notify_event = sp_canvas_crossing;
+ widget_class->leave_notify_event = sp_canvas_crossing;
+ widget_class->focus_in_event = sp_canvas_focus_in;
+ widget_class->focus_out_event = sp_canvas_focus_out;
+}
+
+/**
+ * Callback: object initialization for SPCanvas.
+ */
+static void
+sp_canvas_init (SPCanvas *canvas)
+{
+ GTK_WIDGET_UNSET_FLAGS (canvas, GTK_NO_WINDOW);
+ GTK_WIDGET_UNSET_FLAGS (canvas, GTK_DOUBLE_BUFFERED);
+ GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS);
+
+ 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 (gtk_type_new (sp_canvas_group_get_type ()));
+ canvas->root->canvas = canvas;
+
+ gtk_object_ref (GTK_OBJECT (canvas->root));
+ gtk_object_sink (GTK_OBJECT (canvas->root));
+
+ canvas->need_repick = TRUE;
+
+ canvas->tiles=NULL;
+ canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0;
+ canvas->tileH=canvas->tileV=0;
+}
+
+/**
+ * Convenience function to remove the idle handler of a canvas.
+ */
+static void
+remove_idle (SPCanvas *canvas)
+{
+ if (canvas->idle_id) {
+ gtk_idle_remove (canvas->idle_id);
+ canvas->idle_id = 0;
+ }
+}
+
+/*
+ * Removes the transient state of the canvas (idle handler, grabs).
+ */
+static void
+shutdown_transients (SPCanvas *canvas)
+{
+ /* We turn off the need_redraw flag, since if the canvas is mapped again
+ * it will request a redraw anyways. We do not turn off the need_update
+ * flag, though, because updates are not queued when the canvas remaps
+ * itself.
+ */
+ if (canvas->need_redraw) {
+ canvas->need_redraw = FALSE;
+ }
+ if ( canvas->tiles ) free(canvas->tiles);
+ canvas->tiles=NULL;
+ canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0;
+ canvas->tileH=canvas->tileV=0;
+
+ if (canvas->grabbed_item) {
+ canvas->grabbed_item = NULL;
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ }
+
+ remove_idle (canvas);
+}
+
+/**
+ * Destroy handler for SPCanvas.
+ */
+static void
+sp_canvas_destroy (GtkObject *object)
+{
+ SPCanvas *canvas = SP_CANVAS (object);
+
+ if (canvas->root) {
+ gtk_object_unref (GTK_OBJECT (canvas->root));
+ canvas->root = NULL;
+ }
+
+ shutdown_transients (canvas);
+
+ if (GTK_OBJECT_CLASS (canvas_parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (canvas_parent_class)->destroy) (object);
+}
+
+/**
+ * Returns new canvas as widget.
+ */
+GtkWidget *
+sp_canvas_new_aa (void)
+{
+ SPCanvas *canvas = (SPCanvas *)gtk_type_new (sp_canvas_get_type ());
+
+ return (GtkWidget *) canvas;
+}
+
+/**
+ * The canvas widget's realize callback.
+ */
+static void
+sp_canvas_realize (GtkWidget *widget)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ GdkWindowAttr attributes;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gdk_rgb_get_visual ();
+ attributes.colormap = gdk_rgb_get_cmap ();
+ attributes.event_mask = (gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_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_FOCUS_CHANGE_MASK);
+ gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+ gdk_window_set_user_data (widget->window, widget);
+ gtk_widget_set_events(widget, attributes.event_mask);
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ canvas->pixmap_gc = gdk_gc_new (SP_CANVAS_WINDOW (canvas));
+}
+
+/**
+ * The canvas widget's unrealize callback.
+ */
+static void
+sp_canvas_unrealize (GtkWidget *widget)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ shutdown_transients (canvas);
+
+ gdk_gc_destroy (canvas->pixmap_gc);
+ canvas->pixmap_gc = NULL;
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize)
+ (* GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) (widget);
+}
+
+/**
+ * The canvas widget's size_request callback.
+ */
+static void
+sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req)
+{
+ static_cast<void>(SP_CANVAS (widget));
+
+ req->width = 256;
+ req->height = 256;
+}
+
+/**
+ * The canvas widget's size_allocate callback.
+ */
+static void
+sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ /* Schedule redraw of new region */
+ sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+allocation->width,canvas->y0+allocation->height);
+ if (allocation->width > widget->allocation.width) {
+ sp_canvas_request_redraw (canvas,
+ canvas->x0 + widget->allocation.width,
+ 0,
+ canvas->x0 + allocation->width,
+ canvas->y0 + allocation->height);
+ }
+ if (allocation->height > widget->allocation.height) {
+ sp_canvas_request_redraw (canvas,
+ 0,
+ canvas->y0 + widget->allocation.height,
+ canvas->x0 + allocation->width,
+ canvas->y0 + allocation->height);
+ }
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+ gdk_window_move_resize (widget->window,
+ widget->allocation.x, widget->allocation.y,
+ widget->allocation.width, widget->allocation.height);
+ }
+}
+
+/**
+ * Helper that emits an event for an item in the canvas, be it the current
+ * item, grabbed item, or focused item, as appropriate.
+ */
+static int
+emit_event (SPCanvas *canvas, GdkEvent *event)
+{
+ guint mask;
+
+ if (canvas->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;
+ break;
+ default:
+ mask = 0;
+ break;
+ }
+
+ if (!(mask & canvas->grabbed_event_mask)) return FALSE;
+ }
+
+ /* Convert to world coordinates -- we have two cases because of diferent
+ * offsets of the fields in the event structures.
+ */
+
+ GdkEvent ev = *event;
+
+ switch (ev.type) {
+ case GDK_ENTER_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ ev.crossing.x += canvas->x0;
+ ev.crossing.y += canvas->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 += canvas->x0;
+ ev.motion.y += canvas->y0;
+ break;
+ default:
+ break;
+ }
+
+ /* 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 = NULL;
+ if (canvas->grabbed_item && !is_descendant (canvas->current_item, canvas->grabbed_item)) {
+ item = canvas->grabbed_item;
+ } else {
+ item = canvas->current_item;
+ }
+
+ if (canvas->focused_item &&
+ ((event->type == GDK_KEY_PRESS) ||
+ (event->type == GDK_KEY_RELEASE) ||
+ (event->type == GDK_FOCUS_CHANGE))) {
+ item = canvas->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) {
+ gtk_object_ref (GTK_OBJECT (item));
+ gtk_signal_emit (GTK_OBJECT (item), item_signals[ITEM_EVENT], &ev, &finished);
+ SPCanvasItem *parent = item->parent;
+ gtk_object_unref (GTK_OBJECT (item));
+ item = parent;
+ }
+
+ return finished;
+}
+
+/**
+ * Helper that re-picks the current item in the canvas, based on the event's
+ * coordinates and emits enter/leave events for items as appropriate.
+ */
+static int
+pick_current_item (SPCanvas *canvas, GdkEvent *event)
+{
+ double x, y;
+
+ int retval = 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 != &canvas->pick_event) {
+ if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) {
+ /* these fields have the same offsets in both types of events */
+
+ canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY;
+ canvas->pick_event.crossing.window = event->motion.window;
+ canvas->pick_event.crossing.send_event = event->motion.send_event;
+ canvas->pick_event.crossing.subwindow = NULL;
+ canvas->pick_event.crossing.x = event->motion.x;
+ canvas->pick_event.crossing.y = event->motion.y;
+ canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL;
+ canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR;
+ canvas->pick_event.crossing.focus = FALSE;
+ canvas->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) {
+ canvas->pick_event.crossing.x_root = event->motion.x_root;
+ canvas->pick_event.crossing.y_root = event->motion.y_root;
+ } else {
+ canvas->pick_event.crossing.x_root = event->button.x_root;
+ canvas->pick_event.crossing.y_root = event->button.y_root;
+ }
+ } else {
+ canvas->pick_event = *event;
+ }
+ }
+
+ /* Don't do anything else if this is a recursive call */
+ if (canvas->in_repick) return retval;
+
+ /* LeaveNotify means that there is no current item, so we don't look for one */
+ if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) {
+ /* these fields don't have the same offsets in both types of events */
+
+ if (canvas->pick_event.type == GDK_ENTER_NOTIFY) {
+ x = canvas->pick_event.crossing.x;
+ y = canvas->pick_event.crossing.y;
+ } else {
+ x = canvas->pick_event.motion.x;
+ y = canvas->pick_event.motion.y;
+ }
+
+ /* world coords */
+ x += canvas->x0;
+ y += canvas->y0;
+
+ /* find the closest item */
+ if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) {
+ sp_canvas_item_invoke_point (canvas->root, NR::Point(x, y), &canvas->new_current_item);
+ } else {
+ canvas->new_current_item = NULL;
+ }
+ } else {
+ canvas->new_current_item = NULL;
+ }
+
+ if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item) {
+ return retval; /* current item did not change */
+ }
+
+ /* Synthesize events for old and new current items */
+
+ if ((canvas->new_current_item != canvas->current_item)
+ && (canvas->current_item != NULL)
+ && !canvas->left_grabbed_item) {
+ GdkEvent new_event;
+ SPCanvasItem *item;
+
+ item = canvas->current_item;
+
+ new_event = canvas->pick_event;
+ new_event.type = GDK_LEAVE_NOTIFY;
+
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = NULL;
+ canvas->in_repick = TRUE;
+ retval = emit_event (canvas, &new_event);
+ canvas->in_repick = FALSE;
+ }
+
+ /* Handle the rest of cases */
+
+ canvas->left_grabbed_item = FALSE;
+ canvas->current_item = canvas->new_current_item;
+
+ if (canvas->current_item != NULL) {
+ GdkEvent new_event;
+
+ new_event = canvas->pick_event;
+ new_event.type = GDK_ENTER_NOTIFY;
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = NULL;
+ retval = emit_event (canvas, &new_event);
+ }
+
+ return retval;
+}
+
+/**
+ * Button event handler for the canvas.
+ */
+static gint
+sp_canvas_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
+ has a pointer grab in effect */
+ if (!canvas->grabbed_item &&
+ event->window != SP_CANVAS_WINDOW (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;
+ }
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ /* Pick the current item as if the button were not pressed, and
+ * then process the event.
+ */
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+ canvas->state ^= mask;
+ retval = emit_event (canvas, (GdkEvent *) event);
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ /* Process the event as if the button were pressed, then repick
+ * after the button has been released
+ */
+ canvas->state = event->state;
+ retval = emit_event (canvas, (GdkEvent *) event);
+ event->state ^= mask;
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+ event->state ^= mask;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return retval;
+}
+
+/**
+ * Scroll event handler for the canvas.
+ *
+ * \todo FIXME: generate motion events to re-select items.
+ */
+static gint
+sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event)
+{
+ return emit_event (SP_CANVAS (widget), (GdkEvent *) event);
+}
+
+/**
+ * Motion event handler for the canvas.
+ */
+static int
+sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (event->window != SP_CANVAS_WINDOW (canvas))
+ return FALSE;
+
+ if (canvas->grabbed_event_mask & GDK_POINTER_MOTION_HINT_MASK) {
+ gint x, y;
+ gdk_window_get_pointer (widget->window, &x, &y, NULL);
+ event->x = x;
+ event->y = y;
+ }
+
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+
+ return emit_event (canvas, (GdkEvent *) event);
+}
+
+/**
+ * Helper that draws a specific rectangular part of the canvas.
+ */
+static void
+sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1)
+{
+ g_return_if_fail (!canvas->need_update);
+
+ GtkWidget *widget = GTK_WIDGET (canvas);
+
+ int draw_x1 = MAX (xx0, canvas->x0);
+ int draw_y1 = MAX (yy0, canvas->y0);
+ int draw_x2 = MIN (xx1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width);
+ int draw_y2 = MIN (yy1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height);
+
+ int bw = draw_x2 - draw_x1;
+ int bh = draw_y2 - draw_y1;
+ if ((bw < 1) || (bh < 1))
+ return;
+
+ int sw, sh;
+ if (canvas->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
+ /* 256K is the cached buffer and we need 3 channels */
+ if (bw * bh < 87381) { // 256K/3
+ // We can go with single buffer
+ sw = bw;
+ sh = bh;
+ } else if (bw <= (16 * 341)) {
+ // Go with row buffer
+ sw = bw;
+ sh = 87381 / bw;
+ } else if (bh <= (16 * 256)) {
+ // Go with column buffer
+ sw = 87381 / bh;
+ sh = bh;
+ } else {
+ sw = 341;
+ sh = 256;
+ }
+ } else { // paths only, so 1M works faster
+ /* 1M is the cached buffer and we need 3 channels */
+ if (bw * bh < 349525) { // 1M/3
+ // We can go with single buffer
+ sw = bw;
+ sh = bh;
+ } else if (bw <= (16 * 682)) {
+ // Go with row buffer
+ sw = bw;
+ sh = 349525 / bw;
+ } else if (bh <= (16 * 512)) {
+ // Go with column buffer
+ sw = 349525 / bh;
+ sh = bh;
+ } else {
+ sw = 682;
+ sh = 512;
+ }
+ }
+
+ // As we can come from expose, we have to tile here
+ for (int y0 = draw_y1; y0 < draw_y2; y0 += sh) {
+ int y1 = MIN (y0 + sh, draw_y2);
+ for (int x0 = draw_x1; x0 < draw_x2; x0 += sw) {
+ int x1 = MIN (x0 + sw, draw_x2);
+
+ SPCanvasBuf buf;
+ if (canvas->rendermode != RENDERMODE_OUTLINE) {
+ buf.buf = nr_pixelstore_256K_new (FALSE, 0);
+ } else {
+ buf.buf = nr_pixelstore_1M_new (FALSE, 0);
+ }
+
+ buf.buf_rowstride = sw * 3;
+ buf.rect.x0 = x0;
+ buf.rect.y0 = y0;
+ buf.rect.x1 = x1;
+ buf.rect.y1 = y1;
+ GdkColor *color = &widget->style->bg[GTK_STATE_NORMAL];
+ buf.bg_color = (((color->red & 0xff00) << 8)
+ | (color->green & 0xff00)
+ | (color->blue >> 8));
+ buf.is_empty = true;
+
+ if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) {
+ SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf);
+ }
+
+ if (buf.is_empty) {
+ gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color);
+ gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas),
+ canvas->pixmap_gc,
+ TRUE,
+ x0 - canvas->x0, y0 - canvas->y0,
+ x1 - x0, y1 - y0);
+ } else {
+ gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas),
+ canvas->pixmap_gc,
+ x0 - canvas->x0, y0 - canvas->y0,
+ x1 - x0, y1 - y0,
+ GDK_RGB_DITHER_MAX,
+ buf.buf,
+ sw * 3,
+ x0 - canvas->x0, y0 - canvas->y0);
+ }
+
+ if (canvas->rendermode != RENDERMODE_OUTLINE) {
+ nr_pixelstore_256K_free (buf.buf);
+ } else {
+ nr_pixelstore_1M_free (buf.buf);
+ }
+
+ }
+ }
+}
+
+/**
+ * The canvas widget's expose callback.
+ */
+static gint
+sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (!GTK_WIDGET_DRAWABLE (widget) ||
+ (event->window != SP_CANVAS_WINDOW (canvas)))
+ return FALSE;
+
+ int n_rects;
+ GdkRectangle *rects;
+ gdk_region_get_rectangles (event->region, &rects, &n_rects);
+
+ for (int i = 0; i < n_rects; i++) {
+ NRRectL rect;
+
+ rect.x0 = rects[i].x + canvas->x0;
+ rect.y0 = rects[i].y + canvas->y0;
+ rect.x1 = rect.x0 + rects[i].width;
+ rect.y1 = rect.y0 + rects[i].height;
+
+ if (canvas->need_update || canvas->need_redraw) {
+ sp_canvas_request_redraw (canvas, rect.x0, rect.y0, rect.x1, rect.y1);
+ } else {
+ /* No pending updates, draw exposed area immediately */
+ sp_canvas_paint_rect (canvas, rect.x0, rect.y0, rect.x1, rect.y1);
+ }
+ }
+
+ if (n_rects > 0)
+ g_free (rects);
+
+ return FALSE;
+}
+
+/**
+ * The canvas widget's keypress callback.
+ */
+static gint
+sp_canvas_key (GtkWidget *widget, GdkEventKey *event)
+{
+ return emit_event (SP_CANVAS (widget), (GdkEvent *) event);
+}
+
+/**
+ * Crossing event handler for the canvas.
+ */
+static gint
+sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event)
+{
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (event->window != SP_CANVAS_WINDOW (canvas))
+ return FALSE;
+
+ canvas->state = event->state;
+ return pick_current_item (canvas, (GdkEvent *) event);
+}
+
+/**
+ * Focus in handler for the canvas.
+ */
+static gint
+sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event)
+{
+ GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (canvas->focused_item) {
+ return emit_event (canvas, (GdkEvent *) event);
+ } else {
+ return FALSE;
+ }
+}
+
+/**
+ * Focus out handler for the canvas.
+ */
+static gint
+sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event)
+{
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+
+ SPCanvas *canvas = SP_CANVAS (widget);
+
+ if (canvas->focused_item)
+ return emit_event (canvas, (GdkEvent *) event);
+ else
+ return FALSE;
+}
+
+/**
+ * Helper that repaints the areas in the canvas that need it.
+ */
+static int
+paint (SPCanvas *canvas)
+{
+ if (canvas->need_update) {
+ sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0);
+ canvas->need_update = FALSE;
+ }
+
+ if (!canvas->need_redraw)
+ return TRUE;
+
+ GtkWidget const *widget = GTK_WIDGET(canvas);
+ int const canvas_x1 = canvas->x0 + widget->allocation.width;
+ int const canvas_y1 = canvas->y0 + widget->allocation.height;
+
+ NRRectL topaint;
+ topaint.x0 = topaint.y0 = topaint.x1 = topaint.y1 = 0;
+
+ for (int j=canvas->tTop&(~3);j<canvas->tBottom;j+=4) {
+ for (int i=canvas->tLeft&(~3);i<canvas->tRight;i+=4) {
+ int mode=0;
+
+ int pl=i+1,pr=i,pt=j+4,pb=j;
+ for (int l=MAX(j,canvas->tTop);l<MIN(j+4,canvas->tBottom);l++) {
+ for (int k=MAX(i,canvas->tLeft);k<MIN(i+4,canvas->tRight);k++) {
+ if ( canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH] ) {
+ mode|=1<<((k-i)+(l-j)*4);
+ if ( k < pl ) pl=k;
+ if ( k+1 > pr ) pr=k+1;
+ if ( l < pt ) pt=l;
+ if ( l+1 > pb ) pb=l+1;
+ }
+ canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH]=0;
+ }
+ }
+
+ if ( mode ) {
+ NRRectL tile;
+ tile.x0 = MAX (pl*32, canvas->x0);
+ tile.y0 = MAX (pt*32, canvas->y0);
+ tile.x1 = MIN (pr*32, canvas_x1);
+ tile.y1 = MIN (pb*32, canvas_y1);
+ if ((tile.x0 < tile.x1) && (tile.y0 < tile.y1)) {
+ nr_rect_l_union (&topaint, &topaint, &tile);
+ }
+
+ }
+ }
+ }
+
+ sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1);
+
+ canvas->need_redraw = FALSE;
+ return TRUE;
+}
+
+/**
+ * Helper that invokes update, paint, and repick on canvas.
+ */
+static int
+do_update (SPCanvas *canvas)
+{
+ /* Cause the update if necessary */
+ if (canvas->need_update) {
+ sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0);
+ canvas->need_update = FALSE;
+ }
+
+ /* Paint if able to */
+ if (GTK_WIDGET_DRAWABLE (canvas)) {
+ return paint (canvas);
+ }
+
+ /* Pick new current item */
+ while (canvas->need_repick) {
+ canvas->need_repick = FALSE;
+ pick_current_item (canvas, &canvas->pick_event);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Idle handler for the canvas that deals with pending updates and redraws.
+ */
+static gint
+idle_handler (gpointer data)
+{
+ GDK_THREADS_ENTER ();
+
+ SPCanvas *canvas = SP_CANVAS (data);
+
+ const int ret = do_update (canvas);
+
+ if (ret) {
+ /* Reset idle id */
+ canvas->idle_id = 0;
+ }
+
+ GDK_THREADS_LEAVE ();
+
+ return !ret;
+}
+
+/**
+ * Convenience function to add an idle handler to a canvas.
+ */
+static void
+add_idle (SPCanvas *canvas)
+{
+ if (canvas->idle_id != 0)
+ return;
+
+ canvas->idle_id = gtk_idle_add_priority (sp_canvas_update_priority, idle_handler, canvas);
+}
+
+/**
+ * Returns the root group of the specified canvas.
+ */
+SPCanvasGroup *
+sp_canvas_root (SPCanvas *canvas)
+{
+ g_return_val_if_fail (canvas != NULL, NULL);
+ g_return_val_if_fail (SP_IS_CANVAS (canvas), NULL);
+
+ return SP_CANVAS_GROUP (canvas->root);
+}
+
+/**
+ * Scrolls canvas to specific position.
+ */
+void
+sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear)
+{
+ g_return_if_fail (canvas != NULL);
+ g_return_if_fail (SP_IS_CANVAS (canvas));
+
+ int ix = (int) (cx + 0.5);
+ int iy = (int) (cy + 0.5);
+ int dx = ix - canvas->x0;
+ int dy = iy - canvas->y0;
+
+ canvas->dx0 = cx;
+ canvas->dy0 = cy;
+ canvas->x0 = ix;
+ canvas->y0 = iy;
+
+ sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+canvas->widget.allocation.width,canvas->y0+canvas->widget.allocation.height);
+
+ if (!clear) {
+ // scrolling without zoom; redraw only the newly exposed areas
+ if ((dx != 0) || (dy != 0)) {
+ int width, height;
+ width = canvas->widget.allocation.width;
+ height = canvas->widget.allocation.height;
+ if (GTK_WIDGET_REALIZED (canvas)) {
+ gdk_window_scroll (SP_CANVAS_WINDOW (canvas), -dx, -dy);
+ gdk_window_process_updates (SP_CANVAS_WINDOW (canvas), TRUE);
+ }
+ if (dx < 0) {
+ sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix - dx, iy + height);
+ } else if (dx > 0) {
+ sp_canvas_request_redraw (canvas, ix + width - dx, iy + 0, ix + width, iy + height);
+ }
+ if (dy < 0) {
+ sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix + width, iy - dy);
+ } else if (dy > 0) {
+ sp_canvas_request_redraw (canvas, ix + 0, iy + height - dy, ix + width, iy + height);
+ }
+ }
+ } else {
+ // scrolling as part of zoom; do nothing here - the next do_update will perform full redraw
+ }
+}
+
+/**
+ * Updates canvas if necessary.
+ */
+void
+sp_canvas_update_now (SPCanvas *canvas)
+{
+ g_return_if_fail (canvas != NULL);
+ g_return_if_fail (SP_IS_CANVAS (canvas));
+
+ if (!(canvas->need_update ||
+ canvas->need_redraw))
+ return;
+
+ remove_idle (canvas);
+ do_update (canvas);
+}
+
+/**
+ * Update callback for canvas widget.
+ */
+static void
+sp_canvas_request_update (SPCanvas *canvas)
+{
+ canvas->need_update = TRUE;
+ add_idle (canvas);
+}
+
+/**
+ * Forces redraw of rectangular canvas area.
+ */
+void
+sp_canvas_request_redraw (SPCanvas *canvas, int x0, int y0, int x1, int y1)
+{
+ NRRectL bbox;
+ NRRectL visible;
+ NRRectL clip;
+
+ g_return_if_fail (canvas != NULL);
+ g_return_if_fail (SP_IS_CANVAS (canvas));
+
+ if (!GTK_WIDGET_DRAWABLE (canvas)) return;
+ if ((x0 >= x1) || (y0 >= y1)) return;
+
+ bbox.x0 = x0;
+ bbox.y0 = y0;
+ bbox.x1 = x1;
+ bbox.y1 = y1;
+
+ visible.x0 = canvas->x0;
+ visible.y0 = canvas->y0;
+ visible.x1 = visible.x0 + GTK_WIDGET (canvas)->allocation.width;
+ visible.y1 = visible.y0 + GTK_WIDGET (canvas)->allocation.height;
+
+ nr_rect_l_intersect (&clip, &bbox, &visible);
+
+ sp_canvas_dirty_rect(canvas,x0,y0,x1,y1);
+ add_idle (canvas);
+}
+
+/**
+ * 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 != NULL);
+ 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 != NULL);
+ 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.
+ */
+NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win)
+{
+ g_assert (canvas != NULL);
+ g_assert (SP_IS_CANVAS (canvas));
+
+ return NR::Point(canvas->x0 + win[0], canvas->y0 + win[1]);
+}
+
+/**
+ * Converts point from world to win coordinates.
+ */
+NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world)
+{
+ g_assert (canvas != NULL);
+ g_assert (SP_IS_CANVAS (canvas));
+
+ return NR::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, NR::Point const &world)
+{
+ g_assert( canvas != NULL );
+ g_assert(SP_IS_CANVAS(canvas));
+
+ using NR::X;
+ using NR::Y;
+ GtkWidget const &w = *GTK_WIDGET(canvas);
+ return ( ( canvas->x0 <= world[X] ) &&
+ ( canvas->y0 <= world[Y] ) &&
+ ( world[X] < canvas->x0 + w.allocation.width ) &&
+ ( world[Y] < canvas->y0 + w.allocation.height ) );
+}
+
+/**
+ * Return canvas window coordinates as NRRect.
+ */
+NR::Rect SPCanvas::getViewbox() const
+{
+ GtkWidget const *w = GTK_WIDGET(this);
+
+ return NR::Rect(NR::Point(dx0, dy0),
+ NR::Point(dx0 + w->allocation.width, dy0 + w->allocation.height));
+}
+
+inline int sp_canvas_tile_floor(int x)
+{
+ return (x&(~31))/32;
+}
+
+inline int sp_canvas_tile_ceil(int x)
+{
+ return ((x+31)&(~31))/32;
+}
+
+/**
+ * Helper that changes tile size for canvas redraw.
+ */
+void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb)
+{
+ if ( nl >= nr || nt >= nb ) {
+ if ( canvas->tiles ) free(canvas->tiles);
+ canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0;
+ canvas->tileH=canvas->tileV=0;
+ canvas->tiles=NULL;
+ return;
+ }
+ int tl=sp_canvas_tile_floor(nl);
+ int tt=sp_canvas_tile_floor(nt);
+ int tr=sp_canvas_tile_ceil(nr);
+ int tb=sp_canvas_tile_ceil(nb);
+
+ int nh=tr-tl,nv=tb-tt;
+ uint8_t* ntiles=(uint8_t*)malloc(nh*nv*sizeof(uint8_t));
+ for (int i=tl;i<tr;i++) {
+ for (int j=tt;j<tb;j++) {
+ int ind=(i-tl)+(j-tt)*nh;
+ if ( i >= canvas->tLeft && i < canvas->tRight && j >= canvas->tTop && j < canvas->tBottom ) {
+ ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH];
+ } else {
+ ntiles[ind]=0;
+ }
+ }
+ }
+ if ( canvas->tiles ) free(canvas->tiles);
+ canvas->tiles=ntiles;
+ canvas->tLeft=tl;
+ canvas->tTop=tt;
+ canvas->tRight=tr;
+ canvas->tBottom=tb;
+ canvas->tileH=nh;
+ canvas->tileV=nv;
+}
+
+/**
+ * Helper that marks specific canvas rectangle for redraw.
+ */
+void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb)
+{
+ if ( nl >= nr || nt >= nb ) {
+ return;
+ }
+ int tl=sp_canvas_tile_floor(nl);
+ int tt=sp_canvas_tile_floor(nt);
+ int tr=sp_canvas_tile_ceil(nr);
+ int tb=sp_canvas_tile_ceil(nb);
+ if ( tl >= canvas->tRight || tr <= canvas->tLeft || tt >= canvas->tBottom || tb <= canvas->tTop ) return;
+ if ( tl < canvas->tLeft ) tl=canvas->tLeft;
+ if ( tr > canvas->tRight ) tr=canvas->tRight;
+ if ( tt < canvas->tTop ) tt=canvas->tTop;
+ if ( tb > canvas->tBottom ) tb=canvas->tBottom;
+
+ canvas->need_redraw = TRUE;
+
+ for (int i=tl;i<tr;i++) {
+ for (int j=tt;j<tb;j++) {
+ canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=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 :
diff --git a/src/display/sp-canvas.h b/src/display/sp-canvas.h
new file mode 100644
index 000000000..905c9272f
--- /dev/null
+++ b/src/display/sp-canvas.h
@@ -0,0 +1,186 @@
+#ifndef __SP_CANVAS_H__
+#define __SP_CANVAS_H__
+
+/** \file
+ * SPCanvas, SPCanvasBuf, and SPCanvasItem.
+ *
+ * Authors:
+ * Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <stdint.h>
+#include <glib/gtypes.h>
+#include <gdk/gdkevents.h>
+#include <gdk/gdkgc.h>
+#include <gtk/gtkobject.h>
+#include <gtk/gtkwidget.h>
+
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-rect.h>
+#include <libnr/nr-rect-l.h>
+
+struct SPCanvas;
+struct SPCanvasGroup;
+
+enum {
+ SP_CANVAS_UPDATE_REQUESTED = 1 << 0,
+ SP_CANVAS_UPDATE_AFFINE = 1 << 1
+};
+
+/**
+ * The canvas buf contains the actual pixels.
+ */
+struct SPCanvasBuf{
+ guchar *buf;
+ int buf_rowstride;
+ NRRectL rect;
+ /// Background color, given as 0xrrggbb
+ guint32 bg_color;
+ // If empty, ignore contents of buffer and use a solid area of bg_color
+ bool is_empty;
+};
+
+/**
+ * An SPCanvasItem refers to a SPCanvas and to its parent item; it has
+ * four coordinates, a bounding rectangle, and a transformation matrix.
+ */
+struct SPCanvasItem : public GtkObject {
+ SPCanvas *canvas;
+ SPCanvasItem *parent;
+
+ double x1, y1, x2, y2;
+ NR::Rect bounds;
+ NR::Matrix xform;
+};
+
+/**
+ * The vtable of an SPCanvasItem.
+ */
+struct SPCanvasItemClass : public GtkObjectClass {
+ void (* update) (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+
+ void (* render) (SPCanvasItem *item, SPCanvasBuf *buf);
+ double (* point) (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+
+ int (* event) (SPCanvasItem *item, GdkEvent *event);
+};
+
+SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...);
+
+#define sp_canvas_item_set gtk_object_set
+
+void sp_canvas_item_affine_absolute(SPCanvasItem *item, NR::Matrix const &aff);
+
+void sp_canvas_item_raise(SPCanvasItem *item, int positions);
+void sp_canvas_item_lower(SPCanvasItem *item, int positions);
+void sp_canvas_item_show(SPCanvasItem *item);
+void sp_canvas_item_hide(SPCanvasItem *item);
+int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime);
+void sp_canvas_item_ungrab(SPCanvasItem *item, guint32 etime);
+
+NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item);
+
+void sp_canvas_item_grab_focus(SPCanvasItem *item);
+
+void sp_canvas_item_request_update(SPCanvasItem *item);
+
+/* get item z-order in parent group */
+
+gint sp_canvas_item_order(SPCanvasItem * item);
+
+
+// SPCanvas -------------------------------------------------
+/**
+ * Port of GnomeCanvas for inkscape needs.
+ */
+struct SPCanvas {
+ GtkWidget widget;
+
+ guint idle_id;
+
+ SPCanvasItem *root;
+
+ double dx0, dy0;
+ int x0, y0;
+
+ /* Area that needs redrawing, stored as a microtile array */
+ int tLeft,tTop,tRight,tBottom;
+ int tileH,tileV;
+ uint8_t *tiles;
+
+ /* 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;
+
+ /* GC for temporary draw pixmap */
+ GdkGC *pixmap_gc;
+
+ unsigned int need_update : 1;
+ unsigned int need_redraw : 1;
+ unsigned int need_repick : 1;
+
+ /* 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;
+
+ int rendermode;
+
+ NR::Rect getViewbox() const;
+};
+
+GtkWidget *sp_canvas_new_aa();
+
+SPCanvasGroup *sp_canvas_root(SPCanvas *canvas);
+
+void sp_canvas_scroll_to(SPCanvas *canvas, double cx, double cy, unsigned int clear);
+void sp_canvas_update_now(SPCanvas *canvas);
+
+void sp_canvas_request_redraw(SPCanvas *canvas, int x1, int y1, int x2, int y2);
+
+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);
+
+NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win);
+NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world);
+
+bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world);
+
+#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/sp-ctrlline.cpp b/src/display/sp-ctrlline.cpp
new file mode 100644
index 000000000..0a278bb69
--- /dev/null
+++ b/src/display/sp-ctrlline.cpp
@@ -0,0 +1,217 @@
+#define __INKSCAPE_CTRLLINE_C__
+
+/*
+ * Simple straight line
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+/*
+ * TODO:
+ * Draw it by hand - we really do not need aa stuff for it
+ *
+ */
+
+#include "display-forward.h"
+#include "sp-canvas-util.h"
+#include "sp-ctrlline.h"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <livarot/Shape.h>
+#include <livarot/Path.h>
+
+struct SPCtrlLine : public SPCanvasItem{
+ guint32 rgba;
+ NRPoint s, e;
+ Shape* shp;
+};
+
+struct SPCtrlLineClass : public SPCanvasItemClass{};
+
+static void sp_ctrlline_class_init (SPCtrlLineClass *klass);
+static void sp_ctrlline_init (SPCtrlLine *ctrlline);
+static void sp_ctrlline_destroy (GtkObject *object);
+
+static void sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static SPCanvasItemClass *parent_class;
+
+GtkType
+sp_ctrlline_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type) {
+ GtkTypeInfo info = {
+ "SPCtrlLine",
+ sizeof (SPCtrlLine),
+ sizeof (SPCtrlLineClass),
+ (GtkClassInitFunc) sp_ctrlline_class_init,
+ (GtkObjectInitFunc) sp_ctrlline_init,
+ NULL, NULL, NULL
+ };
+ type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
+ }
+ return type;
+}
+
+static void
+sp_ctrlline_class_init (SPCtrlLineClass *klass)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) klass;
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
+
+ object_class->destroy = sp_ctrlline_destroy;
+
+ item_class->update = sp_ctrlline_update;
+ item_class->render = sp_ctrlline_render;
+}
+
+static void
+sp_ctrlline_init (SPCtrlLine *ctrlline)
+{
+ ctrlline->rgba = 0x0000ff7f;
+ ctrlline->s.x = ctrlline->s.y = ctrlline->e.x = ctrlline->e.y = 0.0;
+ ctrlline->shp=NULL;
+}
+
+static void
+sp_ctrlline_destroy (GtkObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (SP_IS_CTRLLINE (object));
+
+ SPCtrlLine *ctrlline = SP_CTRLLINE (object);
+
+ if (ctrlline->shp) {
+ delete ctrlline->shp;
+ ctrlline->shp = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrlLine *ctrlline = SP_CTRLLINE (item);
+
+ NRRectL area;
+ area.x0=buf->rect.x0;
+ area.x1=buf->rect.x1;
+ area.y0=buf->rect.y0;
+ area.y1=buf->rect.y1;
+
+ if (ctrlline->shp) {
+ sp_canvas_prepare_buffer (buf);
+ nr_pixblock_render_ctrl_rgba (ctrlline->shp,ctrlline->rgba,area,(char*)buf->buf, buf->buf_rowstride);
+ }
+}
+
+static void
+sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ NRRect dbox;
+
+ SPCtrlLine *cl = SP_CTRLLINE (item);
+
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+
+ if (parent_class->update)
+ (* parent_class->update) (item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ dbox.x0=dbox.x1=dbox.y0=dbox.y1=0;
+ if (cl->shp) {
+ delete cl->shp;
+ cl->shp = NULL;
+ }
+ Path* thePath = new Path;
+ thePath->MoveTo(NR::Point(cl->s.x, cl->s.y) * affine);
+ thePath->LineTo(NR::Point(cl->e.x, cl->e.y) * affine);
+
+ thePath->Convert(1.0);
+ if ( cl->shp == NULL ) cl->shp=new Shape;
+ thePath->Stroke(cl->shp,false,0.5,join_straight,butt_straight,20.0,false);
+ cl->shp->CalcBBox();
+ if ( cl->shp->leftX < cl->shp->rightX ) {
+ if ( dbox.x0 >= dbox.x1 ) {
+ dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX;
+ dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY;
+ } else {
+ if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX;
+ if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX;
+ if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY;
+ if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY;
+ }
+ }
+ delete thePath;
+
+ item->x1 = (int)dbox.x0;
+ item->y1 = (int)dbox.y0;
+ item->x2 = (int)dbox.x1;
+ item->y2 = (int)dbox.y1;
+
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+}
+
+void
+sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba)
+{
+ g_return_if_fail (cl != NULL);
+ g_return_if_fail (SP_IS_CTRLLINE (cl));
+
+ if (rgba != cl->rgba) {
+ SPCanvasItem *item;
+ cl->rgba = rgba;
+ item = SP_CANVAS_ITEM (cl);
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+ }
+}
+
+#define EPSILON 1e-6
+#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON)
+
+void
+sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1)
+{
+ g_return_if_fail (cl != NULL);
+ g_return_if_fail (SP_IS_CTRLLINE (cl));
+
+ if (DIFFER (x0, cl->s.x) || DIFFER (y0, cl->s.y) || DIFFER (x1, cl->e.x) || DIFFER (y1, cl->e.y)) {
+ cl->s.x = x0;
+ cl->s.y = y0;
+ cl->e.x = x1;
+ cl->e.y = y1;
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (cl));
+ }
+}
+
+void
+sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end)
+{
+ sp_ctrlline_set_coords(cl, 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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sp-ctrlline.h b/src/display/sp-ctrlline.h
new file mode 100644
index 000000000..500fbf08f
--- /dev/null
+++ b/src/display/sp-ctrlline.h
@@ -0,0 +1,45 @@
+#ifndef __INKSCAPE_CTRLLINE_H__
+#define __INKSCAPE_CTRLLINE_H__
+
+/*
+ * Simple straight line
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+#include "sp-canvas.h"
+
+
+
+#define SP_TYPE_CTRLLINE (sp_ctrlline_get_type ())
+#define SP_CTRLLINE(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLLINE, SPCtrlLine))
+#define SP_IS_CTRLLINE(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLLINE))
+
+struct SPCtrlLine;
+struct SPCtrlLineClass;
+
+GtkType sp_ctrlline_get_type (void);
+
+void sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba);
+void sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1);
+void sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end);
+
+
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sp-ctrlquadr.cpp b/src/display/sp-ctrlquadr.cpp
new file mode 100644
index 000000000..e9488cdb5
--- /dev/null
+++ b/src/display/sp-ctrlquadr.cpp
@@ -0,0 +1,210 @@
+#define __INKSCAPE_CTRLQUADR_C__
+
+/*
+ * Quadrilateral
+ *
+ * Authors:
+ * bulia byak
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL
+ */
+
+#include "display-forward.h"
+#include "sp-canvas-util.h"
+#include "sp-ctrlquadr.h"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <livarot/Shape.h>
+#include <livarot/Path.h>
+
+struct SPCtrlQuadr : public SPCanvasItem{
+ guint32 rgba;
+ NR::Point p1, p2, p3, p4;
+ Shape* shp;
+};
+
+struct SPCtrlQuadrClass : public SPCanvasItemClass{};
+
+static void sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass);
+static void sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr);
+static void sp_ctrlquadr_destroy (GtkObject *object);
+
+static void sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
+static void sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf);
+
+static SPCanvasItemClass *parent_class;
+
+GtkType
+sp_ctrlquadr_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type) {
+ GtkTypeInfo info = {
+ "SPCtrlQuadr",
+ sizeof (SPCtrlQuadr),
+ sizeof (SPCtrlQuadrClass),
+ (GtkClassInitFunc) sp_ctrlquadr_class_init,
+ (GtkObjectInitFunc) sp_ctrlquadr_init,
+ NULL, NULL, NULL
+ };
+ type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
+ }
+ return type;
+}
+
+static void
+sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) klass;
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
+
+ object_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 = NR::Point(0, 0);
+ ctrlquadr->p2 = NR::Point(0, 0);
+ ctrlquadr->p3 = NR::Point(0, 0);
+ ctrlquadr->p4 = NR::Point(0, 0);
+ ctrlquadr->shp=NULL;
+}
+
+static void
+sp_ctrlquadr_destroy (GtkObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (SP_IS_CTRLQUADR (object));
+
+ SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (object);
+
+ if (ctrlquadr->shp) {
+ delete ctrlquadr->shp;
+ ctrlquadr->shp = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (item);
+
+ NRRectL area;
+ area.x0=buf->rect.x0;
+ area.x1=buf->rect.x1;
+ area.y0=buf->rect.y0;
+ area.y1=buf->rect.y1;
+
+ if (ctrlquadr->shp) {
+ sp_canvas_prepare_buffer (buf);
+ nr_pixblock_render_ctrl_rgba (ctrlquadr->shp,ctrlquadr->rgba,area,(char*)buf->buf, buf->buf_rowstride);
+ }
+}
+
+static void
+sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+{
+ NRRect dbox;
+
+ SPCtrlQuadr *cl = SP_CTRLQUADR (item);
+
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+
+ if (parent_class->update)
+ (* parent_class->update) (item, affine, flags);
+
+ sp_canvas_item_reset_bounds (item);
+
+ dbox.x0=dbox.x1=dbox.y0=dbox.y1=0;
+ if (cl->shp) {
+ delete cl->shp;
+ cl->shp = NULL;
+ }
+ Path* thePath = new Path;
+ thePath->MoveTo(cl->p1 * affine);
+ thePath->LineTo(cl->p2 * affine);
+ thePath->LineTo(cl->p3 * affine);
+ thePath->LineTo(cl->p4 * affine);
+ thePath->LineTo(cl->p1 * affine);
+
+ thePath->Convert(1.0);
+
+ if ( cl->shp == NULL ) cl->shp=new Shape;
+ thePath->Fill(cl->shp, 0);
+
+ cl->shp->CalcBBox();
+ if ( cl->shp->leftX < cl->shp->rightX ) {
+ if ( dbox.x0 >= dbox.x1 ) {
+ dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX;
+ dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY;
+ } else {
+ if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX;
+ if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX;
+ if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY;
+ if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY;
+ }
+ }
+ delete thePath;
+
+ item->x1 = (int)dbox.x0;
+ item->y1 = (int)dbox.y0;
+ item->x2 = (int)dbox.x1;
+ item->y2 = (int)dbox.y1;
+
+ sp_canvas_request_redraw (item->canvas, (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 != NULL);
+ g_return_if_fail (SP_IS_CTRLQUADR (cl));
+
+ if (rgba != cl->rgba) {
+ SPCanvasItem *item;
+ cl->rgba = rgba;
+ item = SP_CANVAS_ITEM (cl);
+ sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
+ }
+}
+
+void
+sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, NR::Point p1, NR::Point p2, NR::Point p3, NR::Point p4)
+{
+ g_return_if_fail (cl != NULL);
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/sp-ctrlquadr.h b/src/display/sp-ctrlquadr.h
new file mode 100644
index 000000000..38951448c
--- /dev/null
+++ b/src/display/sp-ctrlquadr.h
@@ -0,0 +1,43 @@
+#ifndef __INKSCAPE_CTRLQUADR_H__
+#define __INKSCAPE_CTRLQUADR_H__
+
+/*
+ * Quadrilateral
+ *
+ * Authors:
+ * bulia byak
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL
+ */
+
+#include "sp-canvas.h"
+
+
+
+#define SP_TYPE_CTRLQUADR (sp_ctrlquadr_get_type ())
+#define SP_CTRLQUADR(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLQUADR, SPCtrlQuadr))
+#define SP_IS_CTRLQUADR(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLQUADR))
+
+struct SPCtrlQuadr;
+struct SPCtrlQuadrClass;
+
+GtkType sp_ctrlquadr_get_type (void);
+
+void sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba);
+void sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, const NR::Point p1, const NR::Point p2, const NR::Point p3, const NR::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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/testnr.cpp b/src/display/testnr.cpp
new file mode 100644
index 000000000..3a3478d28
--- /dev/null
+++ b/src/display/testnr.cpp
@@ -0,0 +1,24 @@
+#include <gtk/gtk.h>
+#include "sp-arena.h"
+
+int
+main (int argc, char ** argv)
+{
+ GtkWidget * w, * c;
+
+ gtk_init (&argc, &argv);
+
+ w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ c = sp_arena_new ();
+ gtk_widget_show (c);
+
+ gtk_container_add (GTK_CONTAINER (w), c);
+
+ gtk_widget_show (w);
+
+ gtk_main ();
+
+ return 0;
+}
+