summaryrefslogtreecommitdiffstats
path: root/src/libnr
diff options
context:
space:
mode:
authorMenTaLguY <mental@rydia.net>2006-01-16 02:36:01 +0000
committermental <mental@users.sourceforge.net>2006-01-16 02:36:01 +0000
commit179fa413b047bede6e32109e2ce82437c5fb8d34 (patch)
treea5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/libnr
downloadinkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz
inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to '')
-rw-r--r--src/libnr/.cvsignore13
-rw-r--r--src/libnr/Makefile_insert167
-rw-r--r--src/libnr/have_mmx.S47
-rw-r--r--src/libnr/in-svg-plane-test.cpp58
-rw-r--r--src/libnr/in-svg-plane-test.h81
-rw-r--r--src/libnr/in-svg-plane.h33
-rw-r--r--src/libnr/libnr.def89
-rw-r--r--src/libnr/makefile.in17
-rw-r--r--src/libnr/n-art-bpath.h61
-rw-r--r--src/libnr/nr-blit.cpp300
-rw-r--r--src/libnr/nr-blit.h32
-rw-r--r--src/libnr/nr-compose-transform.cpp360
-rw-r--r--src/libnr/nr-compose-transform.h43
-rw-r--r--src/libnr/nr-compose.cpp986
-rw-r--r--src/libnr/nr-compose.h69
-rw-r--r--src/libnr/nr-convex-hull-ops.h29
-rw-r--r--src/libnr/nr-convex-hull.h49
-rw-r--r--src/libnr/nr-coord.h29
-rw-r--r--src/libnr/nr-dim2.h22
-rw-r--r--src/libnr/nr-forward.h42
-rw-r--r--src/libnr/nr-gradient.cpp317
-rw-r--r--src/libnr/nr-gradient.h47
-rw-r--r--src/libnr/nr-i-coord.h25
-rw-r--r--src/libnr/nr-macros.h71
-rw-r--r--src/libnr/nr-matrix-div.cpp22
-rw-r--r--src/libnr/nr-matrix-div.h21
-rw-r--r--src/libnr/nr-matrix-fns.cpp54
-rw-r--r--src/libnr/nr-matrix-fns.h50
-rw-r--r--src/libnr/nr-matrix-ops.h45
-rw-r--r--src/libnr/nr-matrix-rotate-ops.cpp18
-rw-r--r--src/libnr/nr-matrix-rotate-ops.h20
-rw-r--r--src/libnr/nr-matrix-scale-ops.cpp37
-rw-r--r--src/libnr/nr-matrix-scale-ops.h14
-rw-r--r--src/libnr/nr-matrix-test.cpp197
-rw-r--r--src/libnr/nr-matrix-test.h221
-rw-r--r--src/libnr/nr-matrix-translate-ops.cpp26
-rw-r--r--src/libnr/nr-matrix-translate-ops.h36
-rw-r--r--src/libnr/nr-matrix.cpp607
-rw-r--r--src/libnr/nr-matrix.h453
-rw-r--r--src/libnr/nr-maybe.h140
-rw-r--r--src/libnr/nr-object.cpp295
-rw-r--r--src/libnr/nr-object.h157
-rw-r--r--src/libnr/nr-path-code.h28
-rw-r--r--src/libnr/nr-path.cpp461
-rw-r--r--src/libnr/nr-path.h62
-rw-r--r--src/libnr/nr-pixblock-line.cpp92
-rw-r--r--src/libnr/nr-pixblock-line.h28
-rw-r--r--src/libnr/nr-pixblock-pattern.cpp126
-rw-r--r--src/libnr/nr-pixblock-pattern.h28
-rw-r--r--src/libnr/nr-pixblock-pixel.cpp230
-rw-r--r--src/libnr/nr-pixblock-pixel.h28
-rw-r--r--src/libnr/nr-pixblock.cpp411
-rw-r--r--src/libnr/nr-pixblock.h95
-rw-r--r--src/libnr/nr-pixops.h46
-rw-r--r--src/libnr/nr-point-fns-test.cpp107
-rw-r--r--src/libnr/nr-point-fns-test.h141
-rw-r--r--src/libnr/nr-point-fns.cpp74
-rw-r--r--src/libnr/nr-point-fns.h104
-rw-r--r--src/libnr/nr-point-l.h95
-rw-r--r--src/libnr/nr-point-matrix-ops.h47
-rw-r--r--src/libnr/nr-point-ops.h88
-rw-r--r--src/libnr/nr-point.h159
-rw-r--r--src/libnr/nr-rect-l.cpp22
-rw-r--r--src/libnr/nr-rect-l.h131
-rw-r--r--src/libnr/nr-rect-ops.h51
-rw-r--r--src/libnr/nr-rect.cpp233
-rw-r--r--src/libnr/nr-rect.h255
-rw-r--r--src/libnr/nr-render.h25
-rw-r--r--src/libnr/nr-rotate-fns-test.cpp43
-rw-r--r--src/libnr/nr-rotate-fns-test.h54
-rw-r--r--src/libnr/nr-rotate-fns.cpp66
-rw-r--r--src/libnr/nr-rotate-fns.h29
-rw-r--r--src/libnr/nr-rotate-matrix-ops.cpp19
-rw-r--r--src/libnr/nr-rotate-matrix-ops.h21
-rw-r--r--src/libnr/nr-rotate-ops.h43
-rw-r--r--src/libnr/nr-rotate-test.cpp89
-rw-r--r--src/libnr/nr-rotate-test.h112
-rw-r--r--src/libnr/nr-rotate.h66
-rw-r--r--src/libnr/nr-scale-matrix-ops.cpp26
-rw-r--r--src/libnr/nr-scale-matrix-ops.h13
-rw-r--r--src/libnr/nr-scale-ops.h40
-rw-r--r--src/libnr/nr-scale-test.cpp71
-rw-r--r--src/libnr/nr-scale-test.h92
-rw-r--r--src/libnr/nr-scale-translate-ops.cpp19
-rw-r--r--src/libnr/nr-scale-translate-ops.h20
-rw-r--r--src/libnr/nr-scale.h50
-rw-r--r--src/libnr/nr-svp-private.h30
-rw-r--r--src/libnr/nr-svp-render.cpp615
-rw-r--r--src/libnr/nr-svp-render.h30
-rw-r--r--src/libnr/nr-svp.cpp180
-rw-r--r--src/libnr/nr-svp.h65
-rw-r--r--src/libnr/nr-translate-matrix-ops.cpp27
-rw-r--r--src/libnr/nr-translate-matrix-ops.h22
-rw-r--r--src/libnr/nr-translate-ops.h43
-rw-r--r--src/libnr/nr-translate-rotate-ops.cpp20
-rw-r--r--src/libnr/nr-translate-rotate-ops.h21
-rw-r--r--src/libnr/nr-translate-scale-ops.cpp25
-rw-r--r--src/libnr/nr-translate-scale-ops.h20
-rw-r--r--src/libnr/nr-translate-test.cpp65
-rw-r--r--src/libnr/nr-translate-test.h87
-rw-r--r--src/libnr/nr-translate.h34
-rw-r--r--src/libnr/nr-types-test.cpp105
-rw-r--r--src/libnr/nr-types-test.h145
-rw-r--r--src/libnr/nr-types.cpp68
-rw-r--r--src/libnr/nr-types.h47
-rw-r--r--src/libnr/nr-values.cpp23
-rw-r--r--src/libnr/nr-values.h45
-rw-r--r--src/libnr/nr_config.h.mingw12
-rw-r--r--src/libnr/nr_config.h.win3214
-rw-r--r--src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S125
-rw-r--r--src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S231
-rw-r--r--src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S414
-rw-r--r--src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S227
-rw-r--r--src/libnr/testnr.cpp92
-rw-r--r--src/libnrtype/.cvsignore5
-rw-r--r--src/libnrtype/FontFactory.cpp630
-rw-r--r--src/libnrtype/FontFactory.h112
-rw-r--r--src/libnrtype/FontInstance.cpp763
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Compute.cpp1514
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Input.cpp270
-rwxr-xr-xsrc/libnrtype/Layout-TNG-OutIter.cpp994
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Output.cpp469
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Scanline-Maker.h169
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Scanline-Makers.cpp189
-rwxr-xr-xsrc/libnrtype/Layout-TNG.cpp45
-rwxr-xr-xsrc/libnrtype/Layout-TNG.h1029
-rw-r--r--src/libnrtype/Makefile_insert42
-rw-r--r--src/libnrtype/RasterFont.cpp431
-rw-r--r--src/libnrtype/RasterFont.h66
-rw-r--r--src/libnrtype/TextWrapper.cpp937
-rw-r--r--src/libnrtype/TextWrapper.h139
-rw-r--r--src/libnrtype/boundary-type.h31
-rw-r--r--src/libnrtype/font-glyph.h29
-rw-r--r--src/libnrtype/font-instance.h121
-rw-r--r--src/libnrtype/font-style-to-pos.cpp120
-rw-r--r--src/libnrtype/font-style-to-pos.h20
-rw-r--r--src/libnrtype/font-style.h38
-rw-r--r--src/libnrtype/libnrtype.def59
-rw-r--r--src/libnrtype/makefile.in17
-rw-r--r--src/libnrtype/nr-type-pos-def.cpp268
-rw-r--r--src/libnrtype/nr-type-pos-def.h102
-rw-r--r--src/libnrtype/nr-type-primitives.cpp167
-rw-r--r--src/libnrtype/nr-type-primitives.h50
-rw-r--r--src/libnrtype/nrtype-forward.h23
-rw-r--r--src/libnrtype/one-box.h28
-rw-r--r--src/libnrtype/one-glyph.h46
-rw-r--r--src/libnrtype/one-para.h20
-rw-r--r--src/libnrtype/raster-glyph.h49
-rw-r--r--src/libnrtype/raster-position.h46
-rw-r--r--src/libnrtype/text-boundary.h49
150 files changed, 21359 insertions, 0 deletions
diff --git a/src/libnr/.cvsignore b/src/libnr/.cvsignore
new file mode 100644
index 000000000..4191391ae
--- /dev/null
+++ b/src/libnr/.cvsignore
@@ -0,0 +1,13 @@
+Makefile
+Makefile.in
+.deps
+.libs
+nr_config.h
+testnr
+gen_nr_config
+makefile
+.dirstamp
+*-test
+test-nr
+test-nr.cpp
+test-nr-main.cpp
diff --git a/src/libnr/Makefile_insert b/src/libnr/Makefile_insert
new file mode 100644
index 000000000..541432193
--- /dev/null
+++ b/src/libnr/Makefile_insert
@@ -0,0 +1,167 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+libnr/all: libnr/libnr.a
+
+libnr/clean:
+ rm -f libnr/libnr.a libnr/libtest-nr.a $(libnr_libnr_a_OBJECTS) $(libnr_libtest_nr_a_OBJECTS)
+
+if USE_MMX
+libnr_mmx_sources = \
+ libnr/have_mmx.S \
+ libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S \
+ libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S \
+ libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S \
+ libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S
+endif
+
+libnr_libnr_a_SOURCES = \
+ libnr/in-svg-plane.h \
+ libnr/n-art-bpath.h \
+ libnr/nr-blit.cpp \
+ libnr/nr-blit.h \
+ libnr/nr-compose-transform.cpp \
+ libnr/nr-compose-transform.h \
+ libnr/nr-compose.cpp \
+ libnr/nr-compose.h \
+ libnr/nr-convex-hull.h \
+ libnr/nr-coord.h \
+ libnr/nr-dim2.h \
+ libnr/nr-forward.h \
+ libnr/nr-gradient.cpp \
+ libnr/nr-gradient.h \
+ libnr/nr-i-coord.h \
+ libnr/nr-macros.h \
+ libnr/nr-matrix-div.cpp \
+ libnr/nr-matrix-div.h \
+ libnr/nr-matrix-fns.cpp \
+ libnr/nr-matrix-fns.h \
+ libnr/nr-matrix-ops.h \
+ libnr/nr-matrix-rotate-ops.cpp \
+ libnr/nr-matrix-rotate-ops.h \
+ libnr/nr-matrix-scale-ops.cpp \
+ libnr/nr-matrix-scale-ops.h \
+ libnr/nr-matrix-translate-ops.cpp \
+ libnr/nr-matrix-translate-ops.h \
+ libnr/nr-matrix.cpp \
+ libnr/nr-matrix.h \
+ libnr/nr-maybe.h \
+ libnr/nr-object.cpp \
+ libnr/nr-object.h \
+ libnr/nr-path.cpp \
+ libnr/nr-path.h \
+ libnr/nr-path-code.h \
+ libnr/nr-pixblock-line.cpp \
+ libnr/nr-pixblock-line.h \
+ libnr/nr-pixblock-pattern.cpp \
+ libnr/nr-pixblock-pattern.h \
+ libnr/nr-pixblock-pixel.cpp \
+ libnr/nr-pixblock-pixel.h \
+ libnr/nr-pixblock.cpp \
+ libnr/nr-pixblock.h \
+ libnr/nr-pixops.h \
+ libnr/nr-point-fns.cpp \
+ libnr/nr-point-fns.h \
+ libnr/nr-point-l.h \
+ libnr/nr-point-matrix-ops.h \
+ libnr/nr-point-ops.h \
+ libnr/nr-point.h \
+ libnr/nr-rect-l.cpp \
+ libnr/nr-rect-l.h \
+ libnr/nr-rect.cpp \
+ libnr/nr-rect.h \
+ libnr/nr-rect-ops.h \
+ libnr/nr-render.h \
+ libnr/nr-rotate-fns.cpp \
+ libnr/nr-rotate-fns.h \
+ libnr/nr-rotate-ops.h \
+ libnr/nr-rotate-matrix-ops.cpp \
+ libnr/nr-rotate-matrix-ops.h \
+ libnr/nr-rotate.h \
+ libnr/nr-scale-matrix-ops.cpp \
+ libnr/nr-scale-matrix-ops.h \
+ libnr/nr-scale-translate-ops.cpp \
+ libnr/nr-scale-translate-ops.h \
+ libnr/nr-scale-ops.h \
+ libnr/nr-scale.h \
+ libnr/nr-svp-private.h \
+ libnr/nr-svp-render.cpp \
+ libnr/nr-svp-render.h \
+ libnr/nr-svp.cpp \
+ libnr/nr-svp.h \
+ libnr/nr-translate-matrix-ops.cpp \
+ libnr/nr-translate-matrix-ops.h \
+ libnr/nr-translate-scale-ops.cpp \
+ libnr/nr-translate-scale-ops.h \
+ libnr/nr-translate-ops.h \
+ libnr/nr-translate.h \
+ libnr/nr-translate-rotate-ops.cpp \
+ libnr/nr-translate-rotate-ops.h \
+ libnr/nr-types.cpp \
+ libnr/nr-types.h \
+ libnr/nr-values.cpp \
+ libnr/nr-values.h \
+ $(libnr_mmx_sources)
+
+libnr_testnr_SOURCES = \
+ libnr/testnr.cpp
+
+libnr_testnr_LDADD = \
+ libnr/libnr.a \
+ -lglib-2.0
+
+
+libnr/test-nr-main.cpp: libnr/test-nr.cpp
+ $(top_srcdir)/cxxtest/cxxtestgen.pl --error-printer -root -o libnr/test-nr-main.cpp $(libnr_test_nr_includes)
+
+libnr/test-nr.cpp: $(libnr_test_nr_includes)
+ $(top_srcdir)/cxxtest/cxxtestgen.pl -part -o libnr/test-nr.cpp $(libnr_test_nr_includes)
+
+libnr_test_nr_includes = \
+ $(srcdir)/libnr/nr-types-test.h \
+ $(srcdir)/libnr/nr-translate-test.h \
+ $(srcdir)/libnr/nr-rotate-test.h \
+ $(srcdir)/libnr/nr-scale-test.h \
+ $(srcdir)/libnr/nr-point-fns-test.h \
+ $(srcdir)/libnr/nr-rotate-fns-test.h \
+ $(srcdir)/libnr/in-svg-plane-test.h \
+ $(srcdir)/libnr/nr-matrix-test.h
+
+libnr_libtest_nr_a_SOURCES = \
+ libnr/test-nr.cpp \
+ $(libnr_test_nr_includes)
+
+libnr_test_nr_SOURCES = \
+ libnr/test-nr-main.cpp \
+ $(libnr_test_nr_includes)
+
+libnr_test_nr_LDADD = \
+ libnr/libnr.a \
+ libnr/libtest-nr.a \
+ -lglib-2.0
+
+# -L/usr/X11R6/lib
+# -lX11
+
+libnr_in_svg_plane_test_SOURCES = libnr/in-svg-plane-test.cpp
+libnr_in_svg_plane_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_types_test_SOURCES = libnr/nr-types-test.cpp
+libnr_nr_types_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_point_fns_test_SOURCES = libnr/nr-point-fns-test.cpp
+libnr_nr_point_fns_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_matrix_test_SOURCES = libnr/nr-matrix-test.cpp
+libnr_nr_matrix_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_rotate_test_SOURCES = libnr/nr-rotate-test.cpp
+libnr_nr_rotate_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_rotate_fns_test_SOURCES = libnr/nr-rotate-fns-test.cpp
+libnr_nr_rotate_fns_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_scale_test_SOURCES = libnr/nr-scale-test.cpp
+libnr_nr_scale_test_LDADD = libnr/libnr.a -lglib-2.0
+
+libnr_nr_translate_test_SOURCES = libnr/nr-translate-test.cpp
+libnr_nr_translate_test_LDADD = libnr/libnr.a -lglib-2.0
diff --git a/src/libnr/have_mmx.S b/src/libnr/have_mmx.S
new file mode 100644
index 000000000..d6428191e
--- /dev/null
+++ b/src/libnr/have_mmx.S
@@ -0,0 +1,47 @@
+ .file "have_mmx.S"
+
+# Ensure Inkscape is execshield protected
+ .section .note.GNU-stack
+ .previous
+
+ .version "01.01"
+gcc2_compiled.:
+.text
+ .align 16
+.globl nr_have_mmx
+ .type nr_have_mmx,@function
+
+nr_have_mmx:
+ push %ebx
+
+# Check if bit 21 in flags word is writeable
+
+ pushfl
+ popl %eax
+ movl %eax,%ebx
+ xorl $0x00200000, %eax
+ pushl %eax
+ popfl
+ pushfl
+ popl %eax
+
+ cmpl %eax, %ebx
+
+ je .notfound
+
+# OK, we have CPUID
+
+ movl $1, %eax
+ cpuid
+
+ test $0x00800000, %edx
+ jz .notfound
+
+ movl $1, %eax
+ jmp .out
+
+.notfound:
+ movl $0, %eax
+.out:
+ popl %ebx
+ ret
diff --git a/src/libnr/in-svg-plane-test.cpp b/src/libnr/in-svg-plane-test.cpp
new file mode 100644
index 000000000..f5620c32b
--- /dev/null
+++ b/src/libnr/in-svg-plane-test.cpp
@@ -0,0 +1,58 @@
+#include <glib/gmacros.h>
+#include <cmath>
+
+#include "libnr/in-svg-plane.h"
+#include "utest/utest.h"
+#include "isnan.h"
+
+int main(int argc, char *argv[])
+{
+ utest_start("in-svg-plane.h");
+
+ NR::Point const p3n4(3.0, -4.0);
+ NR::Point const p0(0.0, 0.0);
+ double const small = pow(2.0, -1070);
+ double const inf = 1e400;
+ double const nan = inf - inf;
+
+ NR::Point const small_left(-small, 0.0);
+ NR::Point const small_n3_4(-3.0 * small, 4.0 * small);
+ NR::Point const part_nan(3., nan);
+
+ assert(isNaN(nan));
+ assert(!isNaN(small));
+
+ UTEST_TEST("in_svg_plane") {
+ UTEST_ASSERT(in_svg_plane(p3n4));
+ UTEST_ASSERT(in_svg_plane(p0));
+ UTEST_ASSERT(in_svg_plane(small_left));
+ UTEST_ASSERT(in_svg_plane(small_n3_4));
+ UTEST_ASSERT(nan != nan);
+ UTEST_ASSERT(!in_svg_plane(NR::Point(nan, 3.)));
+ UTEST_ASSERT(!in_svg_plane(NR::Point(inf, nan)));
+ UTEST_ASSERT(!in_svg_plane(NR::Point(0., -inf)));
+ double const xs[] = {inf, -inf, nan, 1., -2., small, -small};
+ for (unsigned i = 0; i < G_N_ELEMENTS(xs); ++i) {
+ for (unsigned j = 0; j < G_N_ELEMENTS(xs); ++j) {
+ UTEST_ASSERT( in_svg_plane(NR::Point(xs[i], xs[j]))
+ == (fabs(xs[i]) < inf &&
+ fabs(xs[j]) < inf ) );
+ }
+ }
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/in-svg-plane-test.h b/src/libnr/in-svg-plane-test.h
new file mode 100644
index 000000000..6cd29bd6a
--- /dev/null
+++ b/src/libnr/in-svg-plane-test.h
@@ -0,0 +1,81 @@
+#include <cxxtest/TestSuite.h>
+
+#include <glib/gmacros.h>
+#include <cmath>
+
+#include "libnr/in-svg-plane.h"
+#include "isnan.h"
+
+class InSvgPlaneTest : public CxxTest::TestSuite
+{
+public:
+
+ InSvgPlaneTest() :
+ setupValid(true),
+ p3n4( 3.0, -4.0 ),
+ p0(0.0, 0.0),
+ small( pow(2.0, -1070) ),
+ inf( 1e400 ),
+ nan( inf - inf ),
+ small_left( -small, 0.0 ),
+ small_n3_4( -3.0 * small, 4.0 * small ),
+ part_nan( 3., nan )
+ {
+ setupValid &= isNaN(nan);
+ setupValid &= !isNaN(small);
+ }
+ virtual ~InSvgPlaneTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static InSvgPlaneTest *createSuite() { return new InSvgPlaneTest(); }
+ static void destroySuite( InSvgPlaneTest *suite ) { delete suite; }
+
+// Called before each test in this suite
+ void setUp()
+ {
+ TS_ASSERT( setupValid );
+ }
+
+ bool setupValid;
+ NR::Point const p3n4;
+ NR::Point const p0;
+ double const small;
+ double const inf;
+ double const nan;
+ NR::Point const small_left;
+ NR::Point const small_n3_4;
+ NR::Point const part_nan;
+
+
+ void testInSvgPlane(void)
+ {
+ TS_ASSERT( in_svg_plane(p3n4) );
+ TS_ASSERT( in_svg_plane(p0) );
+ TS_ASSERT( in_svg_plane(small_left) );
+ TS_ASSERT( in_svg_plane(small_n3_4) );
+ TS_ASSERT_DIFFERS( nan, nan );
+ TS_ASSERT( !in_svg_plane(NR::Point(nan, 3.)) );
+ TS_ASSERT( !in_svg_plane(NR::Point(inf, nan)) );
+ TS_ASSERT( !in_svg_plane(NR::Point(0., -inf)) );
+ double const xs[] = {inf, -inf, nan, 1., -2., small, -small};
+ for (unsigned i = 0; i < G_N_ELEMENTS(xs); ++i) {
+ for (unsigned j = 0; j < G_N_ELEMENTS(xs); ++j) {
+ TS_ASSERT_EQUALS( in_svg_plane(NR::Point(xs[i], xs[j])),
+ (fabs(xs[i]) < inf &&
+ fabs(xs[j]) < inf ) );
+ }
+ }
+ }
+};
+
+/*
+ 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/libnr/in-svg-plane.h b/src/libnr/in-svg-plane.h
new file mode 100644
index 000000000..901fc748f
--- /dev/null
+++ b/src/libnr/in-svg-plane.h
@@ -0,0 +1,33 @@
+#ifndef SEEN_LIBNR_IN_SVG_PLANE_H
+#define SEEN_LIBNR_IN_SVG_PLANE_H
+
+#include "libnr/nr-point-fns.h"
+
+
+/**
+ * Returns true iff the coordinates of \a p are finite, non-NaN, and "small enough". Currently we
+ * use the magic number 1e18 for determining "small enough", as this number has in the past been
+ * used in sodipodi code as a sort of "infinity" value.
+ *
+ * For SVG Tiny output, we might choose a smaller value corresponding to the range of valid numbers
+ * in SVG Tiny (which uses fixed-point arithmetic).
+ */
+inline bool
+in_svg_plane(NR::Point const p)
+{
+ return NR::LInfty(p) < 1e18;
+}
+
+
+#endif /* !SEEN_LIBNR_IN_SVG_PLANE_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/libnr/libnr.def b/src/libnr/libnr.def
new file mode 100644
index 000000000..d8f224ca9
--- /dev/null
+++ b/src/libnr/libnr.def
@@ -0,0 +1,89 @@
+EXPORTS
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM
+ nr_R8G8B8A8_N_EMPTY_A8_RGBA32
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8
+ nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8
+ nr_R8G8B8A8_P_EMPTY_A8_RGBA32
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8
+ nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8
+; nr_R8G8B8_EMPTY_A8_RGBA32
+ nr_R8G8B8_R8G8B8_A8_RGBA32
+ nr_R8G8B8_R8G8B8_R8G8B8A8_N
+ nr_R8G8B8_R8G8B8_R8G8B8A8_P
+ nr_active_object_add_listener
+ nr_active_object_get_type
+ nr_active_object_remove_listener_by_data
+ nr_blit_pixblock_mask_rgba32
+ nr_blit_pixblock_pixblock_alpha
+ nr_blit_pixblock_pixblock_mask
+ nr_compose_pixblock_pixblock_pixel
+ nr_emit_fail_warning
+ nr_flat_free_list
+ nr_flat_free_one
+ nr_flat_insert_sorted
+ nr_flat_new_full
+ nr_lgradient_renderer_setup
+ nr_matrix_invert
+ nr_matrix_set_rotate
+ nr_matrix_set_scale
+ nr_matrix_set_translate
+ nr_matrix_multiply
+ nr_object_check_instance_cast
+ nr_object_check_instance_type
+ nr_object_delete
+ nr_object_get_type
+ nr_object_new
+ nr_object_ref
+ nr_object_register_type
+ nr_object_release
+ nr_object_setup
+ nr_object_unref
+ nr_path_duplicate_transform
+ nr_path_matrix_bbox_nion
+ nr_path_matrix_point_bbox_wind_distance
+ nr_pixblock_draw_line_rgba32
+ nr_pixblock_free
+ nr_pixblock_new
+ nr_pixblock_release
+ nr_pixblock_render_gray_noise
+ nr_pixblock_render_svp_mask_or
+ nr_pixblock_setup
+ nr_pixblock_setup_extern
+ nr_pixblock_setup_fast
+ nr_pixelstore_16K_free
+ nr_pixelstore_16K_new
+ nr_pixelstore_4K_free
+ nr_pixelstore_4K_new
+ nr_pixelstore_64K_free
+ nr_pixelstore_64K_new
+ nr_rect_d_intersect
+ nr_rect_d_matrix_transform
+ nr_rect_d_union
+ nr_rect_l_intersect
+ nr_rect_l_union
+ nr_rgradient_renderer_setup
+ nr_svp_bbox
+ nr_svp_free
+ nr_svp_point_distance
+ nr_svp_point_wind
+ nr_type_is_a
+ nr_vertex_free_list
+ nr_vertex_free_one
+ nr_vertex_new
+ nr_vertex_new_xy
+ nr_vertex_reverse_list
diff --git a/src/libnr/makefile.in b/src/libnr/makefile.in
new file mode 100644
index 000000000..8c80244ab
--- /dev/null
+++ b/src/libnr/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) libnr/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) libnr/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/libnr/n-art-bpath.h b/src/libnr/n-art-bpath.h
new file mode 100644
index 000000000..abce499e0
--- /dev/null
+++ b/src/libnr/n-art-bpath.h
@@ -0,0 +1,61 @@
+#ifndef SEEN_LIBNR_N_ART_BPATH_H
+#define SEEN_LIBNR_N_ART_BPATH_H
+
+/** \file
+ * NArtBpath: old-style path segment.
+ */
+
+#include "libnr/nr-point.h"
+#include "libnr/nr-path-code.h"
+
+/**
+ * Old-style path segment.
+ *
+ * Arrays of paths segment start with a MOVETO or MOVETO_OPEN segment
+ * where the former indicates the beginning of a closed subpath.
+ * \see subpath_from_bpath()
+ */
+class NArtBpath {
+public:
+ NRPathcode code; ///< Type of segment
+ double x1, y1; ///< Position of control point in case of NR_CURVETO
+ double x2, y2; ///< Position of control point in case of NR_CURVETO
+ double x3, y3; ///< Position of next point
+
+ /// Convert i-th position data pair to Point object
+ /// \pre 1 <= i <= 3
+ NR::Point c(unsigned const i) const {
+ switch (i) {
+ case 1: return NR::Point(x1, y1);
+ case 2: return NR::Point(x2, y2);
+ case 3: return NR::Point(x3, y3);
+ default: abort();
+ }
+ }
+
+ /// Set i-th position data pair from Point
+ /// \pre 1 <= i <= 3
+ void setC(unsigned const i, NR::Point const &p) {
+ using NR::X; using NR::Y;
+ switch (i) {
+ case 1: x1 = p[X]; y1 = p[Y]; break;
+ case 2: x2 = p[X]; y2 = p[Y]; break;
+ case 3: x3 = p[X]; y3 = p[Y]; break;
+ default: abort();
+ }
+ }
+};
+
+
+#endif /* !SEEN_LIBNR_N_ART_BPATH_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/libnr/nr-blit.cpp b/src/libnr/nr-blit.cpp
new file mode 100644
index 000000000..2a93bc9bd
--- /dev/null
+++ b/src/libnr/nr-blit.cpp
@@ -0,0 +1,300 @@
+#define __NR_BLIT_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include "nr-pixops.h"
+#include "nr-compose.h"
+#include "nr-blit.h"
+
+void
+nr_blit_pixblock_pixblock_alpha (NRPixBlock *d, NRPixBlock *s, unsigned int alpha)
+{
+ NRRectL clip;
+ unsigned char *dpx, *spx;
+ int dbpp, sbpp;
+ int w, h;
+
+ if (alpha == 0) return;
+ if (s->empty) return;
+ /* fixme: */
+ if (s->mode == NR_PIXBLOCK_MODE_A8) return;
+ /* fixme: */
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8) return;
+
+ /*
+ * Possible variants as of now:
+ *
+ * 0. SRC EP - DST EP *
+ * 1. SRC EP - DST EN *
+ * 2. SRC EP - DST P *
+ * 3. SRC EP - DST N *
+ * 4. SRC EN - DST EP *
+ * 5. SRC EN - DST EN *
+ * 6. SRC EN - DST P *
+ * 7. SRC EN - DST N *
+ * 8. SRC P - DST EP *
+ * 9. SRC P - DST EN *
+ * A. SRC P - DST P *
+ * B. SRC P - DST N *
+ * C. SRC N - DST EP *
+ * D. SRC N - DST EN *
+ * E. SRC N - DST P *
+ * F. SRC N - DST N *
+ *
+ */
+
+ nr_rect_l_intersect (&clip, &d->area, &s->area);
+
+ if (nr_rect_l_test_empty (&clip)) return;
+
+ /* Pointers */
+ dbpp = NR_PIXBLOCK_BPP (d);
+ dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + dbpp * (clip.x0 - d->area.x0);
+ sbpp = NR_PIXBLOCK_BPP (s);
+ spx = NR_PIXBLOCK_PX (s) + (clip.y0 - s->area.y0) * s->rs + sbpp * (clip.x0 - s->area.x0);
+ w = clip.x1 - clip.x0;
+ h = clip.y1 - clip.y0;
+
+ switch (d->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ /* No rendering into alpha at moment */
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ nr_R8G8B8_R8G8B8_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha);
+ } else {
+ nr_R8G8B8_R8G8B8_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha);
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ if (d->empty) {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* Case 8 */
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha);
+ } else {
+ /* Case C */
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha);
+ }
+ } else {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* case A */
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha);
+ } else {
+ /* case E */
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha);
+ }
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ if (d->empty) {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* Case 9 */
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha);
+ } else {
+ /* Case D */
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha);
+ }
+ } else {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* case B */
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha);
+ } else {
+ /* case F */
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha);
+ }
+ }
+ break;
+ }
+}
+
+void
+nr_blit_pixblock_pixblock_mask (NRPixBlock *d, NRPixBlock *s, NRPixBlock *m)
+{
+ NRRectL clip;
+ unsigned char *dpx, *spx, *mpx;
+ int dbpp, sbpp;
+ int w, h;
+
+ if (s->empty) return;
+ /* fixme: */
+ if (s->mode == NR_PIXBLOCK_MODE_A8) return;
+ /* fixme: */
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8) return;
+
+ /*
+ * Possible variants as of now:
+ *
+ * 0. SRC EP - DST EP *
+ * 1. SRC EP - DST EN *
+ * 2. SRC EP - DST P *
+ * 3. SRC EP - DST N *
+ * 4. SRC EN - DST EP *
+ * 5. SRC EN - DST EN *
+ * 6. SRC EN - DST P *
+ * 7. SRC EN - DST N *
+ * 8. SRC P - DST EP *
+ * 9. SRC P - DST EN *
+ * A. SRC P - DST P *
+ * B. SRC P - DST N *
+ * C. SRC N - DST EP *
+ * D. SRC N - DST EN *
+ * E. SRC N - DST P *
+ * F. SRC N - DST N *
+ *
+ */
+
+ nr_rect_l_intersect (&clip, &d->area, &s->area);
+ nr_rect_l_intersect (&clip, &clip, &m->area);
+
+ if (nr_rect_l_test_empty (&clip)) return;
+
+ /* Pointers */
+ dbpp = NR_PIXBLOCK_BPP (d);
+ dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + dbpp * (clip.x0 - d->area.x0);
+ sbpp = NR_PIXBLOCK_BPP (s);
+ spx = NR_PIXBLOCK_PX (s) + (clip.y0 - s->area.y0) * s->rs + sbpp * (clip.x0 - s->area.x0);
+ mpx = NR_PIXBLOCK_PX (m) + (clip.y0 - m->area.y0) * m->rs + 1 * (clip.x0 - m->area.x0);
+ w = clip.x1 - clip.x0;
+ h = clip.y1 - clip.y0;
+
+ switch (d->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ /* No rendering into alpha at moment */
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ } else {
+ nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ if (d->empty) {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* Case 8 */
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ } else {
+ /* Case C */
+ nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ }
+ } else {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* case A */
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ } else {
+ /* case E */
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ }
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ if (d->empty) {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* Case 9 */
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ } else {
+ /* Case D */
+ nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ }
+ } else {
+ if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ /* case B */
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ } else {
+ /* case F */
+ nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs);
+ }
+ }
+ break;
+ }
+}
+
+void
+nr_blit_pixblock_mask_rgba32 (NRPixBlock *d, NRPixBlock *m, unsigned long rgba)
+{
+ if (!(rgba & 0xff)) return;
+
+ if (m) {
+ NRRectL clip;
+ unsigned char *dpx, *mpx;
+ int w, h;
+
+ if (m->mode != NR_PIXBLOCK_MODE_A8) return;
+
+ if (!nr_rect_l_test_intersect (&d->area, &m->area)) return;
+
+ nr_rect_l_intersect (&clip, &d->area, &m->area);
+
+ /* Pointers */
+ dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + NR_PIXBLOCK_BPP (d) * (clip.x0 - d->area.x0);
+ mpx = NR_PIXBLOCK_PX (m) + (clip.y0 - m->area.y0) * m->rs + (clip.x0 - m->area.x0);
+ w = clip.x1 - clip.x0;
+ h = clip.y1 - clip.y0;
+
+ if (d->empty) {
+ if (d->mode == NR_PIXBLOCK_MODE_R8G8B8) {
+ nr_R8G8B8_R8G8B8_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ } else if (d->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ } else {
+ nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ }
+ d->empty = 0;
+ } else {
+ if (d->mode == NR_PIXBLOCK_MODE_R8G8B8) {
+ nr_R8G8B8_R8G8B8_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ } else if (d->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ } else {
+ nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba);
+ }
+ }
+ } else {
+ unsigned int r, g, b, a;
+ int x, y;
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+ for (y = d->area.y0; y < d->area.y1; y++) {
+ unsigned char *p;
+ p = NR_PIXBLOCK_PX (d) + (y - d->area.y0) * d->rs;
+ for (x = d->area.x0; x < d->area.x1; x++) {
+ unsigned int da;
+ switch (d->mode) {
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ 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;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ p[0] = NR_COMPOSENPP (r, a, p[0], p[3]);
+ p[1] = NR_COMPOSENPP (g, a, p[1], p[3]);
+ p[2] = NR_COMPOSENPP (b, a, p[2], p[3]);
+ p[3] = (65025 - (255 - a) * (255 - p[3]) + 127) / 255;
+ p += 4;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ da = 65025 - (255 - a) * (255 - p[3]);
+ p[0] = NR_COMPOSENNN_A7 (r, a, p[0], p[3], da);
+ p[1] = NR_COMPOSENNN_A7 (g, a, p[1], p[3], da);
+ p[2] = NR_COMPOSENNN_A7 (b, a, p[2], p[3], da);
+ p[3] = (da + 127) / 255;
+ p += 4;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/libnr/nr-blit.h b/src/libnr/nr-blit.h
new file mode 100644
index 000000000..3221c8187
--- /dev/null
+++ b/src/libnr/nr-blit.h
@@ -0,0 +1,32 @@
+#ifndef __NR_BLIT_H__
+#define __NR_BLIT_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+
+#define nr_blit_pixblock_pixblock(d,s) nr_blit_pixblock_pixblock_alpha (d, s, 255)
+
+void nr_blit_pixblock_pixblock_alpha (NRPixBlock *d, NRPixBlock *s, unsigned int alpha);
+void nr_blit_pixblock_pixblock_mask (NRPixBlock *d, NRPixBlock *s, NRPixBlock *m);
+void nr_blit_pixblock_mask_rgba32 (NRPixBlock *d, NRPixBlock *m, unsigned long rgba32);
+
+#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/libnr/nr-compose-transform.cpp b/src/libnr/nr-compose-transform.cpp
new file mode 100644
index 000000000..bb5022a74
--- /dev/null
+++ b/src/libnr/nr-compose-transform.cpp
@@ -0,0 +1,360 @@
+#define __NR_COMPOSE_TRANSFORM_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "nr-pixops.h"
+#include "nr-matrix.h"
+
+
+#ifdef WITH_MMX
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+/* fixme: */
+int nr_have_mmx (void);
+void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const long *FFd2s, unsigned int alpha);
+void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits);
+#define NR_PIXOPS_MMX (1 && nr_have_mmx ())
+#ifdef __cplusplus
+}
+#endif //__cplusplus
+#endif
+
+/* fixme: Implement missing (Lauris) */
+/* fixme: PREMUL colors before calculating average (Lauris) */
+
+/* Fixed point precision */
+#define FBITS 12
+
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd)
+{
+ int xsize, ysize, size, dbits;
+ long FFs_x_x, FFs_x_y, FFs_y_x, FFs_y_y, FFs__x, FFs__y;
+ long FFs_x_x_S, FFs_x_y_S, FFs_y_x_S, FFs_y_y_S;
+ /* Subpixel positions */
+ int FF_sx_S[256];
+ int FF_sy_S[256];
+ unsigned char *d0;
+ int FFsx0, FFsy0;
+ int x, y;
+
+ if (alpha == 0) return;
+
+ xsize = (1 << xd);
+ ysize = (1 << yd);
+ size = xsize * ysize;
+ dbits = xd + yd;
+
+ /* Set up fixed point matrix */
+ FFs_x_x = (long) (d2s[0] * (1 << FBITS) + 0.5);
+ FFs_x_y = (long) (d2s[1] * (1 << FBITS) + 0.5);
+ FFs_y_x = (long) (d2s[2] * (1 << FBITS) + 0.5);
+ FFs_y_y = (long) (d2s[3] * (1 << FBITS) + 0.5);
+ FFs__x = (long) (d2s[4] * (1 << FBITS) + 0.5);
+ FFs__y = (long) (d2s[5] * (1 << FBITS) + 0.5);
+
+ FFs_x_x_S = FFs_x_x >> xd;
+ FFs_x_y_S = FFs_x_y >> xd;
+ FFs_y_x_S = FFs_y_x >> yd;
+ FFs_y_y_S = FFs_y_y >> yd;
+
+ /* Set up subpixel matrix */
+ /* fixme: We can calculate that in floating point (Lauris) */
+ for (y = 0; y < ysize; y++) {
+ for (x = 0; x < xsize; x++) {
+ FF_sx_S[y * xsize + x] = FFs_x_x_S * x + FFs_y_x_S * y;
+ FF_sy_S[y * xsize + x] = FFs_x_y_S * x + FFs_y_y_S * y;
+ }
+ }
+
+ d0 = px;
+ FFsx0 = FFs__x;
+ FFsy0 = FFs__y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d;
+ long FFsx, FFsy;
+ d = d0;
+ FFsx = FFsx0;
+ FFsy = FFsy0;
+ for (x = 0; x < w; x++) {
+ unsigned int r, g, b, a;
+ long sx, sy;
+ int i;
+ r = g = b = a = 0;
+ for (i = 0; i < size; i++) {
+ sx = (FFsx + FF_sx_S[i]) >> FBITS;
+ if ((sx >= 0) && (sx < sw)) {
+ sy = (FFsy + FF_sy_S[i]) >> FBITS;
+ if ((sy >= 0) && (sy < sh)) {
+ const unsigned char *s;
+ unsigned int ca;
+ s = spx + sy * srs + sx * 4;
+ ca = NR_PREMUL (s[3], alpha);
+ r += NR_PREMUL (s[0], ca);
+ g += NR_PREMUL (s[1], ca);
+ b += NR_PREMUL (s[2], ca);
+ a += ca;
+ }
+ }
+ }
+ a >>= dbits;
+ if (a != 0) {
+ r = r >> dbits;
+ g = g >> dbits;
+ b = b >> dbits;
+ if (a == 255) {
+ /* Transparent BG, premul src */
+ d[0] = r;
+ d[1] = g;
+ d[2] = b;
+ d[3] = a;
+ } else {
+ unsigned int ca;
+ /* Full composition */
+ ca = 65025 - (255 - a) * (255 - d[3]);
+ d[0] = NR_COMPOSENNN_A7 (r, a, d[0], d[3], ca);
+ d[1] = NR_COMPOSENNN_A7 (g, a, d[1], d[3], ca);
+ d[2] = NR_COMPOSENNN_A7 (b, a, d[2], d[3], ca);
+ d[3] = (ca + 127) / 255;
+ }
+ }
+ /* Advance pointers */
+ FFsx += FFs_x_x;
+ FFsy += FFs_x_y;
+ d += 4;
+ }
+ FFsx0 += FFs_y_x;
+ FFsy0 += FFs_y_y;
+ d0 += rs;
+ }
+}
+
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+
+static void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const long *FFd2s, unsigned int alpha)
+{
+ unsigned char *d0;
+ int FFsx0, FFsy0;
+ int x, y;
+
+ d0 = px;
+ FFsx0 = FFd2s[4];
+ FFsy0 = FFd2s[5];
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d;
+ long FFsx, FFsy;
+ d = d0;
+ FFsx = FFsx0;
+ FFsy = FFsy0;
+ for (x = 0; x < w; x++) {
+ long sx, sy;
+ sx = FFsx >> FBITS;
+ if ((sx >= 0) && (sx < sw)) {
+ sy = FFsy >> FBITS;
+ if ((sy >= 0) && (sy < sh)) {
+ const unsigned char *s;
+ unsigned int a;
+ s = spx + sy * srs + sx * 4;
+ a = NR_PREMUL (s[3], alpha);
+ if (a != 0) {
+ if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, premul src */
+ d[0] = NR_PREMUL (s[0], a);
+ d[1] = NR_PREMUL (s[1], a);
+ d[2] = NR_PREMUL (s[2], a);
+ d[3] = a;
+ } else {
+ d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]);
+ d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]);
+ d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]);
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ }
+ }
+ }
+ }
+ /* Advance pointers */
+ FFsx += FFd2s[0];
+ FFsy += FFd2s[1];
+ d += 4;
+ }
+ FFsx0 += FFd2s[2];
+ FFsy0 += FFd2s[3];
+ d0 += rs;
+ }
+}
+
+static void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits)
+{
+ int size;
+ unsigned char *d0;
+ int FFsx0, FFsy0;
+ int x, y;
+
+ size = (1 << dbits);
+ unsigned alpha_rounding_fix = size * 255;
+ unsigned rgb_rounding_fix = size * (255 * 256);
+ if (alpha > 127) ++alpha;
+
+ d0 = px;
+ FFsx0 = FFd2s[4];
+ FFsy0 = FFd2s[5];
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d;
+ long FFsx, FFsy;
+ d = d0;
+ FFsx = FFsx0;
+ FFsy = FFsy0;
+ for (x = 0; x < w; x++) {
+ unsigned int r, g, b, a;
+ int i;
+ r = g = b = a = 0;
+ for (i = 0; i < size; i++) {
+ long sx, sy;
+ sx = (FFsx + FF_S[2 * i]) >> FBITS;
+ if ((sx >= 0) && (sx < sw)) {
+ sy = (FFsy + FF_S[2 * i + 1]) >> FBITS;
+ if ((sy >= 0) && (sy < sh)) {
+ const unsigned char *s;
+ unsigned int ca;
+ s = spx + sy * srs + sx * 4;
+ ca = s[3] * alpha;
+ r += s[0] * ca;
+ g += s[1] * ca;
+ b += s[2] * ca;
+ a += ca;
+ }
+ }
+ }
+ a = (a + alpha_rounding_fix) >> (8 + dbits);
+ if (a != 0) {
+ r = (r + rgb_rounding_fix) >> (16 + dbits);
+ g = (g + rgb_rounding_fix) >> (16 + dbits);
+ b = (b + rgb_rounding_fix) >> (16 + dbits);
+ if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, premul src */
+ d[0] = r;
+ d[1] = g;
+ d[2] = b;
+ d[3] = a;
+ } else {
+ d[0] = NR_COMPOSEPPP (r, a, d[0], d[3]);
+ d[1] = NR_COMPOSEPPP (g, a, d[1], d[3]);
+ d[2] = NR_COMPOSEPPP (b, a, d[2], d[3]);
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ }
+ }
+ /* Advance pointers */
+ FFsx += FFd2s[0];
+ FFsy += FFd2s[1];
+ d += 4;
+ }
+ FFsx0 += FFd2s[2];
+ FFsy0 += FFd2s[3];
+ d0 += rs;
+ }
+}
+
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd)
+{
+ int dbits;
+ long FFd2s[6];
+ int i;
+
+ if (alpha == 0) return;
+
+ dbits = xd + yd;
+
+ for (i = 0; i < 6; i++) {
+ FFd2s[i] = (long) (d2s[i] * (1 << FBITS) + 0.5);
+ }
+
+ if (dbits == 0) {
+#ifdef WITH_MMX
+ if (NR_PIXOPS_MMX) {
+ /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */
+ nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha);
+ return;
+ }
+#endif
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha);
+ } else {
+ int xsize, ysize;
+ long FFs_x_x_S, FFs_x_y_S, FFs_y_x_S, FFs_y_y_S;
+ long FF_S[2 * 256];
+ int x, y;
+
+ xsize = (1 << xd);
+ ysize = (1 << yd);
+
+ FFs_x_x_S = FFd2s[0] >> xd;
+ FFs_x_y_S = FFd2s[1] >> xd;
+ FFs_y_x_S = FFd2s[2] >> yd;
+ FFs_y_y_S = FFd2s[3] >> yd;
+
+ /* Set up subpixel matrix */
+ /* fixme: We can calculate that in floating point (Lauris) */
+ for (y = 0; y < ysize; y++) {
+ for (x = 0; x < xsize; x++) {
+ FF_S[2 * (y * xsize + x)] = FFs_x_x_S * x + FFs_y_x_S * y;
+ FF_S[2 * (y * xsize + x) + 1] = FFs_x_y_S * x + FFs_y_y_S * y;
+ }
+ }
+
+#ifdef WITH_MMX
+ if (NR_PIXOPS_MMX) {
+ /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */
+ nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits);
+ return;
+ }
+#endif
+ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits);
+ }
+}
+
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
diff --git a/src/libnr/nr-compose-transform.h b/src/libnr/nr-compose-transform.h
new file mode 100644
index 000000000..7ffb20074
--- /dev/null
+++ b/src/libnr/nr-compose-transform.h
@@ -0,0 +1,43 @@
+#ifndef __NR_COMPOSE_TRANSFORM_H__
+#define __NR_COMPOSE_TRANSFORM_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-forward.h>
+
+/* FINAL DST SRC */
+
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int sw, int sh, int srs,
+ const NR::Matrix &d2s, unsigned int alpha, int xd, int yd);
+
+#endif
diff --git a/src/libnr/nr-compose.cpp b/src/libnr/nr-compose.cpp
new file mode 100644
index 000000000..8eed6e84c
--- /dev/null
+++ b/src/libnr/nr-compose.cpp
@@ -0,0 +1,986 @@
+#define __NR_COMPOSE_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include "nr-pixops.h"
+
+#ifdef WITH_MMX
+/* fixme: */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+int nr_have_mmx (void);
+void nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned char *c);
+void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned char *c);
+void nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+#define NR_PIXOPS_MMX nr_have_mmx ()
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
+void
+nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ if (alpha == 0) {
+ memset (px, 0x0, 4 * w);
+ } else if (alpha == 255) {
+ memcpy (px, spx, 4 * w);
+ } else {
+ const unsigned char *s;
+ unsigned char *d;
+ d = px;
+ s = spx;
+ for (c = 0; c < w; c++) {
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = NR_PREMUL (*s, alpha);
+ s++;
+ }
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ if (alpha == 0) {
+ memset (px, 0x0, 4 * w);
+ } else {
+ const unsigned char *s;
+ unsigned char *d;
+ s = spx;
+ d = px;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = a;
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+ }
+}
+
+void
+nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = (s[3] * alpha + 127) / 255;
+ d[0] = (s[0] * a + 127) / 255;
+ d[1] = (s[1] * a + 127) / 255;
+ d[2] = (s[2] * a + 127) / 255;
+ d[3] = a;
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ if (alpha == 255) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ } else {
+ d[0] = NR_PREMUL (s[0], alpha);
+ d[1] = NR_PREMUL (s[1], alpha);
+ d[2] = NR_PREMUL (s[2], alpha);
+ d[3] = NR_PREMUL (s[3], alpha);
+ }
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Full coverage, COPY */
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = a;
+ } else {
+ unsigned int ca;
+ /* Full composition */
+ ca = 65025 - (255 - a) * (255 - d[3]);
+ d[0] = NR_COMPOSENNN_A7 (s[0], a, d[0], d[3], ca);
+ d[1] = NR_COMPOSENNN_A7 (s[1], a, d[1], d[3], ca);
+ d[2] = NR_COMPOSENNN_A7 (s[2], a, d[2], d[3], ca);
+ d[3] = (ca + 127) / 255;
+ }
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Full coverage, demul src */
+ d[0] = (s[0] * 255 + (s[3] >> 1)) / s[3];
+ d[1] = (s[1] * 255 + (s[3] >> 1)) / s[3];
+ d[2] = (s[2] * 255 + (s[3] >> 1)) / s[3];
+ d[3] = a;
+ } else {
+ if (alpha == 255) {
+ unsigned int ca;
+ /* Full composition */
+ ca = 65025 - (255 - s[3]) * (255 - d[3]);
+ d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca);
+ d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[1], d[3], ca);
+ d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[2], d[3], ca);
+ d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
+ } else {
+ // calculate premultiplied from two premultiplieds:
+ d[0] = NR_COMPOSEPPP(NR_PREMUL (s[0], alpha), a, NR_PREMUL (d[0], d[3]), 0); // last parameter not used
+ d[1] = NR_COMPOSEPPP(NR_PREMUL (s[1], alpha), a, NR_PREMUL (d[1], d[3]), 0);
+ d[2] = NR_COMPOSEPPP(NR_PREMUL (s[2], alpha), a, NR_PREMUL (d[2], d[3]), 0);
+ // total opacity:
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ // un-premultiply channels:
+ d[0] = d[0]*255/d[3];
+ d[1] = d[1]*255/d[3];
+ d[2] = d[2]*255/d[3];
+ }
+ }
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, premul src */
+ d[0] = NR_PREMUL (s[0], a);
+ d[1] = NR_PREMUL (s[1], a);
+ d[2] = NR_PREMUL (s[2], a);
+ d[3] = a;
+ } else {
+ d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]);
+ d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]);
+ d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]);
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ }
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, COPY */
+ d[0] = NR_PREMUL (s[0], alpha);
+ d[1] = NR_PREMUL (s[1], alpha);
+ d[2] = NR_PREMUL (s[2], alpha);
+ d[3] = NR_PREMUL (s[3], alpha);
+ } else {
+ if (alpha == 255) {
+ /* Simple */
+ d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]);
+ d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]);
+ d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]);
+ d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
+ } else {
+ unsigned int c;
+ c = NR_PREMUL (s[0], alpha);
+ d[0] = NR_COMPOSEPPP (c, a, d[0], d[3]);
+ c = NR_PREMUL (s[1], alpha);
+ d[1] = NR_COMPOSEPPP (c, a, d[1], d[3]);
+ c = NR_PREMUL (s[2], alpha);
+ d[2] = NR_COMPOSEPPP (c, a, d[2], d[3]);
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ }
+ }
+ d += 4;
+ s += 4;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+/* Masked operations */
+
+void
+nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int x, y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (x = 0; x < w; x++) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = (s[3] * m[0] + 127) / 255;
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int x, y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (x = 0; x < w; x++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a == 0) {
+ d[3] = 0;
+ } else {
+ d[0] = (s[0] * 255 + (a >> 1)) / a;
+ d[1] = (s[1] * 255 + (a >> 1)) / a;
+ d[2] = (s[2] * 255 + (a >> 1)) / a;
+ d[3] = a;
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ d[0] = NR_PREMUL (s[0], a);
+ d[1] = NR_PREMUL (s[1], a);
+ d[2] = NR_PREMUL (s[2], a);
+ d[3] = a;
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ if (m[0] == 255) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ } else {
+ d[0] = NR_PREMUL (s[0], m[0]);
+ d[1] = NR_PREMUL (s[1], m[0]);
+ d[2] = NR_PREMUL (s[2], m[0]);
+ d[3] = NR_PREMUL (s[3], m[0]);
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Full coverage, COPY */
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = a;
+ } else {
+ unsigned int ca;
+ /* Full composition */
+ ca = 65025 - (255 - a) * (255 - d[3]);
+ d[0] = NR_COMPOSENNN_A7 (s[0], a, d[0], d[3], ca);
+ d[1] = NR_COMPOSENNN_A7 (s[1], a, d[1], d[3], ca);
+ d[2] = NR_COMPOSENNN_A7 (s[2], a, d[2], d[3], ca);
+ d[3] = (ca + 127) / 255;
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Full coverage, demul src */
+ d[0] = (s[0] * 255 + (s[3] >> 1)) / s[3];
+ d[1] = (s[1] * 255 + (s[3] >> 1)) / s[3];
+ d[2] = (s[2] * 255 + (s[3] >> 1)) / s[3];
+ d[3] = a;
+ } else {
+ if (m[0] == 255) {
+ unsigned int ca;
+ /* Full composition */
+ ca = 65025 - (255 - s[3]) * (255 - d[3]);
+ d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca);
+ d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[1], d[3], ca);
+ d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[2], d[3], ca);
+ d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
+ } else {
+ // calculate premultiplied from two premultiplieds:
+ d[0] = NR_COMPOSEPPP(NR_PREMUL (s[0], m[0]), a, NR_PREMUL (d[0], d[3]), 0); // last parameter not used
+ d[1] = NR_COMPOSEPPP(NR_PREMUL (s[1], m[0]), a, NR_PREMUL (d[1], d[3]), 0);
+ d[2] = NR_COMPOSEPPP(NR_PREMUL (s[2], m[0]), a, NR_PREMUL (d[2], d[3]), 0);
+ // total opacity:
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ // un-premultiply channels:
+ d[0] = d[0]*255/d[3];
+ d[1] = d[1]*255/d[3];
+ d[2] = d[2]*255/d[3];
+ }
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, premul src */
+ d[0] = NR_PREMUL (s[0], a);
+ d[1] = NR_PREMUL (s[1], a);
+ d[2] = NR_PREMUL (s[2], a);
+ d[3] = a;
+ } else {
+ d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]);
+ d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]);
+ d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]);
+ d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255;
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a == 0) {
+ /* Transparent FG, NOP */
+ } else if ((a == 255) || (d[3] == 0)) {
+ /* Transparent BG, COPY */
+ d[0] = NR_PREMUL (s[0], m[0]);
+ d[1] = NR_PREMUL (s[1], m[0]);
+ d[2] = NR_PREMUL (s[2], m[0]);
+ d[3] = NR_PREMUL (s[3], m[0]);
+ } else {
+ if (m[0] == 255) {
+ /* Simple */
+ d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]);
+ d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]);
+ d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]);
+ d[3] = NR_A7_NORMALIZED(s[3], d[3]);
+ } else {
+ unsigned int c;
+ c = NR_PREMUL (s[0], m[0]);
+ d[0] = NR_COMPOSEPPP (c, a, d[0], d[3]);
+ c = NR_PREMUL (s[1], m[0]);
+ d[1] = NR_COMPOSEPPP (c, a, d[1], d[3]);
+ c = NR_PREMUL (s[2], m[0]);
+ d[2] = NR_COMPOSEPPP (c, a, d[2], d[3]);
+ d[3] = NR_A7_NORMALIZED(a, d[3]);
+ }
+ }
+ d += 4;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba)
+{
+ unsigned int r, g, b, a;
+ int x, y;
+
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+
+ if (a == 0) return;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (x = 0; x < w; x++) {
+ d[0] = r;
+ d[1] = g;
+ d[2] = b;
+ d[3] = NR_PREMUL (s[0], a);
+ d += 4;
+ s += 1;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba)
+{
+ unsigned int r, g, b, a;
+ int x, y;
+
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+
+ if (a == 0) return;
+
+#ifdef WITH_MMX
+ if (NR_PIXOPS_MMX) {
+ unsigned char c[4];
+ c[0] = NR_PREMUL (r, a);
+ c[1] = NR_PREMUL (g, a);
+ c[2] = NR_PREMUL (b, a);
+ c[3] = a;
+ /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */
+ nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP (px, w, h, rs, spx, srs, c);
+ return;
+ }
+#endif
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (x = 0; x < w; x++) {
+ unsigned int ca;
+ ca = s[0] * a;
+ d[0] = (r * ca + 32512) / 65025;
+ d[1] = (g * ca + 32512) / 65025;
+ d[2] = (b * ca + 32512) / 65025;
+ d[3] = (ca + 127) / 255;
+ d += 4;
+ s += 1;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8_R8G8B8_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba)
+{
+ unsigned int r, g, b, a;
+ int x, y;
+
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+
+ if (a == 0) return;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *m;
+ d = (unsigned char *) px;
+ m = (unsigned char *) mpx;
+ for (x = 0; x < w; x++) {
+ unsigned int alpha;
+ alpha = NR_PREMUL (a, m[0]);
+ d[0] = NR_COMPOSEN11 (r, alpha, d[0]);
+ d[1] = NR_COMPOSEN11 (g, alpha, d[1]);
+ d[2] = NR_COMPOSEN11 (b, alpha, d[2]);
+ d += 3;
+ m += 1;
+ }
+ px += rs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba)
+{
+ unsigned int r, g, b, a;
+ int x, y;
+
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+
+ if (a == 0) return;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (x = 0; x < w; x++) {
+ unsigned int ca;
+ ca = NR_PREMUL (s[0], a);
+ if (ca == 0) {
+ /* Transparent FG, NOP */
+ } else if ((ca == 255) || (d[3] == 0)) {
+ /* Full coverage, COPY */
+ d[0] = r;
+ d[1] = g;
+ d[2] = b;
+ d[3] = ca;
+ } else {
+ unsigned int da;
+ /* Full composition */
+ da = 65025 - (255 - ca) * (255 - d[3]);
+ d[0] = NR_COMPOSENNN_A7 (r, ca, d[0], d[3], da);
+ d[1] = NR_COMPOSENNN_A7 (g, ca, d[1], d[3], da);
+ d[2] = NR_COMPOSENNN_A7 (b, ca, d[2], d[3], da);
+ d[3] = (da + 127) / 255;
+ }
+ d += 4;
+ s += 1;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba)
+{
+ unsigned int r, g, b, a;
+ int x, y;
+
+ if (!(rgba & 0xff)) return;
+
+ r = NR_RGBA32_R (rgba);
+ g = NR_RGBA32_G (rgba);
+ b = NR_RGBA32_B (rgba);
+ a = NR_RGBA32_A (rgba);
+
+#ifdef WITH_MMX
+ if (NR_PIXOPS_MMX) {
+ unsigned char c[4];
+ c[0] = NR_PREMUL (r, a);
+ c[1] = NR_PREMUL (g, a);
+ c[2] = NR_PREMUL (b, a);
+ c[3] = a;
+ /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */
+ nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP (px, w, h, rs, spx, srs, c);
+ return;
+ }
+#endif
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ for (x = 0; x < w; x++) {
+ unsigned int ca;
+ ca = NR_PREMUL (s[0], a);
+ if (ca == 0) {
+ /* Transparent FG, NOP */
+ } else if ((ca == 255) || (d[3] == 0)) {
+ /* Full coverage, COPY */
+ d[0] = NR_PREMUL (r, ca);
+ d[1] = NR_PREMUL (g, ca);
+ d[2] = NR_PREMUL (b, ca);
+ d[3] = ca;
+ } else {
+ /* Full composition */
+ d[0] = NR_COMPOSENPP (r, ca, d[0], d[3]);
+ d[1] = NR_COMPOSENPP (g, ca, d[1], d[3]);
+ d[2] = NR_COMPOSENPP (b, ca, d[2], d[3]);
+ d[3] = (65025 - (255 - ca) * (255 - d[3]) + 127) / 255;
+ }
+ d += 4;
+ s += 1;
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+/* RGB */
+
+void
+nr_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ if (alpha == 0) return;
+
+#ifdef WITH_MMX
+ if (NR_PIXOPS_MMX) {
+ /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */
+ nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P (px, w, h, rs, spx, srs, alpha);
+ return;
+ }
+#endif
+
+ for (r = 0; r < h; r++) {
+ const unsigned char *s;
+ unsigned char *d;
+ if (alpha == 255) {
+ d = px;
+ s = spx;
+ for (c = 0; c < w; c++) {
+ if (s[3] == 0) {
+ /* NOP */
+ } else if (s[3] == 255) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ } else {
+ d[0] = NR_COMPOSEP11 (s[0], s[3], d[0]);
+ d[1] = NR_COMPOSEP11 (s[1], s[3], d[1]);
+ d[2] = NR_COMPOSEP11 (s[2], s[3], d[2]);
+ }
+ d += 3;
+ s += 4;
+ }
+ } else {
+ d = px;
+ s = spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* NOP */
+ } else {
+ d[0] = NR_COMPOSEP11 (s[0], a, d[0]);
+ d[1] = NR_COMPOSEP11 (s[1], a, d[1]);
+ d[2] = NR_COMPOSEP11 (s[2], a, d[2]);
+ }
+ /* a == 255 is impossible, because alpha < 255 */
+ d += 3;
+ s += 4;
+ }
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8_R8G8B8_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha)
+{
+ int r, c;
+
+ for (r = 0; r < h; r++) {
+ const unsigned char *s;
+ unsigned char *d;
+ if (alpha == 0) {
+ /* NOP */
+ } else if (alpha == 255) {
+ d = px;
+ s = spx;
+ for (c = 0; c < w; c++) {
+ if (s[3] == 0) {
+ /* NOP */
+ } else if (s[3] == 255) {
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ } else {
+ 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;
+ s += 4;
+ }
+ } else {
+ d = px;
+ s = spx;
+ for (c = 0; c < w; c++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], alpha);
+ if (a == 0) {
+ /* NOP */
+ } else {
+ d[0] = NR_COMPOSEN11 (s[0], a, d[0]);
+ d[1] = NR_COMPOSEN11 (s[1], a, d[1]);
+ d[2] = NR_COMPOSEN11 (s[2], a, d[2]);
+ }
+ /* a == 255 is impossible, because alpha < 255 */
+ d += 3;
+ s += 4;
+ }
+ }
+ px += rs;
+ spx += srs;
+ }
+}
+
+void
+nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int x, y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (x = 0; x < w; x++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a != 0) {
+ unsigned int r, g, b;
+ r = NR_PREMUL (s[0], m[0]);
+ d[0] = NR_COMPOSEP11 (r, a, d[0]);
+ g = NR_PREMUL (s[1], m[0]);
+ d[1] = NR_COMPOSEP11 (g, a, d[1]);
+ b = NR_PREMUL (s[2], m[0]);
+ d[2] = NR_COMPOSEP11 (b, a, d[2]);
+ }
+ d += 3;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+void
+nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs)
+{
+ int x, y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *d, *s, *m;
+ d = (unsigned char *) px;
+ s = (unsigned char *) spx;
+ m = (unsigned char *) mpx;
+ for (x = 0; x < w; x++) {
+ unsigned int a;
+ a = NR_PREMUL (s[3], m[0]);
+ if (a != 0) {
+ d[0] = NR_COMPOSEP11 (s[0], a, d[0]);
+ d[1] = NR_COMPOSEP11 (s[1], a, d[1]);
+ d[2] = NR_COMPOSEP11 (s[2], a, d[2]);
+ }
+ d += 3;
+ s += 4;
+ m += 1;
+ }
+ px += rs;
+ spx += srs;
+ mpx += mrs;
+ }
+}
+
+
diff --git a/src/libnr/nr-compose.h b/src/libnr/nr-compose.h
new file mode 100644
index 000000000..ccdb52cb0
--- /dev/null
+++ b/src/libnr/nr-compose.h
@@ -0,0 +1,69 @@
+#ifndef __NR_COMPOSE_H__
+#define __NR_COMPOSE_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+/* FINAL DST SRC */
+
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+
+/* FINAL DST SRC MASK */
+
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int srs,
+ const unsigned char *mpx, int mrs);
+void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int srs,
+ const unsigned char *mpx, int mrs);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int srs,
+ const unsigned char *mpx, int mrs);
+void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs,
+ const unsigned char *spx, int srs,
+ const unsigned char *mpx, int mrs);
+
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (unsigned char *p, int w, int h, int rs,
+ const unsigned char *s, int srs,
+ const unsigned char *m, int mrs);
+void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (unsigned char *p, int w, int h, int rs,
+ const unsigned char *s, int srs,
+ const unsigned char *m, int mrs);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (unsigned char *p, int w, int h, int rs,
+ const unsigned char *s, int srs,
+ const unsigned char *m, int mrs);
+void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (unsigned char *p, int w, int h, int rs,
+ const unsigned char *s, int srs,
+ const unsigned char *m, int mrs);
+
+/* FINAL DST MASK COLOR */
+
+void nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba);
+void nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba);
+
+void nr_R8G8B8_R8G8B8_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba);
+void nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba);
+void nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba);
+
+/* RGB */
+
+void nr_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8_R8G8B8_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha);
+void nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs);
+void nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs);
+
+#endif
diff --git a/src/libnr/nr-convex-hull-ops.h b/src/libnr/nr-convex-hull-ops.h
new file mode 100644
index 000000000..2e96bf367
--- /dev/null
+++ b/src/libnr/nr-convex-hull-ops.h
@@ -0,0 +1,29 @@
+#ifndef SEEN_NR_CONVEX_HULL_FNS_H
+#define SEEN_NR_CONVEX_HULL_FNS_H
+
+/* ex:set et ts=4 sw=4: */
+
+/*
+ * A class representing the convex hull of a set of points.
+ *
+ * Copyright 2004 MenTaLguY <mental@rydia.net>
+ *
+ * This code is licensed under the GNU GPL; see COPYING for more information.
+ */
+
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-convex-hull.h>
+
+namespace NR {
+
+ConvexHull operator*(const Rect &r, const Matrix &m) {
+ ConvexHull points(r.corner(0));
+ for ( unsigned i = 1 ; i < 4 ; i++ ) {
+ points.add(r.corner(i));
+ }
+ return points;
+}
+
+} /* namespace NR */
+
+#endif
diff --git a/src/libnr/nr-convex-hull.h b/src/libnr/nr-convex-hull.h
new file mode 100644
index 000000000..28cde376d
--- /dev/null
+++ b/src/libnr/nr-convex-hull.h
@@ -0,0 +1,49 @@
+#ifndef SEEN_NR_CONVEX_HULL_H
+#define SEEN_NR_CONVEX_HULL_H
+
+/* ex:set et ts=4 sw=4: */
+
+/*
+ * A class representing the convex hull of a set of points.
+ *
+ * Copyright 2004 MenTaLguY <mental@rydia.net>
+ *
+ * This code is licensed under the GNU GPL; see COPYING for more information.
+ */
+
+#include <libnr/nr-rect.h>
+
+namespace NR {
+
+class ConvexHull {
+public:
+ explicit ConvexHull(Point const &p) : _bounds(p, p) {}
+
+ Point midpoint() const {
+ return _bounds.midpoint();
+ }
+
+ void add(Point const &p) {
+ _bounds.expandTo(p);
+ }
+ void add(Rect const &p) {
+ // Note that this is a hack. when convexhull actually works
+ // you will need to add all four points.
+ _bounds.expandTo(p.min());
+ _bounds.expandTo(p.max());
+ }
+ void add(ConvexHull const &h) {
+ _bounds.expandTo(h._bounds);
+ }
+
+ Rect const &bounds() const {
+ return _bounds;
+ }
+
+private:
+ Rect _bounds;
+};
+
+} /* namespace NR */
+
+#endif
diff --git a/src/libnr/nr-coord.h b/src/libnr/nr-coord.h
new file mode 100644
index 000000000..668e2b460
--- /dev/null
+++ b/src/libnr/nr-coord.h
@@ -0,0 +1,29 @@
+#ifndef SEEN_NR_COORD_H
+#define SEEN_NR_COORD_H
+
+namespace NR {
+
+/**
+ * A "real" type with sufficient precision for coordinates.
+ *
+ * You may safely assume that double (or even float) provides enough precision for storing
+ * on-canvas points, and hence that double provides enough precision for dot products of
+ * differences of on-canvas points.
+ */
+typedef double Coord;
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_COORD_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/libnr/nr-dim2.h b/src/libnr/nr-dim2.h
new file mode 100644
index 000000000..d06fd4227
--- /dev/null
+++ b/src/libnr/nr-dim2.h
@@ -0,0 +1,22 @@
+#ifndef SEEN_NR_DIM2_H
+#define SEEN_NR_DIM2_H
+
+namespace NR {
+
+enum Dim2 { X=0, Y };
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_DIM2_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/libnr/nr-forward.h b/src/libnr/nr-forward.h
new file mode 100644
index 000000000..cbc4d8eec
--- /dev/null
+++ b/src/libnr/nr-forward.h
@@ -0,0 +1,42 @@
+#ifndef __NR_FORWARD_H__
+#define __NR_FORWARD_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+namespace NR {
+class Matrix;
+class Point;
+class Rect;
+class rotate;
+class scale;
+class translate;
+}
+
+class NArtBpath;
+struct NRBPath;
+struct NRPixBlock;
+struct NRMatrix;
+struct NRPoint;
+struct NRRect;
+struct NRRectL;
+
+
+#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/libnr/nr-gradient.cpp b/src/libnr/nr-gradient.cpp
new file mode 100644
index 000000000..fa4f9f91f
--- /dev/null
+++ b/src/libnr/nr-gradient.cpp
@@ -0,0 +1,317 @@
+#define __NR_GRADIENT_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixops.h>
+#include <libnr/nr-pixblock-pixel.h>
+#include <libnr/nr-blit.h>
+#include <libnr/nr-gradient.h>
+
+#define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1)
+#define NRG_2MASK ((long long) ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1))
+
+/* Radial */
+
+static void nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
+static void nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
+static void nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
+static void nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb);
+static void nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb);
+
+NRRenderer *
+nr_rgradient_renderer_setup(NRRGradientRenderer *rgr,
+ unsigned char const *cv,
+ unsigned spread,
+ NRMatrix const *gs2px,
+ float cx, float cy,
+ float fx, float fy,
+ float r)
+{
+ rgr->vector = cv;
+ rgr->spread = spread;
+
+ if (r < NR_EPSILON) {
+ rgr->renderer.render = nr_rgradient_render_block_end;
+ } else if (NR_DF_TEST_CLOSE(cx, fx, NR_EPSILON) &&
+ NR_DF_TEST_CLOSE(cy, fy, NR_EPSILON)) {
+ rgr->renderer.render = nr_rgradient_render_block_symmetric;
+
+ nr_matrix_invert(&rgr->px2gs, gs2px);
+ rgr->px2gs.c[0] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+ rgr->px2gs.c[1] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+ rgr->px2gs.c[2] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+ rgr->px2gs.c[3] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+ rgr->px2gs.c[4] -= cx;
+ rgr->px2gs.c[5] -= cy;
+ rgr->px2gs.c[4] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+ rgr->px2gs.c[5] *= (NR_GRADIENT_VECTOR_LENGTH / r);
+
+ rgr->cx = 0.0;
+ rgr->cy = 0.0;
+ rgr->fx = rgr->cx;
+ rgr->fy = rgr->cy;
+ rgr->r = 1.0;
+ } else {
+ rgr->renderer.render = nr_rgradient_render_block_optimized;
+
+ NR::Coord const df = hypot(fx - cx, fy - cy);
+ if (df >= r) {
+ fx = cx + (fx - cx ) * r / (float) df;
+ fy = cy + (fy - cy ) * r / (float) df;
+ }
+
+ NRMatrix n2gs;
+ n2gs.c[0] = cx - fx;
+ n2gs.c[1] = cy - fy;
+ n2gs.c[2] = cy - fy;
+ n2gs.c[3] = fx - cx;
+ n2gs.c[4] = fx;
+ n2gs.c[5] = fy;
+
+ NRMatrix n2px;
+ nr_matrix_multiply(&n2px, &n2gs, gs2px);
+ nr_matrix_invert(&rgr->px2gs, &n2px);
+
+ rgr->cx = 1.0;
+ rgr->cy = 0.0;
+ rgr->fx = 0.0;
+ rgr->fy = 0.0;
+ rgr->r = r / (float) hypot(fx - cx, fy - cy);
+ rgr->C = 1.0F - rgr->r * rgr->r;
+ /* INVARIANT: C < 0 */
+ rgr->C = MIN(rgr->C, -NR_EPSILON);
+ }
+
+ return (NRRenderer *) rgr;
+}
+
+static void
+nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
+{
+ NRRGradientRenderer *rgr = (NRRGradientRenderer *) r;
+ nr_rgradient_render_generic_symmetric(rgr, pb);
+}
+
+static void
+nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
+{
+ NRRGradientRenderer *rgr = (NRRGradientRenderer *) r;
+ nr_rgradient_render_generic_optimized(rgr, pb);
+}
+
+static void
+nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
+{
+ unsigned char const *c = ((NRRGradientRenderer *) r)->vector + 4 * (NR_GRADIENT_VECTOR_LENGTH - 1);
+
+ nr_blit_pixblock_mask_rgba32(pb, m, (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]);
+}
+
+/*
+ * The archetype is following
+ *
+ * gx gy - pixel coordinates
+ * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle
+ *
+ * (1) (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx)
+ * (2) (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r
+ *
+ * (3) Py = (Px - fx) * (gy - fy) / (gx - fx) + fy
+ * (4) (gy - fy) / (gx - fx) = D
+ * (5) Py = D * Px - D * fx + fy
+ *
+ * (6) D * fx - fy + cy = N
+ * (7) Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r
+ * (8) (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r
+ *
+ * (9) A = D * D + 1
+ * (10) B = -2 * (cx + D * N)
+ * (11) C = cx * cx + N * N - r * r
+ *
+ * (12) Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A
+ */
+
+static void
+nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb)
+{
+ NR::Coord const dx = rgr->px2gs.c[0];
+ NR::Coord const dy = rgr->px2gs.c[1];
+
+ if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
+ for (int y = pb->area.y0; y < pb->area.y1; y++) {
+ unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
+ NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
+ NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
+ for (int x = pb->area.x0; x < pb->area.x1; x++) {
+ NR::Coord const pos = hypot(gx, gy);
+ int idx;
+ if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
+ idx = (int) ((long long) pos & NRG_MASK);
+ } else {
+ idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
+ }
+ unsigned char const *s = rgr->vector + 4 * idx;
+ d[0] = NR_COMPOSENPP(s[0], s[3], d[0], d[3]);
+ d[1] = NR_COMPOSENPP(s[1], s[3], d[1], d[3]);
+ d[2] = NR_COMPOSENPP(s[2], s[3], d[2], d[3]);
+ d[3] = (255*255 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
+ d += 4;
+ gx += dx;
+ gy += dy;
+ }
+ }
+ } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) {
+ for (int y = pb->area.y0; y < pb->area.y1; y++) {
+ unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
+ NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
+ NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
+ for (int x = pb->area.x0; x < pb->area.x1; x++) {
+ NR::Coord const pos = hypot(gx, gy);
+ int idx;
+ if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
+ idx = (int) ((long long) pos & NRG_MASK);
+ } else {
+ idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
+ }
+ unsigned char const *s = rgr->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) {
+ unsigned ca = 255*255 - (255 - s[3]) * (255 - 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] = (ca + 127) / 255;
+ }
+ d += 4;
+ gx += dx;
+ gy += dy;
+ }
+ }
+ } else {
+ NRPixBlock spb;
+ nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
+ (unsigned char *) rgr->vector,
+ 4 * NR_GRADIENT_VECTOR_LENGTH,
+ 0, 0);
+ int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
+ ? 1
+ : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
+ ? 3
+ : 4 );
+
+ for (int y = pb->area.y0; y < pb->area.y1; y++) {
+ unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
+ NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
+ NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
+ for (int x = pb->area.x0; x < pb->area.x1; x++) {
+ NR::Coord const pos = hypot(gx, gy);
+ int idx;
+ if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
+ idx = (int) ((long long) pos & NRG_MASK);
+ } else {
+ idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
+ }
+ unsigned char const *s = rgr->vector + 4 * idx;
+ nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
+ d += bpp;
+ gx += dx;
+ gy += dy;
+ }
+ }
+
+ nr_pixblock_release(&spb);
+ }
+}
+
+static void
+nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb)
+{
+ int const x0 = pb->area.x0;
+ int const y0 = pb->area.y0;
+ int const x1 = pb->area.x1;
+ int const y1 = pb->area.y1;
+ int const rs = pb->rs;
+
+ NRPixBlock spb;
+ nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
+ (unsigned char *) rgr->vector,
+ 4 * NR_GRADIENT_VECTOR_LENGTH,
+ 0, 0);
+ int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
+ ? 1
+ : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
+ ? 3
+ : 4 );
+
+ for (int y = y0; y < y1; y++) {
+ unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs;
+ NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
+ NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
+ NR::Coord const dx = rgr->px2gs.c[0];
+ NR::Coord const dy = rgr->px2gs.c[1];
+ for (int x = x0; x < x1; x++) {
+ NR::Coord const gx2 = gx * gx;
+ NR::Coord const gxy2 = gx2 + gy * gy;
+ NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2;
+ /* INVARIANT: qgx2_4 >= 0.0 */
+ /* qgx2_4 = MAX(qgx2_4, 0.0); */
+ NR::Coord const pxgx = gx + sqrt(qgx2_4);
+ /* We can safely divide by 0 here */
+ /* If we are sure pxgx cannot be -0 */
+ NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH;
+ int idx;
+ if (pos < (1U << 31)) {
+ if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
+ idx = (int) ((long long) pos & NRG_2MASK);
+ if (idx > NRG_MASK) idx = NRG_2MASK - idx;
+ } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
+ idx = (int) ((long long) pos & NRG_MASK);
+ } else {
+ idx = (int) CLAMP(pos, 0, (double) (NR_GRADIENT_VECTOR_LENGTH - 1));
+ }
+ } else {
+ idx = NR_GRADIENT_VECTOR_LENGTH - 1;
+ }
+ unsigned char const *s = rgr->vector + 4 * idx;
+ nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
+ d += bpp;
+
+ gx += dx;
+ gy += dy;
+ }
+ }
+
+ nr_pixblock_release(&spb);
+}
+
+
+/*
+ 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/libnr/nr-gradient.h b/src/libnr/nr-gradient.h
new file mode 100644
index 000000000..f9cb1ba45
--- /dev/null
+++ b/src/libnr/nr-gradient.h
@@ -0,0 +1,47 @@
+#ifndef __NR_GRADIENT_H__
+#define __NR_GRADIENT_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-render.h>
+
+#define NR_GRADIENT_VECTOR_LENGTH 1024
+
+enum {
+ NR_GRADIENT_SPREAD_PAD,
+ NR_GRADIENT_SPREAD_REFLECT,
+ NR_GRADIENT_SPREAD_REPEAT
+};
+
+/* Radial */
+
+struct NRRGradientRenderer {
+ NRRenderer renderer;
+ const unsigned char *vector;
+ unsigned int spread;
+ NRMatrix px2gs;
+ float cx, cy;
+ float fx, fy;
+ float r;
+ float C;
+};
+
+NRRenderer *nr_rgradient_renderer_setup (NRRGradientRenderer *rgr,
+ const unsigned char *cv,
+ unsigned int spread,
+ const NRMatrix *gs2px,
+ float cx, float cy,
+ float fx, float fy,
+ float r);
+
+
+
+#endif
diff --git a/src/libnr/nr-i-coord.h b/src/libnr/nr-i-coord.h
new file mode 100644
index 000000000..f87dea3d5
--- /dev/null
+++ b/src/libnr/nr-i-coord.h
@@ -0,0 +1,25 @@
+#ifndef SEEN_NR_I_COORD_H
+#define SEEN_NR_I_COORD_H
+
+#include <glib/gtypes.h>
+
+namespace NR {
+
+/** An integer type with sufficient precision for coordinates. */
+typedef gint32 ICoord;
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_I_COORD_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/libnr/nr-macros.h b/src/libnr/nr-macros.h
new file mode 100644
index 000000000..74a627ae7
--- /dev/null
+++ b/src/libnr/nr-macros.h
@@ -0,0 +1,71 @@
+#ifndef __NR_MACROS_H__
+#define __NR_MACROS_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <math.h>
+
+#if HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <assert.h>
+
+#define nr_new(t,n) ((t *) malloc ((n) * sizeof (t)))
+#define nr_free free
+#define nr_renew(p,t,n) ((t *) realloc (p, (n) * sizeof (t)))
+
+#ifndef TRUE
+#define TRUE (!0)
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef MAX
+#define MAX(a,b) (((a) < (b)) ? (b) : (a))
+#endif
+#ifndef MIN
+#define MIN(a,b) (((a) > (b)) ? (b) : (a))
+#endif
+
+#ifndef CLAMP
+/** Returns v bounded to within [a, b]. If v is NaN then returns a.
+ *
+ * \pre \a a \<= \a b.
+ */
+# define CLAMP(v,a,b) \
+ (assert (a <= b), \
+ ((v) >= (a)) \
+ ? (((v) > (b)) \
+ ? (b) \
+ : (v)) \
+ : (a))
+#endif
+
+#define NR_DF_TEST_CLOSE(a,b,e) (fabs ((a) - (b)) <= (e))
+
+// Todo: move these into nr-matrix.h
+#define NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(a,b,e) (NR_DF_TEST_CLOSE ((*(a))[0], (*(b))[0], e) && \
+ NR_DF_TEST_CLOSE ((*(a))[1], (*(b))[1], e) && \
+ NR_DF_TEST_CLOSE ((*(a))[2], (*(b))[2], e) && \
+ NR_DF_TEST_CLOSE ((*(a))[3], (*(b))[3], e))
+#define NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(a,b,e) (NR_DF_TEST_CLOSE ((*(a))[4], (*(b))[4], e) && \
+ NR_DF_TEST_CLOSE ((*(a))[5], (*(b))[5], e))
+#define NR_MATRIX_DF_TEST_CLOSE(a,b,e) (NR_MATRIX_DF_TEST_TRANSLATE_CLOSE (a, b, e) && \
+ NR_MATRIX_DF_TEST_TRANSFORM_CLOSE (a, b, e))
+
+#define NR_RECT_DFLS_TEST_EMPTY(a) (((a)->x0 >= (a)->x1) || ((a)->y0 >= (a)->y1))
+#define NR_RECT_DFLS_TEST_INTERSECT(a,b) (((a)->x0 < (b)->x1) && ((a)->x1 > (b)->x0) && ((a)->y0 < (b)->y1) && ((a)->y1 > (b)->y0))
+#define NR_RECT_DF_POINT_DF_TEST_INSIDE(r,p) (((p)->x >= (r)->x0) && ((p)->x < (r)->x1) && ((p)->y >= (r)->y0) && ((p)->y < (r)->y1))
+#define NR_RECT_LS_POINT_LS_TEST_INSIDE(r,p) (((p)->x >= (r)->x0) && ((p)->x < (r)->x1) && ((p)->y >= (r)->y0) && ((p)->y < (r)->y1))
+
+#define NR_MATRIX_D_TO_DOUBLE(m) ((m)->c)
+#define NR_MATRIX_D_FROM_DOUBLE(d) ((NRMatrix *) &(d)[0])
+
+#endif
diff --git a/src/libnr/nr-matrix-div.cpp b/src/libnr/nr-matrix-div.cpp
new file mode 100644
index 000000000..d6fb598b8
--- /dev/null
+++ b/src/libnr/nr-matrix-div.cpp
@@ -0,0 +1,22 @@
+#include "libnr/nr-matrix-ops.h"
+#include "libnr/nr-point-matrix-ops.h"
+
+NR::Point operator/(NR::Point const &p, NR::Matrix const &m) {
+ return p * m.inverse();
+}
+
+NR::Matrix operator/(NR::Matrix const &a, NR::Matrix const &b) {
+ return a * b.inverse();
+}
+
+
+/*
+ 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/libnr/nr-matrix-div.h b/src/libnr/nr-matrix-div.h
new file mode 100644
index 000000000..8669e2ce2
--- /dev/null
+++ b/src/libnr/nr-matrix-div.h
@@ -0,0 +1,21 @@
+#ifndef SEEN_LIBNR_NR_MATRIX_DIV_H
+#define SEEN_LIBNR_NR_MATRIX_DIV_H
+
+#include "libnr/nr-forward.h"
+
+NR::Point operator/(NR::Point const &, NR::Matrix const &);
+
+NR::Matrix operator/(NR::Matrix const &, NR::Matrix const &);
+
+#endif /* !SEEN_LIBNR_NR_MATRIX_DIV_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/libnr/nr-matrix-fns.cpp b/src/libnr/nr-matrix-fns.cpp
new file mode 100644
index 000000000..f392f3213
--- /dev/null
+++ b/src/libnr/nr-matrix-fns.cpp
@@ -0,0 +1,54 @@
+#include <libnr/nr-matrix-fns.h>
+
+namespace NR {
+
+Matrix elliptic_quadratic_form(Matrix const &m) {
+ double const od = m[0] * m[1] + m[2] * m[3];
+ return Matrix((m[0]*m[0] + m[1]*m[1]), od,
+ od, (m[2]*m[2] + m[3]*m[3]),
+ 0, 0);
+/* def quadratic_form((a, b), (c, d)):
+ return ((a*a + c*c), a*c+b*d),(a*c+b*d, (b*b + d*d)) */
+}
+
+Eigen::Eigen(Matrix const &m) {
+ double const B = -m[0] - m[3];
+ double const C = m[0]*m[3] - m[1]*m[2];
+ double const center = -B/2.0;
+ double const delta = sqrt(B*B-4*C)/2.0;
+ values = Point(center + delta, center - delta);
+ for (int i = 0; i < 2; i++) {
+ vectors[i] = unit_vector(rot90(Point(m[0]-values[i], m[1])));
+ }
+}
+
+/** Returns just the scale/rotate/skew part of the matrix without the translation part. */
+Matrix transform(Matrix const &m) {
+ Matrix const ret(m[0], m[1],
+ m[2], m[3],
+ 0, 0);
+ return ret;
+}
+
+translate get_translation(Matrix const &m) {
+ return translate(m[4], m[5]);
+}
+
+void matrix_print(const gchar *say, Matrix const &m)
+{
+ printf ("%s %g %g %g %g %g %g\n", say, m[0], m[1], m[2], m[3], m[4], m[5]);
+}
+
+} // namespace NR
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnr/nr-matrix-fns.h b/src/libnr/nr-matrix-fns.h
new file mode 100644
index 000000000..208366746
--- /dev/null
+++ b/src/libnr/nr-matrix-fns.h
@@ -0,0 +1,50 @@
+#ifndef SEEN_NR_MATRIX_FNS_H
+#define SEEN_NR_MATRIX_FNS_H
+
+#include "nr-matrix.h"
+
+namespace NR {
+
+/** Given a matrix m such that unit_circle = m*x, this returns the
+ * quadratic form x*A*x = 1. */
+Matrix elliptic_quadratic_form(Matrix const &m);
+
+/** Given a matrix (ignoring the translation) this returns the eigen
+ * values and vectors. */
+class Eigen{
+public:
+ Point vectors[2];
+ Point values;
+ Eigen(Matrix const &m);
+};
+
+// Matrix factories
+Matrix from_basis(const Point x_basis, const Point y_basis, const Point offset=Point(0,0));
+
+Matrix identity();
+
+double expansion(Matrix const &m);
+
+bool transform_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon);
+bool translate_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon);
+bool matrix_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon);
+
+Matrix transform(Matrix const &m);
+translate get_translation(Matrix const &m);
+
+void matrix_print(const gchar *say, Matrix const &m);
+
+} // namespace NR
+
+#endif /* !SEEN_NR_MATRIX_FNS_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/libnr/nr-matrix-ops.h b/src/libnr/nr-matrix-ops.h
new file mode 100644
index 000000000..02fb28d0c
--- /dev/null
+++ b/src/libnr/nr-matrix-ops.h
@@ -0,0 +1,45 @@
+/* operator functions for NR::Matrix. */
+#ifndef SEEN_NR_MATRIX_OPS_H
+#define SEEN_NR_MATRIX_OPS_H
+
+#include <libnr/nr-matrix.h>
+
+namespace NR {
+
+inline bool operator==(Matrix const &a, Matrix const &b)
+{
+ for(unsigned i = 0; i < 6; ++i) {
+ if ( a[i] != b[i] ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline bool operator!=(Matrix const &a, Matrix const &b)
+{
+ return !( a == b );
+}
+
+Matrix operator*(Matrix const &a, Matrix const &b);
+
+inline Matrix operator*(Matrix const &a, NRMatrix const &b)
+{
+ return a * NR::Matrix(b);
+}
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_MATRIX_OPS_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/libnr/nr-matrix-rotate-ops.cpp b/src/libnr/nr-matrix-rotate-ops.cpp
new file mode 100644
index 000000000..625291575
--- /dev/null
+++ b/src/libnr/nr-matrix-rotate-ops.cpp
@@ -0,0 +1,18 @@
+#include "libnr/nr-matrix-ops.h"
+
+NR::Matrix operator*(NR::Matrix const &m, NR::rotate const &r)
+{
+ return m * NR::Matrix(r);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-matrix-rotate-ops.h b/src/libnr/nr-matrix-rotate-ops.h
new file mode 100644
index 000000000..44d9c8726
--- /dev/null
+++ b/src/libnr/nr-matrix-rotate-ops.h
@@ -0,0 +1,20 @@
+#ifndef SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_H
+#define SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_H
+
+#include "libnr/nr-forward.h"
+
+NR::Matrix operator*(NR::Matrix const &m, NR::rotate const &r);
+
+
+#endif /* !SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_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/libnr/nr-matrix-scale-ops.cpp b/src/libnr/nr-matrix-scale-ops.cpp
new file mode 100644
index 000000000..90cbaf585
--- /dev/null
+++ b/src/libnr/nr-matrix-scale-ops.cpp
@@ -0,0 +1,37 @@
+#include "libnr/nr-matrix-ops.h"
+
+NR::Matrix
+operator/(NR::Matrix const &m, NR::scale const &s)
+{
+ using NR::X; using NR::Y;
+ NR::Matrix ret(m);
+ ret[0] /= s[X]; ret[1] /= s[Y];
+ ret[2] /= s[X]; ret[3] /= s[Y];
+ ret[4] /= s[X]; ret[5] /= s[Y];
+ assert_close( ret, m * NR::Matrix(s.inverse()) );
+ return ret;
+}
+
+NR::Matrix
+operator*(NR::Matrix const &m, NR::scale const &s)
+{
+ using NR::X; using NR::Y;
+ NR::Matrix ret(m);
+ ret[0] *= s[X]; ret[1] *= s[Y];
+ ret[2] *= s[X]; ret[3] *= s[Y];
+ ret[4] *= s[X]; ret[5] *= s[Y];
+ assert_close( ret, m * NR::Matrix(s) );
+ return ret;
+}
+
+
+/*
+ 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/libnr/nr-matrix-scale-ops.h b/src/libnr/nr-matrix-scale-ops.h
new file mode 100644
index 000000000..dee275182
--- /dev/null
+++ b/src/libnr/nr-matrix-scale-ops.h
@@ -0,0 +1,14 @@
+#ifndef SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H
+#define SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H
+/** \file
+ * Declarations (and definition if inline) of operator blah (NR::Matrix, NR::scale).
+ */
+
+#include "libnr/nr-forward.h"
+
+NR::Matrix operator/(NR::Matrix const &m, NR::scale const &s);
+
+NR::Matrix operator*(NR::Matrix const &m, NR::scale const &s);
+
+
+#endif /* !SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H */
diff --git a/src/libnr/nr-matrix-test.cpp b/src/libnr/nr-matrix-test.cpp
new file mode 100644
index 000000000..b7f064e48
--- /dev/null
+++ b/src/libnr/nr-matrix-test.cpp
@@ -0,0 +1,197 @@
+#include <utest/utest.h>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-matrix-rotate-ops.h>
+#include <libnr/nr-matrix-scale-ops.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-rotate-ops.h>
+#include <libnr/nr-scale-ops.h>
+#include <libnr/nr-scale-translate-ops.h>
+#include <libnr/nr-translate.h>
+#include <libnr/nr-translate-ops.h>
+#include <libnr/nr-translate-scale-ops.h>
+using NR::Matrix;
+using NR::X;
+using NR::Y;
+
+inline bool point_equalp(NR::Point const &a, NR::Point const &b)
+{
+ return ( NR_DF_TEST_CLOSE(a[X], b[X], 1e-5) &&
+ NR_DF_TEST_CLOSE(a[Y], b[Y], 1e-5) );
+}
+
+int main(int argc, char *argv[])
+{
+ int rc = EXIT_SUCCESS;
+
+ Matrix const m_id(NR::identity());
+ NR::rotate const r_id(NR::Point(1, 0));
+ NR::translate const t_id(0, 0);
+
+ utest_start("Matrix");
+
+ Matrix const c16(1.0, 2.0,
+ 3.0, 4.0,
+ 5.0, 6.0);
+ UTEST_TEST("basic constructors, operator=") {
+ Matrix const c16_copy(c16);
+ Matrix c16_eq(m_id);
+ c16_eq = c16;
+ for(unsigned i = 0; i < 6; ++i) {
+ UTEST_ASSERT( c16[i] == 1.0 + i );
+ UTEST_ASSERT( c16[i] == c16_copy[i] );
+ UTEST_ASSERT( c16[i] == c16_eq[i] );
+ UTEST_ASSERT( m_id[i] == double( i == 0 || i == 3 ) );
+ }
+ }
+
+ UTEST_TEST("scale constructor") {
+ NR::scale const s(2.0, 3.0);
+ NR::Matrix const ms(s);
+ NR::Point const p(5.0, 7.0);
+ UTEST_ASSERT( p * s == NR::Point(10.0, 21.0) );
+ UTEST_ASSERT( p * ms == NR::Point(10.0, 21.0) );
+ }
+
+ NR::rotate const r86(NR::Point(.8, .6));
+ NR::Matrix const mr86(r86);
+ UTEST_TEST("rotate constructor") {
+ NR::Point const p0(1.0, 0.0);
+ NR::Point const p90(0.0, 1.0);
+ UTEST_ASSERT( p0 * r86 == NR::Point(.8, .6) );
+ UTEST_ASSERT( p0 * mr86 == NR::Point(.8, .6) );
+ UTEST_ASSERT( p90 * r86 == NR::Point(-.6, .8) );
+ UTEST_ASSERT( p90 * mr86 == NR::Point(-.6, .8) );
+ UTEST_ASSERT(matrix_equalp(Matrix( r86 * r86 ),
+ mr86 * mr86,
+ 1e-14));
+ }
+
+ NR::translate const t23(2.0, 3.0);
+ UTEST_TEST("translate constructor") {
+ NR::Matrix const mt23(t23);
+ NR::Point const b(-2.0, 3.0);
+ UTEST_ASSERT( b * t23 == b * mt23 );
+ }
+
+ NR::scale const s_id(1.0, 1.0);
+ UTEST_TEST("test_identity") {
+ UTEST_ASSERT(m_id.test_identity());
+ UTEST_ASSERT(Matrix(t_id).test_identity());
+ UTEST_ASSERT(!(Matrix(NR::translate(-2, 3)).test_identity()));
+ UTEST_ASSERT(Matrix(r_id).test_identity());
+ NR::rotate const rot180(NR::Point(-1, 0));
+ UTEST_ASSERT(!(Matrix(rot180).test_identity()));
+ UTEST_ASSERT(Matrix(s_id).test_identity());
+ UTEST_ASSERT(!(Matrix(NR::scale(1.0, 0.0)).test_identity()));
+ UTEST_ASSERT(!(Matrix(NR::scale(0.0, 1.0)).test_identity()));
+ UTEST_ASSERT(!(Matrix(NR::scale(1.0, -1.0)).test_identity()));
+ UTEST_ASSERT(!(Matrix(NR::scale(-1.0, -1.0)).test_identity()));
+ }
+
+ UTEST_TEST("inverse") {
+ UTEST_ASSERT( m_id.inverse() == m_id );
+ UTEST_ASSERT( Matrix(t23).inverse() == Matrix(NR::translate(-2.0, -3.0)) );
+ NR::scale const s2(-4.0, 2.0);
+ NR::scale const sp5(-.25, .5);
+ UTEST_ASSERT( Matrix(s2).inverse() == Matrix(sp5) );
+ }
+
+ UTEST_TEST("nr_matrix_invert") {
+ NRMatrix const nr_m_id(m_id);
+ Matrix const m_s2(NR::scale(-4.0, 2.0));
+ NRMatrix const nr_s2(m_s2);
+ Matrix const m_sp5(NR::scale(-.25, .5));
+ NRMatrix const nr_sp5(m_sp5);
+ Matrix const m_t23(t23);
+ NRMatrix const nr_t23(m_t23);
+ NRMatrix inv;
+ nr_matrix_invert(&inv, &nr_m_id);
+ UTEST_ASSERT( Matrix(inv) == m_id );
+ nr_matrix_invert(&inv, &nr_t23);
+ UTEST_ASSERT( Matrix(inv) == Matrix(NR::translate(-2.0, -3.0)) );
+ nr_matrix_invert(&inv, &nr_s2);
+ UTEST_ASSERT( Matrix(inv) == Matrix(nr_sp5) );
+ nr_matrix_invert(&inv, &nr_sp5);
+ UTEST_ASSERT( Matrix(inv) == Matrix(nr_s2) );
+
+ /* Test that nr_matrix_invert handles src == dest. */
+ inv = nr_s2;
+ nr_matrix_invert(&inv, &inv);
+ UTEST_ASSERT( Matrix(inv) == Matrix(nr_sp5) );
+ inv = nr_t23;
+ nr_matrix_invert(&inv, &inv);
+ UTEST_ASSERT( Matrix(inv) == Matrix(NR::translate(-2.0, -3.0)) );
+ }
+
+ UTEST_TEST("elliptic quadratic form") {
+ NR::Matrix const aff(1.0, 1.0,
+ 0.0, 1.0,
+ 5.0, 6.0);
+ NR::Matrix const invaff = aff.inverse();
+ UTEST_ASSERT( invaff[1] == -1.0 );
+
+ NR::Matrix const ef(elliptic_quadratic_form(invaff));
+ NR::Matrix const exp_ef(2, -1,
+ -1, 1,
+ 0, 0);
+ UTEST_ASSERT( ef == exp_ef );
+ }
+
+ UTEST_TEST("Matrix * rotate") {
+ NR::Matrix const ma(2.0, -1.0,
+ 4.0, 4.0,
+ -0.5, 2.0);
+ NR::Matrix const a_r86( ma * r86 );
+ NR::Matrix const ma1( a_r86 * r86.inverse() );
+ UTEST_ASSERT(matrix_equalp(ma1, ma, 1e-12));
+ NR::Matrix const exp_a_r86( 2*.8 + -1*-.6, 2*.6 + -1*.8,
+ 4*.8 + 4*-.6, 4*.6 + 4*.8,
+ -.5*.8 + 2*-.6, -.5*.6 + 2*.8 );
+ UTEST_ASSERT(matrix_equalp(a_r86, exp_a_r86, 1e-12));
+ }
+
+ UTEST_TEST("translate*scale, scale*translate") {
+ NR::translate const t2n4(2, -4);
+ NR::scale const sn2_8(-2, 8);
+ NR::Matrix const exp_ts(-2, 0,
+ 0, 8,
+ -4, -32);
+ NR::Matrix const exp_st(-2, 0,
+ 0, 8,
+ 2, -4);
+ UTEST_ASSERT( exp_ts == t2n4 * sn2_8 );
+ UTEST_ASSERT( exp_st == sn2_8 * t2n4 );
+ }
+
+ UTEST_TEST("Matrix * scale") {
+ NR::Matrix const ma(2.0, -1.0,
+ 4.0, 4.0,
+ -0.5, 2.0);
+ NR::scale const sn2_8(-2, 8);
+ NR::Matrix const exp_as(-4, -8,
+ -8, 32,
+ 1, 16);
+ UTEST_ASSERT( ma * sn2_8 == exp_as );
+ }
+
+ if (!utest_end()) {
+ rc = EXIT_FAILURE;
+ }
+
+ return rc;
+}
+
+
+/*
+ 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/libnr/nr-matrix-test.h b/src/libnr/nr-matrix-test.h
new file mode 100644
index 000000000..476852890
--- /dev/null
+++ b/src/libnr/nr-matrix-test.h
@@ -0,0 +1,221 @@
+#include <cxxtest/TestSuite.h>
+
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-matrix-rotate-ops.h>
+#include <libnr/nr-matrix-scale-ops.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-rotate-ops.h>
+#include <libnr/nr-scale-ops.h>
+#include <libnr/nr-scale-translate-ops.h>
+#include <libnr/nr-translate.h>
+#include <libnr/nr-translate-ops.h>
+#include <libnr/nr-translate-scale-ops.h>
+using NR::Matrix;
+using NR::X;
+using NR::Y;
+
+inline bool point_equalp(NR::Point const &a, NR::Point const &b)
+{
+ return ( NR_DF_TEST_CLOSE(a[X], b[X], 1e-5) &&
+ NR_DF_TEST_CLOSE(a[Y], b[Y], 1e-5) );
+}
+
+class NrMatrixTest : public CxxTest::TestSuite
+{
+public:
+
+ NrMatrixTest() :
+ m_id( NR::identity() ),
+ r_id( NR::Point(1, 0) ),
+ t_id( 0, 0 ),
+ c16( 1.0, 2.0,
+ 3.0, 4.0,
+ 5.0, 6.0),
+ r86( NR::Point(.8, .6) ),
+ mr86( r86 ),
+ t23( 2.0, 3.0 ),
+ s_id( 1.0, 1.0 )
+ {
+ }
+ virtual ~NrMatrixTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrMatrixTest *createSuite() { return new NrMatrixTest(); }
+ static void destroySuite( NrMatrixTest *suite ) { delete suite; }
+
+ Matrix const m_id;
+ NR::rotate const r_id;
+ NR::translate const t_id;
+ Matrix const c16;
+ NR::rotate const r86;
+ NR::Matrix const mr86;
+ NR::translate const t23;
+ NR::scale const s_id;
+
+
+
+
+ void testCtorsAssignmentOp(void)
+ {
+ Matrix const c16_copy(c16);
+ Matrix c16_eq(m_id);
+ c16_eq = c16;
+ for(unsigned i = 0; i < 6; ++i) {
+ TS_ASSERT_EQUALS( c16[i], 1.0 + i );
+ TS_ASSERT_EQUALS( c16[i], c16_copy[i] );
+ TS_ASSERT_EQUALS( c16[i], c16_eq[i] );
+ TS_ASSERT_EQUALS( m_id[i], double( i == 0 || i == 3 ) );
+ }
+ }
+
+ void testScaleCtor(void)
+ {
+ NR::scale const s(2.0, 3.0);
+ NR::Matrix const ms(s);
+ NR::Point const p(5.0, 7.0);
+ TS_ASSERT_EQUALS( p * s, NR::Point(10.0, 21.0) );
+ TS_ASSERT_EQUALS( p * ms, NR::Point(10.0, 21.0) );
+ }
+
+ void testRotateCtor(void)
+ {
+ NR::Point const p0(1.0, 0.0);
+ NR::Point const p90(0.0, 1.0);
+ TS_ASSERT_EQUALS( p0 * r86, NR::Point(.8, .6) );
+ TS_ASSERT_EQUALS( p0 * mr86, NR::Point(.8, .6) );
+ TS_ASSERT_EQUALS( p90 * r86, NR::Point(-.6, .8) );
+ TS_ASSERT_EQUALS( p90 * mr86, NR::Point(-.6, .8) );
+ TS_ASSERT( matrix_equalp(Matrix( r86 * r86 ),
+ mr86 * mr86,
+ 1e-14) );
+ }
+
+ void testTranslateCtor(void)
+ {
+ NR::Matrix const mt23(t23);
+ NR::Point const b(-2.0, 3.0);
+ TS_ASSERT_EQUALS( b * t23, b * mt23 );
+ }
+
+ void testIdentity(void)
+ {
+ TS_ASSERT( m_id.test_identity() );
+ TS_ASSERT( Matrix(t_id).test_identity() );
+ TS_ASSERT( !(Matrix(NR::translate(-2, 3)).test_identity()) );
+ TS_ASSERT( Matrix(r_id).test_identity() );
+ NR::rotate const rot180(NR::Point(-1, 0));
+ TS_ASSERT( !(Matrix(rot180).test_identity()) );
+ TS_ASSERT( Matrix(s_id).test_identity() );
+ TS_ASSERT( !(Matrix(NR::scale(1.0, 0.0)).test_identity()) );
+ TS_ASSERT( !(Matrix(NR::scale(0.0, 1.0)).test_identity()) );
+ TS_ASSERT( !(Matrix(NR::scale(1.0, -1.0)).test_identity()) );
+ TS_ASSERT( !(Matrix(NR::scale(-1.0, -1.0)).test_identity()) );
+ }
+
+ void testInverse(void)
+ {
+ TS_ASSERT_EQUALS( m_id.inverse(), m_id );
+ TS_ASSERT_EQUALS( Matrix(t23).inverse(), Matrix(NR::translate(-2.0, -3.0)) );
+ NR::scale const s2(-4.0, 2.0);
+ NR::scale const sp5(-.25, .5);
+ TS_ASSERT_EQUALS( Matrix(s2).inverse(), Matrix(sp5) );
+ }
+
+ void testNrMatrixInvert(void)
+ {
+ NRMatrix const nr_m_id(m_id);
+ Matrix const m_s2(NR::scale(-4.0, 2.0));
+ NRMatrix const nr_s2(m_s2);
+ Matrix const m_sp5(NR::scale(-.25, .5));
+ NRMatrix const nr_sp5(m_sp5);
+ Matrix const m_t23(t23);
+ NRMatrix const nr_t23(m_t23);
+ NRMatrix inv;
+ nr_matrix_invert(&inv, &nr_m_id);
+ TS_ASSERT_EQUALS( Matrix(inv), m_id );
+ nr_matrix_invert(&inv, &nr_t23);
+ TS_ASSERT_EQUALS( Matrix(inv), Matrix(NR::translate(-2.0, -3.0)) );
+ nr_matrix_invert(&inv, &nr_s2);
+ TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_sp5) );
+ nr_matrix_invert(&inv, &nr_sp5);
+ TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_s2) );
+
+ /* Test that nr_matrix_invert handles src == dest. */
+ inv = nr_s2;
+ nr_matrix_invert(&inv, &inv);
+ TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_sp5) );
+ inv = nr_t23;
+ nr_matrix_invert(&inv, &inv);
+ TS_ASSERT_EQUALS( Matrix(inv), Matrix(NR::translate(-2.0, -3.0)) );
+ }
+
+ void testEllipticQuadraticForm(void)
+ {
+ NR::Matrix const aff(1.0, 1.0,
+ 0.0, 1.0,
+ 5.0, 6.0);
+ NR::Matrix const invaff = aff.inverse();
+ TS_ASSERT_EQUALS( invaff[1], -1.0 );
+
+ NR::Matrix const ef(elliptic_quadratic_form(invaff));
+ NR::Matrix const exp_ef(2, -1,
+ -1, 1,
+ 0, 0);
+ TS_ASSERT_EQUALS( ef, exp_ef );
+ }
+
+ void testMatrixStarRotate(void)
+ {
+ NR::Matrix const ma(2.0, -1.0,
+ 4.0, 4.0,
+ -0.5, 2.0);
+ NR::Matrix const a_r86( ma * r86 );
+ NR::Matrix const ma1( a_r86 * r86.inverse() );
+ TS_ASSERT( matrix_equalp(ma1, ma, 1e-12) );
+ NR::Matrix const exp_a_r86( 2*.8 + -1*-.6, 2*.6 + -1*.8,
+ 4*.8 + 4*-.6, 4*.6 + 4*.8,
+ -.5*.8 + 2*-.6, -.5*.6 + 2*.8 );
+ TS_ASSERT( matrix_equalp(a_r86, exp_a_r86, 1e-12) );
+ }
+
+ void testTranslateStarScale_ScaleStarTranslate(void)
+ {
+ NR::translate const t2n4(2, -4);
+ NR::scale const sn2_8(-2, 8);
+ NR::Matrix const exp_ts(-2, 0,
+ 0, 8,
+ -4, -32);
+ NR::Matrix const exp_st(-2, 0,
+ 0, 8,
+ 2, -4);
+ TS_ASSERT_EQUALS( exp_ts, t2n4 * sn2_8 );
+ TS_ASSERT_EQUALS( exp_st, sn2_8 * t2n4 );
+ }
+
+ void testMatrixStarScale(void)
+ {
+ NR::Matrix const ma(2.0, -1.0,
+ 4.0, 4.0,
+ -0.5, 2.0);
+ NR::scale const sn2_8(-2, 8);
+ NR::Matrix const exp_as(-4, -8,
+ -8, 32,
+ 1, 16);
+ TS_ASSERT_EQUALS( ma * sn2_8, exp_as );
+ }
+};
+
+/*
+ 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/libnr/nr-matrix-translate-ops.cpp b/src/libnr/nr-matrix-translate-ops.cpp
new file mode 100644
index 000000000..0ccdcf9ce
--- /dev/null
+++ b/src/libnr/nr-matrix-translate-ops.cpp
@@ -0,0 +1,26 @@
+#include "libnr/nr-matrix-ops.h"
+
+namespace NR {
+
+Matrix
+operator*(Matrix const &m, translate const &t)
+{
+ Matrix ret(m);
+ ret[4] += t[X];
+ ret[5] += t[Y];
+ assert_close( ret, m * Matrix(t) );
+ return ret;
+}
+
+} // namespace NR
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-matrix-translate-ops.h b/src/libnr/nr-matrix-translate-ops.h
new file mode 100644
index 000000000..f51bccaa1
--- /dev/null
+++ b/src/libnr/nr-matrix-translate-ops.h
@@ -0,0 +1,36 @@
+#ifndef SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_H
+#define SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_H
+
+/** \file
+ * Declarations (and definition if inline) of operator
+ * blah (NR::Matrix, NR::translate).
+ */
+
+#include "libnr/nr-matrix.h"
+#include "libnr/nr-translate.h"
+
+//NR::Matrix operator*(NR::Matrix const &m, NR::translate const &t);
+
+namespace NR {
+Matrix operator*(Matrix const &m, translate const &t);
+}
+
+inline NR::Matrix
+operator/(NR::Matrix const &numer, NR::translate const &denom)
+{
+ return numer * NR::translate(-denom.offset);
+}
+
+
+#endif /* !SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_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/libnr/nr-matrix.cpp b/src/libnr/nr-matrix.cpp
new file mode 100644
index 000000000..24fa2e206
--- /dev/null
+++ b/src/libnr/nr-matrix.cpp
@@ -0,0 +1,607 @@
+#define __NR_MATRIX_C__
+
+/** \file
+ * Various matrix routines. Currently includes some NR::rotate etc. routines too.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include "nr-matrix.h"
+
+
+
+/**
+ * Multiply two NRMatrices together, storing the result in d.
+ */
+NRMatrix *
+nr_matrix_multiply(NRMatrix *d, NRMatrix const *m0, NRMatrix const *m1)
+{
+ if (m0) {
+ if (m1) {
+ NR::Coord d0 = m0->c[0] * m1->c[0] + m0->c[1] * m1->c[2];
+ NR::Coord d1 = m0->c[0] * m1->c[1] + m0->c[1] * m1->c[3];
+ NR::Coord d2 = m0->c[2] * m1->c[0] + m0->c[3] * m1->c[2];
+ NR::Coord d3 = m0->c[2] * m1->c[1] + m0->c[3] * m1->c[3];
+ NR::Coord d4 = m0->c[4] * m1->c[0] + m0->c[5] * m1->c[2] + m1->c[4];
+ NR::Coord d5 = m0->c[4] * m1->c[1] + m0->c[5] * m1->c[3] + m1->c[5];
+
+ NR::Coord *dest = d->c;
+ *dest++ = d0;
+ *dest++ = d1;
+ *dest++ = d2;
+ *dest++ = d3;
+ *dest++ = d4;
+ *dest = d5;
+ } else {
+ *d = *m0;
+ }
+ } else {
+ if (m1) {
+ *d = *m1;
+ } else {
+ nr_matrix_set_identity(d);
+ }
+ }
+
+ return d;
+}
+
+
+
+
+/**
+ * Store the inverted value of Matrix m in d
+ */
+NRMatrix *
+nr_matrix_invert(NRMatrix *d, NRMatrix const *m)
+{
+ if (m) {
+ NR::Coord const det = m->c[0] * m->c[3] - m->c[1] * m->c[2];
+ if (!NR_DF_TEST_CLOSE(det, 0.0, NR_EPSILON)) {
+
+ NR::Coord const idet = 1.0 / det;
+ NR::Coord *dest = d->c;
+
+ /* Cache m->c[0] and m->c[4] in case d == m. */
+ NR::Coord const m_c0(m->c[0]);
+ NR::Coord const m_c4(m->c[4]);
+
+ /*0*/ *dest++ = m->c[3] * idet;
+ /*1*/ *dest++ = -m->c[1] * idet;
+ /*2*/ *dest++ = -m->c[2] * idet;
+ /*3*/ *dest++ = m_c0 * idet;
+ /*4*/ *dest++ = -m_c4 * d->c[0] - m->c[5] * d->c[2];
+ /*5*/ *dest = -m_c4 * d->c[1] - m->c[5] * d->c[3];
+
+ } else {
+ nr_matrix_set_identity(d);
+ }
+ } else {
+ nr_matrix_set_identity(d);
+ }
+
+ return d;
+}
+
+
+
+
+
+/**
+ * Set this matrix to a translation of x and y
+ */
+NRMatrix *
+nr_matrix_set_translate(NRMatrix *m, NR::Coord const x, NR::Coord const y)
+{
+ NR::Coord *dest = m->c;
+
+ *dest++ = 1.0; //0
+ *dest++ = 0.0; //1
+ *dest++ = 0.0; //2
+ *dest++ = 1.0; //3
+ *dest++ = x; //4
+ *dest = y; //5
+
+ return m;
+}
+
+
+
+
+
+/**
+ * Set this matrix to a scaling transform in sx and sy
+ */
+NRMatrix *
+nr_matrix_set_scale(NRMatrix *m, NR::Coord const sx, NR::Coord const sy)
+{
+ NR::Coord *dest = m->c;
+
+ *dest++ = sx; //0
+ *dest++ = 0.0; //1
+ *dest++ = 0.0; //2
+ *dest++ = sy; //3
+ *dest++ = 0.0; //4
+ *dest = 0.0; //5
+
+ return m;
+}
+
+
+
+
+
+/**
+ * Set this matrix to a rotating transform of angle 'theta' radians
+ */
+NRMatrix *
+nr_matrix_set_rotate(NRMatrix *m, NR::Coord const theta)
+{
+ NR::Coord const s = sin(theta);
+ NR::Coord const c = cos(theta);
+
+ NR::Coord *dest = m->c;
+
+ *dest++ = c; //0
+ *dest++ = s; //1
+ *dest++ = -s; //2
+ *dest++ = c; //3
+ *dest++ = 0.0; //4
+ *dest = 0.0; //5
+
+ return m;
+}
+
+
+
+
+
+
+
+
+
+/**
+ * Implement NR functions and methods
+ */
+namespace NR {
+
+
+
+
+
+/**
+ * Constructor. Assign to nr if not null, else identity
+ */
+Matrix::Matrix(NRMatrix const *nr)
+{
+ if (nr) {
+ assign(nr->c);
+ } else {
+ set_identity();
+ }
+}
+
+
+
+
+
+/**
+ * Multiply two matrices together
+ */
+Matrix operator*(Matrix const &m0, Matrix const &m1)
+{
+ NR::Coord const d0 = m0[0] * m1[0] + m0[1] * m1[2];
+ NR::Coord const d1 = m0[0] * m1[1] + m0[1] * m1[3];
+ NR::Coord const d2 = m0[2] * m1[0] + m0[3] * m1[2];
+ NR::Coord const d3 = m0[2] * m1[1] + m0[3] * m1[3];
+ NR::Coord const d4 = m0[4] * m1[0] + m0[5] * m1[2] + m1[4];
+ NR::Coord const d5 = m0[4] * m1[1] + m0[5] * m1[3] + m1[5];
+
+ Matrix ret( d0, d1, d2, d3, d4, d5 );
+
+ return ret;
+}
+
+
+
+
+
+/**
+ * Multiply a matrix by another
+ */
+Matrix &Matrix::operator*=(Matrix const &o)
+{
+ *this = *this * o;
+ return *this;
+}
+
+
+
+
+
+/**
+ * Multiply by a scaling matrix
+ */
+Matrix &Matrix::operator*=(scale const &other)
+{
+ /* This loop is massive overkill. Let's unroll.
+ * o _c[] goes from 0..5
+ * o other[] alternates between 0 and 1
+ */
+ /*
+ * for (unsigned i = 0; i < 3; ++i) {
+ * for (unsigned j = 0; j < 2; ++j) {
+ * this->_c[i * 2 + j] *= other[j];
+ * }
+ * }
+ */
+
+ NR::Coord const xscale = other[0];
+ NR::Coord const yscale = other[1];
+ NR::Coord *dest = _c;
+
+ /*i=0 j=0*/ *dest++ *= xscale;
+ /*i=0 j=1*/ *dest++ *= yscale;
+ /*i=1 j=0*/ *dest++ *= xscale;
+ /*i=1 j=1*/ *dest++ *= yscale;
+ /*i=2 j=0*/ *dest++ *= xscale;
+ /*i=2 j=1*/ *dest *= yscale;
+
+ return *this;
+}
+
+
+
+
+
+/**
+ * Return the inverse of this matrix. If an inverse is not defined,
+ * then return the identity matrix.
+ */
+Matrix Matrix::inverse() const
+{
+ Matrix d(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ NR::Coord const det = _c[0] * _c[3] - _c[1] * _c[2];
+ if (!NR_DF_TEST_CLOSE(det, 0.0, NR_EPSILON)) {
+
+ NR::Coord const idet = 1.0 / det;
+ NR::Coord *dest = d._c;
+
+ /*0*/ *dest++ = _c[3] * idet;
+ /*1*/ *dest++ = -_c[1] * idet;
+ /*2*/ *dest++ = -_c[2] * idet;
+ /*3*/ *dest++ = _c[0] * idet;
+ /*4*/ *dest++ = -_c[4] * d._c[0] - _c[5] * d._c[2];
+ /*5*/ *dest = -_c[4] * d._c[1] - _c[5] * d._c[3];
+
+ } else {
+ d.set_identity();
+ }
+
+ return d;
+}
+
+
+
+
+
+/**
+ * Set this matrix to Identity
+ */
+void Matrix::set_identity()
+{
+ NR::Coord *dest = _c;
+
+ *dest++ = 1.0; //0
+ *dest++ = 0.0; //1
+ *dest++ = 0.0; //2
+ *dest++ = 1.0; //3
+ // translation
+ *dest++ = 0.0; //4
+ *dest = 0.0; //5
+}
+
+
+
+
+
+/**
+ * return an Identity matrix
+ */
+Matrix identity()
+{
+ Matrix ret(1.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 0.0);
+ return ret;
+}
+
+
+
+
+
+/**
+ *
+ */
+Matrix from_basis(Point const x_basis, Point const y_basis, Point const offset)
+{
+ Matrix const ret(x_basis[X], y_basis[X],
+ x_basis[Y], y_basis[Y],
+ offset[X], offset[Y]);
+ return ret;
+}
+
+
+
+
+/**
+ * Returns a rotation matrix corresponding by the specified angle (in radians) about the origin.
+ *
+ * \see NR::rotate_degrees
+ *
+ * Angle direction in Inkscape code: If you use the traditional mathematics convention that y
+ * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If
+ * you take the common non-mathematical convention that y increases downwards, then positive angles
+ * are clockwise, as is common outside of mathematics.
+ */
+rotate::rotate(NR::Coord const theta) :
+ vec(cos(theta),
+ sin(theta))
+{
+}
+
+
+
+
+
+/**
+ * Return the determinant of the Matrix
+ */
+NR::Coord Matrix::det() const
+{
+ return _c[0] * _c[3] - _c[1] * _c[2];
+}
+
+
+
+
+
+/**
+ * Return the scalar of the descriminant of the Matrix
+ */
+NR::Coord Matrix::descrim2() const
+{
+ return fabs(det());
+}
+
+
+
+
+
+/**
+ * Return the descriminant of the Matrix
+ */
+NR::Coord Matrix::descrim() const
+{
+ return sqrt(descrim2());
+}
+
+
+
+
+
+/**
+ * Assign a matrix to a given coordinate array
+ */
+Matrix &Matrix::assign(Coord const *array)
+{
+ assert(array != NULL);
+
+ Coord const *src = array;
+ Coord *dest = _c;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ return *this;
+}
+
+
+
+
+
+/**
+ * Copy this matrix's value to a NRMatrix
+ */
+NRMatrix *Matrix::copyto(NRMatrix *nrm) const {
+
+ assert(nrm != NULL);
+
+ Coord const *src = _c;
+ Coord *dest = nrm->c;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ return nrm;
+}
+
+
+
+
+/**
+ * Copy this matrix's values to an array
+ */
+NR::Coord *Matrix::copyto(NR::Coord *array) const {
+
+ assert(array != NULL);
+
+ Coord const *src = _c;
+ Coord *dest = array;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ return array;
+}
+
+
+
+
+
+/**
+ *
+ */
+double expansion(Matrix const &m) {
+ return sqrt(fabs(m.det()));
+}
+
+
+
+
+
+/**
+ *
+ */
+double Matrix::expansion() const {
+ return sqrt(fabs(det()));
+}
+
+
+
+
+
+/**
+ *
+ */
+double Matrix::expansionX() const {
+ return sqrt(_c[0] * _c[0] + _c[1] * _c[1]);
+}
+
+
+
+
+
+/**
+ *
+ */
+double Matrix::expansionY() const {
+ return sqrt(_c[2] * _c[2] + _c[3] * _c[3]);
+}
+
+
+
+
+
+/**
+ *
+ */
+bool Matrix::is_translation(Coord const eps) const {
+ return ( fabs(_c[0] - 1.0) < eps &&
+ fabs(_c[3] - 1.0) < eps &&
+ fabs(_c[1]) < eps &&
+ fabs(_c[2]) < eps );
+}
+
+
+
+
+
+/**
+ *
+ */
+bool Matrix::test_identity() const {
+ return NR_MATRIX_DF_TEST_CLOSE(this, &NR_MATRIX_IDENTITY, NR_EPSILON);
+}
+
+
+
+
+
+/**
+ *
+ */
+bool transform_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon) {
+ return NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(&m0, &m1, epsilon);
+}
+
+
+
+
+
+/**
+ *
+ */
+bool translate_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon) {
+ return NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(&m0, &m1, epsilon);
+}
+
+
+
+
+
+/**
+ *
+ */
+bool matrix_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon)
+{
+ return ( NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(&m0, &m1, epsilon) &&
+ NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(&m0, &m1, epsilon) );
+}
+
+
+
+
+
+/**
+ * A home-made assertion. Stop if the two matrixes are not 'close' to
+ * each other.
+ */
+void assert_close(Matrix const &a, Matrix const &b)
+{
+ if (!matrix_equalp(a, b, 1e-3)) {
+ fprintf(stderr,
+ "a = | %g %g |,\tb = | %g %g |\n"
+ " | %g %g | \t | %g %g |\n"
+ " | %g %g | \t | %g %g |\n",
+ a[0], a[1], b[0], b[1],
+ a[2], a[3], b[2], b[3],
+ a[4], a[5], b[4], b[5]);
+ abort();
+ }
+}
+
+
+
+} //namespace NR
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-matrix.h b/src/libnr/nr-matrix.h
new file mode 100644
index 000000000..47196137e
--- /dev/null
+++ b/src/libnr/nr-matrix.h
@@ -0,0 +1,453 @@
+#ifndef __NR_MATRIX_H__
+#define __NR_MATRIX_H__
+
+/** \file
+ * Definition of NRMatrix and NR::Matrix types.
+ *
+ * \note Operator functions (e.g. Matrix * Matrix etc.) are mostly in
+ * libnr/nr-matrix-ops.h. See end of file for discussion.
+ *
+ * Main authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>:
+ * Original NRMatrix definition and related macros.
+ *
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>:
+ * NR::Matrix class version of the above.
+ *
+ * This code is in public domain.
+ */
+
+#include <glib/gmessages.h>
+
+#include "libnr/nr-coord.h"
+#include "libnr/nr-values.h"
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-scale.h>
+#include <libnr/nr-translate.h>
+
+/// NRMatrix is the obsolete form of NR::Matrix.
+/// It consists of six NR::Coord values.
+struct NRMatrix {
+ NR::Coord c[6];
+
+ NR::Coord &operator[](int i) { return c[i]; }
+ NR::Coord operator[](int i) const { return c[i]; }
+};
+
+#define nr_matrix_set_identity(m) (*(m) = NR_MATRIX_IDENTITY)
+
+#define nr_matrix_test_identity(m,e) (!(m) || NR_MATRIX_DF_TEST_CLOSE(m, &NR_MATRIX_IDENTITY, e))
+
+#define nr_matrix_test_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_CLOSE(m0, m1, e)))
+#define nr_matrix_test_transform_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(m0, m1, e)))
+#define nr_matrix_test_translate_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(m0, m1, e)))
+
+NRMatrix *nr_matrix_invert(NRMatrix *d, NRMatrix const *m);
+
+/* d,m0,m1 needn't be distinct in any of these multiply routines. */
+
+NRMatrix *nr_matrix_multiply(NRMatrix *d, NRMatrix const *m0, NRMatrix const *m1);
+
+NRMatrix *nr_matrix_set_translate(NRMatrix *m, NR::Coord const x, NR::Coord const y);
+
+NRMatrix *nr_matrix_set_scale(NRMatrix *m, NR::Coord const sx, NR::Coord const sy);
+
+NRMatrix *nr_matrix_set_rotate(NRMatrix *m, NR::Coord const theta);
+
+#define NR_MATRIX_DF_TRANSFORM_X(m,x,y) ((*(m))[0] * (x) + (*(m))[2] * (y) + (*(m))[4])
+#define NR_MATRIX_DF_TRANSFORM_Y(m,x,y) ((*(m))[1] * (x) + (*(m))[3] * (y) + (*(m))[5])
+
+#define NR_MATRIX_DF_EXPANSION2(m) (fabs((*(m))[0] * (*(m))[3] - (*(m))[1] * (*(m))[2]))
+#define NR_MATRIX_DF_EXPANSION(m) (sqrt(NR_MATRIX_DF_EXPANSION2(m)))
+
+namespace NR {
+
+/**
+ * The Matrix class.
+ *
+ * For purposes of multiplication, points should be thought of as row vectors
+ *
+ * p = ( p[X] p[Y] 1 )
+ *
+ * to be right-multiplied by transformation matrices
+ * \verbatim
+ c[] = | c[0] c[1] 0 |
+ | c[2] c[3] 0 |
+ | c[4] c[5] 1 | \endverbatim
+ *
+ * (so the columns of the matrix correspond to the columns (elements) of the result,
+ * and the rows of the matrix correspond to columns (elements) of the "input").
+ */
+class Matrix {
+
+
+ public:
+
+ /**
+ * Various forms of constructor
+ */
+
+ /**
+ *
+ */
+ explicit Matrix() { }
+
+
+ /**
+ *
+ */
+ Matrix(Matrix const &m) {
+
+ NR::Coord const *src = m._c;
+ NR::Coord *dest = _c;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ }
+
+
+
+
+ /**
+ *
+ */
+ Matrix(NRMatrix const &m) {
+
+ NR::Coord const *src = m.c;
+ NR::Coord *dest = _c;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ }
+
+
+
+
+ /**
+ *
+ */
+ Matrix(double c0, double c1,
+ double c2, double c3,
+ double c4, double c5) {
+
+ NR::Coord *dest = _c;
+
+ *dest++ = c0; //0
+ *dest++ = c1; //1
+ *dest++ = c2; //2
+ *dest++ = c3; //3
+ *dest++ = c4; //4
+ *dest = c5; //5
+
+ }
+
+
+
+ /**
+ *
+ */
+ Matrix &operator=(Matrix const &m) {
+
+ NR::Coord const *src = m._c;
+ NR::Coord *dest = _c;
+
+ *dest++ = *src++; //0
+ *dest++ = *src++; //1
+ *dest++ = *src++; //2
+ *dest++ = *src++; //3
+ *dest++ = *src++; //4
+ *dest = *src ; //5
+
+ return *this;
+ }
+
+
+
+
+ /**
+ *
+ */
+ explicit Matrix(scale const &sm) {
+
+ NR::Coord *dest = _c;
+
+ *dest++ = sm[X]; //0
+ *dest++ = 0.0; //1
+ *dest++ = 0.0; //2
+ *dest++ = sm[Y]; //3
+ *dest++ = 0.0; //4
+ *dest = 0.0; //5
+
+ }
+
+
+
+
+
+
+ /**
+ *
+ */
+ explicit Matrix(rotate const &r) {
+
+ NR::Coord *dest = _c;
+
+ *dest++ = r.vec[X]; //0
+ *dest++ = r.vec[Y]; //1
+ *dest++ = -r.vec[Y]; //2
+ *dest++ = r.vec[X]; //3
+ *dest++ = 0.0; //4
+ *dest = 0.0; //5
+
+ }
+
+
+
+
+ /**
+ *
+ */
+ explicit Matrix(translate const &tm) {
+
+ NR::Coord *dest = _c;
+
+ *dest++ = 1.0; //0
+ *dest++ = 0.0; //1
+ *dest++ = 0.0; //2
+ *dest++ = 1.0; //3
+ *dest++ = tm[X]; //4
+ *dest = tm[Y]; //5
+ }
+
+
+
+ /**
+ *
+ */
+ Matrix(NRMatrix const *nr);
+
+
+ /**
+ *
+ */
+ bool test_identity() const;
+
+
+ /**
+ *
+ */
+ bool is_translation(Coord const eps = 1e-6) const;
+
+
+ /**
+ *
+ */
+ Matrix inverse() const;
+
+
+ /**
+ *
+ */
+ Matrix &operator*=(Matrix const &other);
+
+
+ /**
+ *
+ */
+ Matrix &operator*=(scale const &other);
+
+
+
+ /**
+ *
+ */
+ Matrix &operator*=(translate const &other) {
+ _c[4] += other[X];
+ _c[5] += other[Y];
+ return *this;
+ }
+
+
+
+ /**
+ *
+ */
+ inline Coord &operator[](int const i) {
+ return _c[i];
+ }
+
+
+
+ /**
+ *
+ */
+ inline Coord operator[](int const i) const {
+ return _c[i];
+ }
+
+
+ /**
+ *
+ */
+ void set_identity();
+
+ /**
+ *
+ */
+ Coord det() const;
+
+
+ /**
+ *
+ */
+ Coord descrim2() const;
+
+
+ /**
+ *
+ */
+ Coord descrim() const;
+
+
+ /**
+ *
+ */
+ double expansion() const;
+
+
+ /**
+ *
+ */
+ double expansionX() const;
+
+
+ /**
+ *
+ */
+ double expansionY() const;
+
+ // legacy
+
+
+ /**
+ *
+ */
+ Matrix &assign(Coord const *array);
+
+
+ /**
+ *
+ */
+ NRMatrix *copyto(NRMatrix* nrm) const;
+
+
+ /**
+ *
+ */
+ Coord *copyto(Coord *array) const;
+
+
+
+ /**
+ *
+ */
+ operator NRMatrix&() {
+ g_assert(sizeof(_c) == sizeof(NRMatrix));
+ return *reinterpret_cast<NRMatrix *>(_c);
+ }
+
+
+
+ /**
+ *
+ */
+ operator NRMatrix const&() const {
+ g_assert(sizeof(_c) == sizeof(NRMatrix));
+ return *reinterpret_cast<const NRMatrix *>(_c);
+ }
+
+
+
+ /**
+ *
+ */
+ operator NRMatrix*() {
+ g_assert(sizeof(_c) == sizeof(NRMatrix));
+ return reinterpret_cast<NRMatrix *>(_c);
+ }
+
+
+ /**
+ *
+ */
+ operator NRMatrix const*() const {
+ g_assert(sizeof(_c) == sizeof(NRMatrix));
+ return reinterpret_cast<NRMatrix const *>(_c);
+ }
+
+
+ private:
+
+
+ NR::Coord _c[6];
+};
+
+/** A function to print out the Matrix (for debugging) */
+inline std::ostream &operator<< (std::ostream &out_file, const NR::Matrix &m) {
+ out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n";
+ out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n";
+ return out_file;
+}
+
+extern void assert_close(Matrix const &a, Matrix const &b);
+
+} /* namespace NR */
+
+
+
+
+
+
+
+/** \note
+ * Discussion of splitting up nr-matrix.h into lots of little files:
+ *
+ * Advantages:
+ *
+ * - Reducing amount of recompilation necessary when anything changes.
+ *
+ * - Hopefully also reducing compilation time by reducing the number of inline
+ * function definitions encountered by the compiler for a given .o file.
+ * (No timing comparisons done yet. On systems without much memory available
+ * for caching, this may be outweighed by additional I/O costs.)
+ *
+ * Disadvantages:
+ *
+ * - More #include lines necessary per file. If a compile fails due to
+ * not having all the necessary #include lines, then the developer needs
+ * to spend some time working out what #include to add.
+ */
+
+#endif /* !__NR_MATRIX_H__ */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-maybe.h b/src/libnr/nr-maybe.h
new file mode 100644
index 000000000..6eed01ec1
--- /dev/null
+++ b/src/libnr/nr-maybe.h
@@ -0,0 +1,140 @@
+#ifndef __NR_MAYBE_H__
+#define __NR_MAYBE_H__
+
+/*
+ * Functionalesque "Maybe" class
+ *
+ * Copyright 2004 MenTaLguY
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ *
+ * This code is licensed under the GNU GPL; see COPYING for more information.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdexcept>
+#include <typeinfo>
+#include <string>
+
+namespace NR {
+
+/** An exception class for run-time type errors */
+template <typename T>
+class IsNot : public std::domain_error {
+public:
+ IsNot() : domain_error(std::string("Is not ") + typeid(T).name()) {}
+};
+
+/** A type with only one value, which (in principle) is only equal to itself.
+ *
+ * Types that may (at runtime) pretend to be Nothing need only provide an
+ * operator bool operator==(Type, Nothing); the rest of the operator
+ * definitions will be taken care of automatically.
+ *
+ * Such types should also provide a casting operator to Nothing, obviously.
+ */
+struct Nothing {
+ bool operator==(Nothing n) { return true; }
+ bool operator!=(Nothing n) { return false; }
+ template <typename T>
+ bool operator==(T t) { return t == *this; }
+ template <typename T>
+ bool operator!=(T t) { return t != *this; }
+};
+
+template <typename T>
+bool operator==(T t, Nothing n) { return false; }
+template <typename T>
+bool operator!=(T t, Nothing n) { return !( t == n ); }
+
+template <typename T>
+struct MaybeTraits;
+
+template <typename T>
+class Maybe {
+public:
+ typedef MaybeTraits<T> traits;
+ typedef typename traits::storage storage;
+ typedef typename traits::reference reference;
+
+ Maybe(Nothing n) : _is_nothing(true), _t() {}
+
+ Maybe(const Maybe<T> &m) : _is_nothing(m._is_nothing), _t(m._t) {}
+
+ template <typename T2>
+ Maybe(const Maybe<T2> &m)
+ : _is_nothing(m._is_nothing),
+ _t(traits::to_storage(MaybeTraits<T2>::from_storage(m._t))) {}
+
+ template <typename T2>
+ Maybe(T2 t) : _is_nothing(false), _t(traits::to_storage(t)) {}
+
+ reference assume() const throw(IsNot<T>) {
+ if (_is_nothing) {
+ throw IsNot<T>();
+ } else {
+ return traits::from_storage(_t);
+ }
+ }
+
+ operator reference() const throw(IsNot<T>) {
+ if (_is_nothing) {
+ throw IsNot<T>();
+ } else {
+ return traits::from_storage(_t);
+ }
+ }
+ operator Nothing() const throw(IsNot<Nothing>) {
+ if (!_is_nothing) {
+ throw IsNot<Nothing>();
+ } else {
+ return Nothing();
+ }
+ }
+
+ bool operator==(Nothing n) { return _is_nothing; }
+ bool operator==(reference r) {
+ return traits::from_storage(_t) == r;
+ }
+
+private:
+ bool _is_nothing;
+ storage _t;
+};
+
+/* traits classes used by Maybe */
+
+template <typename T>
+struct MaybeTraits {
+ typedef T const storage;
+ typedef T const &reference;
+ static reference to_storage(reference t) { return t; }
+ static reference from_storage(reference t) { return t; }
+};
+
+template <typename T>
+struct MaybeTraits<T&> {
+ typedef T *storage;
+ typedef T &reference;
+ static storage to_storage(reference t) { return &t; }
+ static reference from_storage(storage t) { return *t; }
+};
+
+} /* namespace NR */
+
+#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/libnr/nr-object.cpp b/src/libnr/nr-object.cpp
new file mode 100644
index 000000000..b18685d11
--- /dev/null
+++ b/src/libnr/nr-object.cpp
@@ -0,0 +1,295 @@
+#define __NR_OBJECT_C__
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * This code is in public domain
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <libnr/nr-macros.h>
+
+#include "nr-object.h"
+
+unsigned int nr_emit_fail_warning(const gchar *file, unsigned int line, const gchar *method, const gchar *expr)
+{
+ fprintf (stderr, "File %s line %d (%s): Assertion %s failed\n", file, line, method, expr);
+ return 1;
+}
+
+/* NRObject */
+
+static NRObjectClass **classes = NULL;
+static unsigned int classes_len = 0;
+static unsigned int classes_size = 0;
+
+NRType nr_type_is_a(NRType type, NRType test)
+{
+ nr_return_val_if_fail(type < classes_len, FALSE);
+ nr_return_val_if_fail(test < classes_len, FALSE);
+
+ NRObjectClass *c = classes[type];
+
+ while (c) {
+ if (c->type == test) {
+ return TRUE;
+ }
+ c = c->parent;
+ }
+
+ return FALSE;
+}
+
+void const *nr_object_check_instance_cast(void const *ip, NRType tc)
+{
+ nr_return_val_if_fail(ip != NULL, NULL);
+ nr_return_val_if_fail(nr_type_is_a(((NRObject const *) ip)->klass->type, tc), ip);
+ return ip;
+}
+
+unsigned int nr_object_check_instance_type(void const *ip, NRType tc)
+{
+ if (ip == NULL) {
+ return FALSE;
+ }
+
+ return nr_type_is_a(((NRObject const *) ip)->klass->type, tc);
+}
+
+NRType nr_object_register_type(NRType parent,
+ gchar const *name,
+ unsigned int csize,
+ unsigned int isize,
+ void (* cinit) (NRObjectClass *),
+ void (* iinit) (NRObject *))
+{
+ if (classes_len >= classes_size) {
+ classes_size += 32;
+ classes = nr_renew (classes, NRObjectClass *, classes_size);
+ if (classes_len == 0) {
+ classes[0] = NULL;
+ classes_len = 1;
+ }
+ }
+
+ NRType const type = classes_len;
+ classes_len += 1;
+
+ classes[type] = (NRObjectClass*) new char[csize];
+ NRObjectClass *c = classes[type];
+
+ /* FIXME: is this necessary? */
+ memset(c, 0, csize);
+
+ if (classes[parent]) {
+ memcpy(c, classes[parent], classes[parent]->csize);
+ }
+
+ c->type = type;
+ c->parent = classes[parent];
+ c->name = strdup(name);
+ c->csize = csize;
+ c->isize = isize;
+ c->cinit = cinit;
+ c->iinit = iinit;
+
+ c->cinit(c);
+
+ return type;
+}
+
+static void nr_object_class_init (NRObjectClass *klass);
+static void nr_object_init (NRObject *object);
+static void nr_object_finalize (NRObject *object);
+
+NRType nr_object_get_type()
+{
+ static NRType type = 0;
+
+ if (!type) {
+ type = nr_object_register_type (0,
+ "NRObject",
+ sizeof (NRObjectClass),
+ sizeof (NRObject),
+ (void (*) (NRObjectClass *)) nr_object_class_init,
+ (void (*) (NRObject *)) nr_object_init);
+ }
+
+ return type;
+}
+
+static void nr_object_class_init(NRObjectClass *c)
+{
+ c->finalize = nr_object_finalize;
+ c->cpp_ctor = NRObject::invoke_ctor<NRObject>;
+}
+
+static void nr_object_init (NRObject *object)
+{
+}
+
+static void nr_object_finalize (NRObject *object)
+{
+}
+
+/* Dynamic lifecycle */
+
+static void nr_class_tree_object_invoke_init(NRObjectClass *c, NRObject *object)
+{
+ if (c->parent) {
+ nr_class_tree_object_invoke_init(c->parent, object);
+ }
+ c->iinit (object);
+}
+
+namespace {
+
+void finalize_object(void *base, void *)
+{
+ NRObject *object = reinterpret_cast<NRObject *>(base);
+ object->klass->finalize(object);
+ object->~NRObject();
+}
+
+}
+
+NRObject *NRObject::alloc(NRType type)
+{
+ nr_return_val_if_fail (type < classes_len, NULL);
+
+ NRObjectClass *c = classes[type];
+
+ if ( c->parent && c->cpp_ctor == c->parent->cpp_ctor ) {
+ g_error("Cannot instantiate NRObject class %s which has not registered a C++ constructor\n", c->name);
+ }
+
+ NRObject *object = reinterpret_cast<NRObject *>(
+ ::operator new(c->isize, Inkscape::GC::SCANNED, Inkscape::GC::AUTO,
+ &finalize_object, NULL)
+ );
+ memset(object, 0xf0, c->isize);
+
+ object->klass = c;
+ c->cpp_ctor(object);
+ nr_class_tree_object_invoke_init (c, object);
+
+ return object;
+}
+
+/* NRActiveObject */
+
+static void nr_active_object_class_init(NRActiveObjectClass *c);
+static void nr_active_object_init(NRActiveObject *object);
+static void nr_active_object_finalize(NRObject *object);
+
+static NRObjectClass *parent_class;
+
+NRType nr_active_object_get_type()
+{
+ static NRType type = 0;
+ if (!type) {
+ type = nr_object_register_type (NR_TYPE_OBJECT,
+ "NRActiveObject",
+ sizeof (NRActiveObjectClass),
+ sizeof (NRActiveObject),
+ (void (*) (NRObjectClass *)) nr_active_object_class_init,
+ (void (*) (NRObject *)) nr_active_object_init);
+ }
+ return type;
+}
+
+static void nr_active_object_class_init(NRActiveObjectClass *c)
+{
+ NRObjectClass *object_class = (NRObjectClass *) c;
+
+ parent_class = object_class->parent;
+
+ object_class->finalize = nr_active_object_finalize;
+ object_class->cpp_ctor = NRObject::invoke_ctor<NRActiveObject>;
+}
+
+static void nr_active_object_init(NRActiveObject *object)
+{
+}
+
+static void nr_active_object_finalize(NRObject *object)
+{
+ NRActiveObject *aobject = (NRActiveObject *) object;
+
+ if (aobject->callbacks) {
+ for (unsigned int i = 0; i < aobject->callbacks->length; i++) {
+ NRObjectListener *listener = aobject->callbacks->listeners + i;
+ if ( listener->vector->dispose ) {
+ listener->vector->dispose(object, listener->data);
+ }
+ }
+ free (aobject->callbacks);
+ }
+
+ ((NRObjectClass *) (parent_class))->finalize(object);
+}
+
+void nr_active_object_add_listener(NRActiveObject *object,
+ const NRObjectEventVector *vector,
+ unsigned int size,
+ void *data)
+{
+ if (!object->callbacks) {
+ object->callbacks = (NRObjectCallbackBlock*) malloc(sizeof(NRObjectCallbackBlock));
+ object->callbacks->size = 1;
+ object->callbacks->length = 0;
+ }
+
+ if (object->callbacks->length >= object->callbacks->size) {
+ int newsize = object->callbacks->size << 1;
+ object->callbacks = (NRObjectCallbackBlock *)
+ realloc(object->callbacks, sizeof(NRObjectCallbackBlock) + (newsize - 1) * sizeof (NRObjectListener));
+ object->callbacks->size = newsize;
+ }
+
+ NRObjectListener *listener = object->callbacks->listeners + object->callbacks->length;
+ listener->vector = vector;
+ listener->size = size;
+ listener->data = data;
+ object->callbacks->length += 1;
+}
+
+void nr_active_object_remove_listener_by_data(NRActiveObject *object, void *data)
+{
+ if (object->callbacks == NULL) {
+ return;
+ }
+
+ for (unsigned i = 0; i < object->callbacks->length; i++) {
+ NRObjectListener *listener = object->callbacks->listeners + i;
+ if ( listener->data == data ) {
+ object->callbacks->length -= 1;
+ if ( object->callbacks->length < 1 ) {
+ free(object->callbacks);
+ object->callbacks = NULL;
+ } else if ( object->callbacks->length != i ) {
+ *listener = object->callbacks->listeners[object->callbacks->length];
+ }
+ return;
+ }
+ }
+}
+
+
+
+/*
+ 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/libnr/nr-object.h b/src/libnr/nr-object.h
new file mode 100644
index 000000000..f39814b0d
--- /dev/null
+++ b/src/libnr/nr-object.h
@@ -0,0 +1,157 @@
+#ifndef __NR_OBJECT_H__
+#define __NR_OBJECT_H__
+
+/*
+ * RGBA display list system for inkscape
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * This code is in public domain
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gtypes.h>
+#include "gc-managed.h"
+#include "gc-finalized.h"
+#include "gc-anchored.h"
+
+typedef guint32 NRType;
+
+struct NRObject;
+struct NRObjectClass;
+
+#define NR_TYPE_OBJECT (nr_object_get_type ())
+#define NR_OBJECT(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_OBJECT, NRObject))
+#define NR_IS_OBJECT(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_OBJECT))
+
+#define NR_TYPE_ACTIVE_OBJECT (nr_active_object_get_type ())
+#define NR_ACTIVE_OBJECT(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ACTIVE_OBJECT, NRActiveObject))
+#define NR_IS_ACTIVE_OBJECT(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ACTIVE_OBJECT))
+
+#define nr_return_if_fail(expr) if (!(expr) && nr_emit_fail_warning (__FILE__, __LINE__, "?", #expr)) return
+#define nr_return_val_if_fail(expr,val) if (!(expr) && nr_emit_fail_warning (__FILE__, __LINE__, "?", #expr)) return (val)
+
+unsigned int nr_emit_fail_warning (const gchar *file, unsigned int line, const gchar *method, const gchar *expr);
+
+#ifndef NR_DISABLE_CAST_CHECKS
+#define NR_CHECK_INSTANCE_CAST(ip, tc, ct) ((ct *) nr_object_check_instance_cast (ip, tc))
+#else
+#define NR_CHECK_INSTANCE_CAST(ip, tc, ct) ((ct *) ip)
+#endif
+
+#define NR_CHECK_INSTANCE_TYPE(ip, tc) nr_object_check_instance_type (ip, tc)
+#define NR_OBJECT_GET_CLASS(ip) (((NRObject *) ip)->klass)
+
+NRType nr_type_is_a (NRType type, NRType test);
+
+void const *nr_object_check_instance_cast(void const *ip, NRType tc);
+unsigned int nr_object_check_instance_type(void const *ip, NRType tc);
+
+NRType nr_object_register_type (NRType parent,
+ gchar const *name,
+ unsigned int csize,
+ unsigned int isize,
+ void (* cinit) (NRObjectClass *),
+ void (* iinit) (NRObject *));
+
+/* NRObject */
+
+class NRObject : public Inkscape::GC::Managed<>,
+ public Inkscape::GC::Finalized,
+ public Inkscape::GC::Anchored
+{
+public:
+ NRObjectClass *klass;
+
+ static NRObject *alloc(NRType type);
+
+ template <typename T>
+ static void invoke_ctor(NRObject *object) {
+ new (object) T();
+ }
+
+ /* these can go away eventually */
+ NRObject *reference() {
+ return Inkscape::GC::anchor(this);
+ }
+ NRObject *unreference() {
+ Inkscape::GC::release(this);
+ return NULL;
+ }
+
+protected:
+ NRObject() {}
+
+private:
+ NRObject(NRObject const &); // no copy
+ void operator=(NRObject const &); // no assign
+
+ void *operator new(size_t size, void *placement) { return placement; }
+};
+
+struct NRObjectClass {
+ NRType type;
+ NRObjectClass *parent;
+
+ gchar *name;
+ unsigned int csize;
+ unsigned int isize;
+ void (* cinit) (NRObjectClass *);
+ void (* iinit) (NRObject *);
+ void (* finalize) (NRObject *object);
+ void (*cpp_ctor)(NRObject *object);
+};
+
+NRType nr_object_get_type (void);
+
+/* Dynamic lifecycle */
+
+inline NRObject *nr_object_new (NRType type) {
+ return NRObject::alloc(type);
+}
+
+inline NRObject *nr_object_ref (NRObject *object) {
+ return object->reference();
+}
+inline NRObject *nr_object_unref (NRObject *object) {
+ return object->unreference();
+}
+
+/* NRActiveObject */
+
+struct NRObjectEventVector {
+ void (* dispose) (NRObject *object, void *data);
+};
+
+struct NRObjectListener {
+ const NRObjectEventVector *vector;
+ unsigned int size;
+ void *data;
+};
+
+struct NRObjectCallbackBlock {
+ unsigned int size;
+ unsigned int length;
+ NRObjectListener listeners[1];
+};
+
+struct NRActiveObject : public NRObject {
+ NRActiveObject() : callbacks(NULL) {}
+ NRObjectCallbackBlock *callbacks;
+};
+
+struct NRActiveObjectClass : public NRObjectClass {
+};
+
+NRType nr_active_object_get_type (void);
+
+void nr_active_object_add_listener (NRActiveObject *object, const NRObjectEventVector *vector, unsigned int size, void *data);
+void nr_active_object_remove_listener_by_data (NRActiveObject *object, void *data);
+
+#endif
+
diff --git a/src/libnr/nr-path-code.h b/src/libnr/nr-path-code.h
new file mode 100644
index 000000000..cc174d73b
--- /dev/null
+++ b/src/libnr/nr-path-code.h
@@ -0,0 +1,28 @@
+#ifndef SEEN_LIBNR_NR_PATH_CODE_H
+#define SEEN_LIBNR_NR_PATH_CODE_H
+
+/** \file
+ * NRPathcode enum definition
+ */
+
+typedef enum {
+ NR_MOVETO, ///< Start of closed subpath
+ NR_MOVETO_OPEN, ///< Start of open subpath
+ NR_CURVETO, ///< Bezier curve segment
+ NR_LINETO, ///< Line segment
+ NR_END ///< End record
+} NRPathcode;
+
+
+#endif /* !SEEN_LIBNR_NR_PATH_CODE_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/libnr/nr-path.cpp b/src/libnr/nr-path.cpp
new file mode 100644
index 000000000..6ce2f5f99
--- /dev/null
+++ b/src/libnr/nr-path.cpp
@@ -0,0 +1,461 @@
+#define __NR_PATH_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include "n-art-bpath.h"
+#include "nr-rect.h"
+#include "nr-path.h"
+
+static void nr_curve_bbox (NR::Coord x000, NR::Coord y000, NR::Coord x001, NR::Coord y001, NR::Coord x011, NR::Coord y011, NR::Coord x111, NR::Coord y111, NRRect *bbox);
+
+static void nr_curve_bbox(NR::Point const p000, NR::Point const p001,
+ NR::Point const p011, NR::Point const p111,
+ NRRect *bbox);
+
+NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NRMatrix const *transform)
+{
+ int i;
+
+ if (!s->path) {
+ d->path = NULL;
+ return d;
+ }
+
+ i = 0;
+ while (s->path[i].code != NR_END) i += 1;
+
+ d->path = nr_new (NArtBpath, i + 1);
+
+ i = 0;
+ while (s->path[i].code != NR_END) {
+ d->path[i].code = s->path[i].code;
+ if (s->path[i].code == NR_CURVETO) {
+ d->path[i].x1 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x1, s->path[i].y1);
+ d->path[i].y1 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x1, s->path[i].y1);
+ d->path[i].x2 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x2, s->path[i].y2);
+ d->path[i].y2 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x2, s->path[i].y2);
+ }
+ d->path[i].x3 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x3, s->path[i].y3);
+ d->path[i].y3 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x3, s->path[i].y3);
+ i += 1;
+ }
+ d->path[i].code = NR_END;
+
+ return d;
+}
+
+NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NR::Matrix const transform) {
+ NRMatrix tr = transform;
+ return nr_path_duplicate_transform(d, s, &tr);
+}
+
+NArtBpath* nr_artpath_affine(NArtBpath *s, NR::Matrix const &aff) {
+ NRBPath bp, abp;
+ bp.path = s;
+ nr_path_duplicate_transform(&abp, &bp, aff);
+ return abp.path;
+}
+
+static void
+nr_line_wind_distance (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, NR::Point &pt, int *wind, NR::Coord *best)
+{
+ NR::Coord Ax, Ay, Bx, By, Dx, Dy, s;
+ NR::Coord dist2;
+
+ /* Find distance */
+ Ax = x0;
+ Ay = y0;
+ Bx = x1;
+ By = y1;
+ Dx = x1 - x0;
+ Dy = y1 - y0;
+ const NR::Coord Px = pt[NR::X];
+ const NR::Coord Py = pt[NR::Y];
+
+ if (best) {
+ s = ((Px - Ax) * Dx + (Py - Ay) * Dy) / (Dx * Dx + Dy * Dy);
+ if (s <= 0.0) {
+ dist2 = (Px - Ax) * (Px - Ax) + (Py - Ay) * (Py - Ay);
+ } else if (s >= 1.0) {
+ dist2 = (Px - Bx) * (Px - Bx) + (Py - By) * (Py - By);
+ } else {
+ NR::Coord Qx, Qy;
+ Qx = Ax + s * Dx;
+ Qy = Ay + s * Dy;
+ dist2 = (Px - Qx) * (Px - Qx) + (Py - Qy) * (Py - Qy);
+ }
+
+ if (dist2 < (*best * *best)) *best = sqrt (dist2);
+ }
+
+ if (wind) {
+ /* Find wind */
+ if ((Ax >= Px) && (Bx >= Px)) return;
+ if ((Ay >= Py) && (By >= Py)) return;
+ if ((Ay < Py) && (By < Py)) return;
+ if (Ay == By) return;
+ /* Ctach upper y bound */
+ if (Ay == Py) {
+ if (Ax < Px) *wind -= 1;
+ return;
+ } else if (By == Py) {
+ if (Bx < Px) *wind += 1;
+ return;
+ } else {
+ NR::Coord Qx;
+ /* Have to calculate intersection */
+ Qx = Ax + Dx * (Py - Ay) / Dy;
+ if (Qx < Px) {
+ *wind += (Dy > 0.0) ? 1 : -1;
+ }
+ }
+ }
+}
+
+static void
+nr_curve_bbox_wind_distance (NR::Coord x000, NR::Coord y000,
+ NR::Coord x001, NR::Coord y001,
+ NR::Coord x011, NR::Coord y011,
+ NR::Coord x111, NR::Coord y111,
+ NR::Point &pt,
+ NRRect *bbox, int *wind, NR::Coord *best,
+ NR::Coord tolerance)
+{
+ NR::Coord x0, y0, x1, y1, len2;
+ int needdist, needwind, needline;
+
+ const NR::Coord Px = pt[NR::X];
+ const NR::Coord Py = pt[NR::Y];
+
+ needdist = 0;
+ needwind = 0;
+ needline = 0;
+
+ if (bbox) nr_curve_bbox (x000, y000, x001, y001, x011, y011, x111, y111, bbox);
+
+ x0 = MIN (x000, x001);
+ x0 = MIN (x0, x011);
+ x0 = MIN (x0, x111);
+ y0 = MIN (y000, y001);
+ y0 = MIN (y0, y011);
+ y0 = MIN (y0, y111);
+ x1 = MAX (x000, x001);
+ x1 = MAX (x1, x011);
+ x1 = MAX (x1, x111);
+ y1 = MAX (y000, y001);
+ y1 = MAX (y1, y011);
+ y1 = MAX (y1, y111);
+
+ if (best) {
+ /* Quicly adjust to endpoints */
+ len2 = (x000 - Px) * (x000 - Px) + (y000 - Py) * (y000 - Py);
+ if (len2 < (*best * *best)) *best = (NR::Coord) sqrt (len2);
+ len2 = (x111 - Px) * (x111 - Px) + (y111 - Py) * (y111 - Py);
+ if (len2 < (*best * *best)) *best = (NR::Coord) sqrt (len2);
+
+ if (((x0 - Px) < *best) && ((y0 - Py) < *best) && ((Px - x1) < *best) && ((Py - y1) < *best)) {
+ /* Point is inside sloppy bbox */
+ /* Now we have to decide, whether subdivide */
+ /* fixme: (Lauris) */
+ if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {
+ needdist = 1;
+ } else {
+ needline = 1;
+ }
+ }
+ }
+ if (!needdist && wind) {
+ if ((y1 >= Py) && (y0 < Py) && (x0 < Px)) {
+ /* Possible intersection at the left */
+ /* Now we have to decide, whether subdivide */
+ /* fixme: (Lauris) */
+ if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {
+ needwind = 1;
+ } else {
+ needline = 1;
+ }
+ }
+ }
+
+ if (needdist || needwind) {
+ NR::Coord x00t, x0tt, xttt, x1tt, x11t, x01t;
+ NR::Coord y00t, y0tt, yttt, y1tt, y11t, y01t;
+ NR::Coord s, t;
+
+ t = 0.5;
+ s = 1 - t;
+
+ x00t = s * x000 + t * x001;
+ x01t = s * x001 + t * x011;
+ x11t = s * x011 + t * x111;
+ x0tt = s * x00t + t * x01t;
+ x1tt = s * x01t + t * x11t;
+ xttt = s * x0tt + t * x1tt;
+
+ y00t = s * y000 + t * y001;
+ y01t = s * y001 + t * y011;
+ y11t = s * y011 + t * y111;
+ y0tt = s * y00t + t * y01t;
+ y1tt = s * y01t + t * y11t;
+ yttt = s * y0tt + t * y1tt;
+
+ nr_curve_bbox_wind_distance (x000, y000, x00t, y00t, x0tt, y0tt, xttt, yttt, pt, NULL, wind, best, tolerance);
+ nr_curve_bbox_wind_distance (xttt, yttt, x1tt, y1tt, x11t, y11t, x111, y111, pt, NULL, wind, best, tolerance);
+ } else if (1 || needline) {
+ nr_line_wind_distance (x000, y000, x111, y111, pt, wind, best);
+ }
+}
+
+void
+nr_path_matrix_point_bbox_wind_distance (NRBPath *bpath, NR::Matrix const &m, NR::Point &pt,
+ NRRect *bbox, int *wind, NR::Coord *dist,
+ NR::Coord tolerance)
+{
+ NR::Coord x0, y0, x3, y3;
+ const NArtBpath *p;
+
+ if (!bpath->path) {
+ if (wind) *wind = 0;
+ if (dist) *dist = NR_HUGE;
+ return;
+ }
+
+ x0 = y0 = 0.0;
+ x3 = y3 = 0.0;
+
+ for (p = bpath->path; p->code != NR_END; p+= 1) {
+ switch (p->code) {
+ case NR_MOVETO_OPEN:
+ case NR_MOVETO:
+ x0 = m[0] * p->x3 + m[2] * p->y3 + m[4];
+ y0 = m[1] * p->x3 + m[3] * p->y3 + m[5];
+ if (bbox) {
+ bbox->x0 = (NR::Coord) MIN (bbox->x0, x0);
+ bbox->y0 = (NR::Coord) MIN (bbox->y0, y0);
+ bbox->x1 = (NR::Coord) MAX (bbox->x1, x0);
+ bbox->y1 = (NR::Coord) MAX (bbox->y1, y0);
+ }
+ break;
+ case NR_LINETO:
+ x3 = m[0] * p->x3 + m[2] * p->y3 + m[4];
+ y3 = m[1] * p->x3 + m[3] * p->y3 + m[5];
+ if (bbox) {
+ bbox->x0 = (NR::Coord) MIN (bbox->x0, x3);
+ bbox->y0 = (NR::Coord) MIN (bbox->y0, y3);
+ bbox->x1 = (NR::Coord) MAX (bbox->x1, x3);
+ bbox->y1 = (NR::Coord) MAX (bbox->y1, y3);
+ }
+ if (dist || wind) {
+ nr_line_wind_distance (x0, y0, x3, y3, pt, wind, dist);
+ }
+ x0 = x3;
+ y0 = y3;
+ break;
+ case NR_CURVETO:
+ x3 = m[0] * p->x3 + m[2] * p->y3 + m[4];
+ y3 = m[1] * p->x3 + m[3] * p->y3 + m[5];
+ nr_curve_bbox_wind_distance (x0, y0,
+ m[0] * p->x1 + m[2] * p->y1 + m[4],
+ m[1] * p->x1 + m[3] * p->y1 + m[5],
+ m[0] * p->x2 + m[2] * p->y2 + m[4],
+ m[1] * p->x2 + m[3] * p->y2 + m[5],
+ x3, y3,
+ pt,
+ bbox, wind, dist, tolerance);
+ x0 = x3;
+ y0 = y3;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+nr_curve_bbox(NR::Point const p000, NR::Point const p001,
+ NR::Point const p011, NR::Point const p111,
+ NRRect *bbox)
+{
+ using NR::X;
+ using NR::Y;
+
+ nr_curve_bbox(p000[X], p000[Y],
+ p001[X], p001[Y],
+ p011[X], p011[Y],
+ p111[X], p111[Y],
+ bbox);
+}
+
+/* Fast bbox calculation */
+/* Thanks to Nathan Hurst for suggesting it */
+
+static void
+nr_curve_bbox (NR::Coord x000, NR::Coord y000, NR::Coord x001, NR::Coord y001, NR::Coord x011, NR::Coord y011, NR::Coord x111, NR::Coord y111, NRRect *bbox)
+{
+ NR::Coord a, b, c, D;
+
+ bbox->x0 = (NR::Coord) MIN (bbox->x0, x111);
+ bbox->y0 = (NR::Coord) MIN (bbox->y0, y111);
+ bbox->x1 = (NR::Coord) MAX (bbox->x1, x111);
+ bbox->y1 = (NR::Coord) MAX (bbox->y1, y111);
+
+ /*
+ * xttt = s * (s * (s * x000 + t * x001) + t * (s * x001 + t * x011)) + t * (s * (s * x001 + t * x011) + t * (s * x011 + t * x111))
+ * xttt = s * (s2 * x000 + s * t * x001 + t * s * x001 + t2 * x011) + t * (s2 * x001 + s * t * x011 + t * s * x011 + t2 * x111)
+ * xttt = s * (s2 * x000 + 2 * st * x001 + t2 * x011) + t * (s2 * x001 + 2 * st * x011 + t2 * x111)
+ * xttt = s3 * x000 + 2 * s2t * x001 + st2 * x011 + s2t * x001 + 2st2 * x011 + t3 * x111
+ * xttt = s3 * x000 + 3s2t * x001 + 3st2 * x011 + t3 * x111
+ * xttt = s3 * x000 + (1 - s) 3s2 * x001 + (1 - s) * (1 - s) * 3s * x011 + (1 - s) * (1 - s) * (1 - s) * x111
+ * xttt = s3 * x000 + (3s2 - 3s3) * x001 + (3s - 6s2 + 3s3) * x011 + (1 - 2s + s2 - s + 2s2 - s3) * x111
+ * xttt = (x000 - 3 * x001 + 3 * x011 - x111) * s3 +
+ * ( 3 * x001 - 6 * x011 + 3 * x111) * s2 +
+ * ( 3 * x011 - 3 * x111) * s +
+ * ( x111)
+ * xttt' = (3 * x000 - 9 * x001 + 9 * x011 - 3 * x111) * s2 +
+ * ( 6 * x001 - 12 * x011 + 6 * x111) * s +
+ * ( 3 * x011 - 3 * x111)
+ */
+
+ a = 3 * x000 - 9 * x001 + 9 * x011 - 3 * x111;
+ b = 6 * x001 - 12 * x011 + 6 * x111;
+ c = 3 * x011 - 3 * x111;
+
+ /*
+ * s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a;
+ */
+ if (fabs (a) < NR_EPSILON) {
+ /* s = -c / b */
+ if (fabs (b) > NR_EPSILON) {
+ double s, t, xttt;
+ s = -c / b;
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
+ bbox->x0 = (float) MIN (bbox->x0, xttt);
+ bbox->x1 = (float) MAX (bbox->x1, xttt);
+ }
+ }
+ } else {
+ /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */
+ D = b * b - 4 * a * c;
+ if (D >= 0.0) {
+ NR::Coord d, s, t, xttt;
+ /* Have solution */
+ d = sqrt (D);
+ s = (-b + d) / (2 * a);
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
+ bbox->x0 = (NR::Coord) MIN (bbox->x0, xttt);
+ bbox->x1 = (NR::Coord) MAX (bbox->x1, xttt);
+ }
+ s = (-b - d) / (2 * a);
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
+ bbox->x0 = (NR::Coord) MIN (bbox->x0, xttt);
+ bbox->x1 = (NR::Coord) MAX (bbox->x1, xttt);
+ }
+ }
+ }
+
+ a = 3 * y000 - 9 * y001 + 9 * y011 - 3 * y111;
+ b = 6 * y001 - 12 * y011 + 6 * y111;
+ c = 3 * y011 - 3 * y111;
+
+ if (fabs (a) < NR_EPSILON) {
+ /* s = -c / b */
+ if (fabs (b) > NR_EPSILON) {
+ double s, t, yttt;
+ s = -c / b;
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
+ bbox->y0 = (float) MIN (bbox->y0, yttt);
+ bbox->y1 = (float) MAX (bbox->y1, yttt);
+ }
+ }
+ } else {
+ /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */
+ D = b * b - 4 * a * c;
+ if (D >= 0.0) {
+ NR::Coord d, s, t, yttt;
+ /* Have solution */
+ d = sqrt (D);
+ s = (-b + d) / (2 * a);
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
+ bbox->y0 = (NR::Coord) MIN (bbox->y0, yttt);
+ bbox->y1 = (NR::Coord) MAX (bbox->y1, yttt);
+ }
+ s = (-b - d) / (2 * a);
+ if ((s > 0.0) && (s < 1.0)) {
+ t = 1.0 - s;
+ yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
+ bbox->y0 = (NR::Coord) MIN (bbox->y0, yttt);
+ bbox->y1 = (NR::Coord) MAX (bbox->y1, yttt);
+ }
+ }
+ }
+}
+
+void
+nr_path_matrix_bbox_union(NRBPath const *bpath, NR::Matrix const &m,
+ NRRect *bbox)
+{
+ using NR::X;
+ using NR::Y;
+
+ if (!bpath->path) {
+ return;
+ }
+
+ NR::Point prev0(0, 0);
+ for (NArtBpath const *p = bpath->path; p->code != NR_END; ++p) {
+ switch (p->code) {
+ case NR_MOVETO_OPEN:
+ case NR_MOVETO: {
+ NR::Point const c3(p->x3,
+ p->y3);
+ prev0 = c3 * m;
+ nr_rect_union_pt(bbox, prev0);
+ break;
+ }
+
+ case NR_LINETO: {
+ NR::Point const c3(p->x3,
+ p->y3);
+ NR::Point const endPt( c3 * m );
+ nr_rect_union_pt(bbox, endPt);
+ prev0 = endPt;
+ break;
+ }
+
+ case NR_CURVETO: {
+ NR::Point const c1(p->x1, p->y1);
+ NR::Point const c2(p->x2, p->y2);
+ NR::Point const c3(p->x3, p->y3);
+ NR::Point const endPt( c3 * m );
+ nr_curve_bbox(prev0,
+ c1 * m,
+ c2 * m,
+ endPt,
+ bbox);
+ prev0 = endPt;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+}
+
diff --git a/src/libnr/nr-path.h b/src/libnr/nr-path.h
new file mode 100644
index 000000000..625a0e498
--- /dev/null
+++ b/src/libnr/nr-path.h
@@ -0,0 +1,62 @@
+#ifndef __NR_PATH_H__
+#define __NR_PATH_H__
+
+/*
+ * NArtBpath: Curve component. Adapted from libart.
+ */
+
+/*
+ * libart_lgpl/art_bpath.h copyright information:
+ *
+ * Copyright (C) 1998 Raph Levien
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#include <libnr/nr-forward.h>
+#include <libnr/nr-coord.h>
+
+NArtBpath* nr_artpath_affine(NArtBpath *s, NR::Matrix const &transform);
+
+//ArtBpath* nr_artpath_to_art_bpath(NArtBpath *s); // this lives in src/extension/internal/gnome.cpp to avoid requiring libart everywhere
+
+struct NRBPath {
+ NArtBpath *path;
+};
+
+NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NRMatrix const *transform);
+
+NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NR::Matrix const transform);
+
+void nr_path_matrix_point_bbox_wind_distance (NRBPath *bpath, NR::Matrix const &m, NR::Point &pt,
+ NRRect *bbox, int *wind, NR::Coord *dist,
+ NR::Coord tolerance);
+
+void nr_path_matrix_bbox_union(NRBPath const *bpath, NR::Matrix const &m, NRRect *bbox);
+
+#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/libnr/nr-pixblock-line.cpp b/src/libnr/nr-pixblock-line.cpp
new file mode 100644
index 000000000..5e025c7eb
--- /dev/null
+++ b/src/libnr/nr-pixblock-line.cpp
@@ -0,0 +1,92 @@
+#define __NR_PIXBLOCK_LINE_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixops.h>
+#include <libnr/nr-pixblock-pixel.h>
+
+void
+nr_pixblock_draw_line_rgba32 (NRPixBlock *d, long x0, long y0, long x1, long y1, short first, unsigned long rgba)
+{
+ long deltax, deltay, xinc1, xinc2, yinc1, yinc2;
+ long den, num, numadd, numpixels;
+ long x, y, curpixel;
+ /* Pixblock */
+ int dbpp;
+ NRPixBlock spb;
+ unsigned char *spx;
+
+ if (x1 >= x0) {
+ deltax = x1 - x0;
+ xinc1 = 1;
+ xinc2 = 1;
+ } else {
+ deltax = x0 - x1;
+ xinc1 = -1;
+ xinc2 = -1;
+ }
+
+ if (y1 >= y0) {
+ deltay = y1 - y0;
+ yinc1 = 1;
+ yinc2 = 1;
+ } else {
+ deltay = y0 - y1;
+ yinc1 = -1;
+ yinc2 = -1;
+ }
+
+ if (deltax >= deltay) {
+ xinc1 = 0;
+ yinc2 = 0;
+ den = deltax;
+ num = deltax / 2;
+ numadd = deltay;
+ numpixels = deltax;
+ } else {
+ xinc2 = 0;
+ yinc1 = 0;
+ den = deltay;
+ num = deltay / 2;
+ numadd = deltax;
+ numpixels = deltay;
+ }
+
+ /* We can be quite sure 1x1 pixblock is TINY */
+ nr_pixblock_setup_fast (&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, 1, 1, 0);
+ spb.empty = 0;
+ spx = NR_PIXBLOCK_PX (&spb);
+ spx[0] = NR_RGBA32_R (rgba);
+ spx[1] = NR_RGBA32_G (rgba);
+ spx[2] = NR_RGBA32_B (rgba);
+ spx[3] = NR_RGBA32_A (rgba);
+
+ dbpp = NR_PIXBLOCK_BPP (d);
+
+ x = x0;
+ y = y0;
+
+ for (curpixel = 0; curpixel <= numpixels; curpixel++) {
+ if ((x >= d->area.x0) && (y >= d->area.y0) && (x < d->area.x1) && (y < d->area.y1)) {
+ nr_compose_pixblock_pixblock_pixel (d, NR_PIXBLOCK_PX (d) + (y - d->area.y0) * d->rs + (x - d->area.x0) * dbpp, &spb, spx);
+ }
+ num += numadd;
+ if (num >= den) {
+ num -= den;
+ x += xinc1;
+ y += yinc1;
+ }
+ x += xinc2;
+ y += yinc2;
+ }
+
+ nr_pixblock_release (&spb);
+}
+
diff --git a/src/libnr/nr-pixblock-line.h b/src/libnr/nr-pixblock-line.h
new file mode 100644
index 000000000..7fd58a0ab
--- /dev/null
+++ b/src/libnr/nr-pixblock-line.h
@@ -0,0 +1,28 @@
+#ifndef __NR_PIXBLOCK_LINE_H__
+#define __NR_PIXBLOCK_LINE_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+
+void nr_pixblock_draw_line_rgba32 (NRPixBlock *d, long x0, long y0, long x1, long y1, short first, unsigned long rgba);
+
+#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/libnr/nr-pixblock-pattern.cpp b/src/libnr/nr-pixblock-pattern.cpp
new file mode 100644
index 000000000..03bd688ba
--- /dev/null
+++ b/src/libnr/nr-pixblock-pattern.cpp
@@ -0,0 +1,126 @@
+#define __NR_PIXBLOCK_PATTERN_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+
+#include "nr-pixops.h"
+#include "nr-pixblock-pattern.h"
+
+#define NR_NOISE_SIZE 1024
+
+void
+nr_pixblock_render_gray_noise (NRPixBlock *pb, NRPixBlock *mask)
+{
+ static unsigned char *noise = NULL;
+ static unsigned int seed = 0;
+ unsigned int v;
+ NRRectL clip;
+ int x, y, bpp;
+
+ if (mask) {
+ if (mask->empty) return;
+ nr_rect_l_intersect (&clip, &pb->area, &mask->area);
+ if (nr_rect_l_test_empty (&clip)) return;
+ } else {
+ clip = pb->area;
+ }
+
+ if (!noise) {
+ int i;
+ noise = nr_new (unsigned char, NR_NOISE_SIZE);
+ for (i = 0; i < NR_NOISE_SIZE; i++) noise[i] = (rand () / (RAND_MAX >> 8)) & 0xff;
+ }
+
+ bpp = NR_PIXBLOCK_BPP (pb);
+
+ v = (rand () / (RAND_MAX >> 8)) & 0xff;
+
+ if (mask) {
+ for (y = clip.y0; y < clip.y1; y++) {
+ unsigned char *d, *m;
+ d = NR_PIXBLOCK_PX (pb) + (y - pb->area.y0) * pb->rs + (clip.x0 - pb->area.x0) * bpp;
+ m = NR_PIXBLOCK_PX (mask) + (y - mask->area.y0) * pb->rs + (clip.x0 - mask->area.x0);
+ for (x = clip.x0; x < clip.x1; x++) {
+ v = v ^ noise[seed];
+ switch (pb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ d[0] = (65025 - (255 - m[0]) * (255 - d[0]) + 127) / 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = NR_COMPOSEN11 (v, m[0], d[0]);
+ d[1] = NR_COMPOSEN11 (v, m[0], d[1]);
+ d[2] = NR_COMPOSEN11 (v, m[0], d[2]);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ if (m[0] != 0) {
+ unsigned int ca;
+ ca = NR_A7 (m[0], d[3]);
+ d[0] = NR_COMPOSENNN_A7 (v, m[0], d[0], d[3], ca);
+ d[1] = NR_COMPOSENNN_A7 (v, m[0], d[1], d[3], ca);
+ d[2] = NR_COMPOSENNN_A7 (v, m[0], d[2], d[3], ca);
+ d[3] = (ca + 127) / 255;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = NR_COMPOSENPP (v, m[0], d[0], d[3]);
+ d[1] = NR_COMPOSENPP (v, m[0], d[1], d[3]);
+ d[2] = NR_COMPOSENPP (v, m[0], d[2], d[3]);
+ d[3] = (NR_A7 (d[3], m[0]) + 127) / 255;
+ break;
+ default:
+ break;
+ }
+ d += bpp;
+ m += 1;
+ if (++seed >= NR_NOISE_SIZE) {
+ int i;
+ i = (rand () / (RAND_MAX / NR_NOISE_SIZE)) % NR_NOISE_SIZE;
+ noise[i] ^= v;
+ seed = i % (NR_NOISE_SIZE >> 2);
+ }
+ }
+ }
+ } else {
+ for (y = clip.y0; y < clip.y1; y++) {
+ unsigned char *d;
+ d = NR_PIXBLOCK_PX (pb) + (y - pb->area.y0) * pb->rs + (clip.x0 - pb->area.x0) * bpp;
+ for (x = clip.x0; x < clip.x1; x++) {
+ v = v ^ noise[seed];
+ switch (pb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ d[0] = 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = v;
+ d[1] = v;
+ d[2] = v;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = v;
+ d[1] = v;
+ d[2] = v;
+ d[3] = 255;
+ default:
+ break;
+ }
+ d += bpp;
+ if (++seed >= NR_NOISE_SIZE) {
+ int i;
+ i = (rand () / (RAND_MAX / NR_NOISE_SIZE)) % NR_NOISE_SIZE;
+ noise[i] ^= v;
+ seed = i % (NR_NOISE_SIZE >> 2);
+ }
+ }
+ }
+ }
+
+ pb->empty = 0;
+}
diff --git a/src/libnr/nr-pixblock-pattern.h b/src/libnr/nr-pixblock-pattern.h
new file mode 100644
index 000000000..463a24379
--- /dev/null
+++ b/src/libnr/nr-pixblock-pattern.h
@@ -0,0 +1,28 @@
+#ifndef __NR_PIXBLOCK_PATTERN_H__
+#define __NR_PIXBLOCK_PATTERN_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+
+void nr_pixblock_render_gray_noise (NRPixBlock *pb, NRPixBlock *mask);
+
+#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/libnr/nr-pixblock-pixel.cpp b/src/libnr/nr-pixblock-pixel.cpp
new file mode 100644
index 000000000..c778c0c7f
--- /dev/null
+++ b/src/libnr/nr-pixblock-pixel.cpp
@@ -0,0 +1,230 @@
+#define __NR_PIXBLOCK_PIXEL_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include "nr-pixops.h"
+#include "nr-pixblock-pixel.h"
+
+void
+nr_compose_pixblock_pixblock_pixel (NRPixBlock *dpb, unsigned char *d, const NRPixBlock *spb, const unsigned char *s)
+{
+ if (spb->empty) return;
+
+ if (dpb->empty) {
+ /* Empty destination */
+ switch (dpb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = s[3];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = s[3];
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = NR_COMPOSEN11 (s[0], s[3], 255);
+ d[1] = NR_COMPOSEN11 (s[1], s[3], 255);
+ d[2] = NR_COMPOSEN11 (s[2], s[3], 255);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = NR_COMPOSEP11 (s[0], s[3], 255);
+ d[1] = NR_COMPOSEP11 (s[1], s[3], 255);
+ d[2] = NR_COMPOSEP11 (s[2], s[3], 255);
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ if (s[3] == 0) {
+ d[0] = 255;
+ d[1] = 255;
+ d[2] = 255;
+ } else {
+ d[0] = (s[0] * 255) / s[3];
+ d[1] = (s[1] * 255) / s[3];
+ d[2] = (s[2] * 255) / s[3];
+ }
+ d[3] = s[3];
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = NR_PREMUL (s[0], s[3]);
+ d[1] = NR_PREMUL (s[1], s[3]);
+ d[2] = NR_PREMUL (s[2], s[3]);
+ d[3] = s[3];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ d[3] = s[3];
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ /* Image destination */
+ switch (dpb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = 255;
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = NR_A7_NORMALIZED(s[3],d[0]);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = NR_A7_NORMALIZED(s[3],d[0]);
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ 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]);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = NR_COMPOSEP11 (s[0], s[3], d[0]);
+ d[1] = NR_COMPOSEP11 (s[1], s[3], d[1]);
+ d[2] = NR_COMPOSEP11 (s[2], s[3], d[2]);
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ if (s[3] != 0) {
+ unsigned int ca;
+ 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] = (ca + 127) / 255;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ if (s[3] != 0) {
+ unsigned int ca;
+ ca = NR_A7 (s[3], d[3]);
+ d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca);
+ d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[0], d[3], ca);
+ d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[0], d[3], ca);
+ d[3] = (ca + 127) / 255;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ switch (spb->mode) {
+ case NR_PIXBLOCK_MODE_A8:
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8:
+ d[0] = s[0];
+ d[1] = s[1];
+ d[2] = s[2];
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8N:
+ d[0] = NR_COMPOSENPP (s[0], s[3], d[0], d[3]);
+ d[1] = NR_COMPOSENPP (s[1], s[3], d[1], d[3]);
+ d[2] = NR_COMPOSENPP (s[2], s[3], d[2], d[3]);
+ d[3] = NR_A7_NORMALIZED(s[3],d[3]);
+ break;
+ case NR_PIXBLOCK_MODE_R8G8B8A8P:
+ d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]);
+ d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]);
+ d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]);
+ d[3] = NR_A7_NORMALIZED(s[3],d[3]);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
diff --git a/src/libnr/nr-pixblock-pixel.h b/src/libnr/nr-pixblock-pixel.h
new file mode 100644
index 000000000..d989f53cf
--- /dev/null
+++ b/src/libnr/nr-pixblock-pixel.h
@@ -0,0 +1,28 @@
+#ifndef __NR_PIXBLOCK_PIXEL_H__
+#define __NR_PIXBLOCK_PIXEL_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+
+void nr_compose_pixblock_pixblock_pixel (NRPixBlock *dpb, unsigned char *d, const NRPixBlock *spb, const unsigned char *s);
+
+#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/libnr/nr-pixblock.cpp b/src/libnr/nr-pixblock.cpp
new file mode 100644
index 000000000..9b1ff2752
--- /dev/null
+++ b/src/libnr/nr-pixblock.cpp
@@ -0,0 +1,411 @@
+#define __NR_PIXBLOCK_C__
+
+/** \file
+ * \brief Allocation/Setup of NRPixBlock objects. Pixel store functions.
+ *
+ * Authors:
+ * (C) 1999-2002 Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in the Public Domain
+ */
+
+#include "nr-pixblock.h"
+
+/// Size of buffer that needs no allocation (default 4).
+#define NR_TINY_MAX sizeof (unsigned char *)
+
+/**
+ * Pixbuf initialisation using homegrown memory handling ("pixelstore").
+ *
+ * Pixbuf sizes are differentiated into tiny, <4K, <16K, <64K, and more,
+ * with each type having its own method of memory handling. After allocating
+ * memory, the buffer is cleared if the clear flag is set. Intended to
+ * reduce memory fragmentation.
+ * \param pb Pointer to the pixbuf struct.
+ * \param mode Indicates grayscale/RGB/RGBA.
+ * \param clear True if buffer should be cleared.
+ * \pre x1>=x0 && y1>=y0 && pb!=NULL
+ */
+void
+nr_pixblock_setup_fast (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear)
+{
+ int w, h, bpp;
+ size_t size;
+
+ w = x1 - x0;
+ h = y1 - y0;
+ bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4;
+
+ size = bpp * w * h;
+
+ if (size <= NR_TINY_MAX) {
+ pb->size = NR_PIXBLOCK_SIZE_TINY;
+ if (clear) memset (pb->data.p, 0x0, size);
+ } else if (size <= 4096) {
+ pb->size = NR_PIXBLOCK_SIZE_4K;
+ pb->data.px = nr_pixelstore_4K_new (clear, 0x0);
+ } else if (size <= 16384) {
+ pb->size = NR_PIXBLOCK_SIZE_16K;
+ pb->data.px = nr_pixelstore_16K_new (clear, 0x0);
+ } else if (size <= 65536) {
+ pb->size = NR_PIXBLOCK_SIZE_64K;
+ pb->data.px = nr_pixelstore_64K_new (clear, 0x0);
+ } else if (size <= 262144) {
+ pb->size = NR_PIXBLOCK_SIZE_256K;
+ pb->data.px = nr_pixelstore_256K_new (clear, 0x0);
+ } else if (size <= 1048576) {
+ pb->size = NR_PIXBLOCK_SIZE_1M;
+ pb->data.px = nr_pixelstore_1M_new (clear, 0x0);
+ } else {
+ pb->size = NR_PIXBLOCK_SIZE_BIG;
+ pb->data.px = nr_new (unsigned char, size);
+ if (clear) memset (pb->data.px, 0x0, size);
+ }
+
+ pb->mode = mode;
+ pb->empty = 1;
+ pb->area.x0 = x0;
+ pb->area.y0 = y0;
+ pb->area.x1 = x1;
+ pb->area.y1 = y1;
+ pb->rs = bpp * w;
+}
+
+/**
+ * Pixbuf initialisation using nr_new.
+ *
+ * After allocating memory, the buffer is cleared if the clear flag is set.
+ * \param pb Pointer to the pixbuf struct.
+ * \param mode Indicates grayscale/RGB/RGBA.
+ * \param clear True if buffer should be cleared.
+ * \pre x1>=x0 && y1>=y0 && pb!=NULL
+ */
+void
+nr_pixblock_setup (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear)
+{
+ int w, h, bpp;
+ size_t size;
+
+ w = x1 - x0;
+ h = y1 - y0;
+ bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4;
+
+ size = bpp * w * h;
+
+ if (size <= NR_TINY_MAX) {
+ pb->size = NR_PIXBLOCK_SIZE_TINY;
+ if (clear) memset (pb->data.p, 0x0, size);
+ } else {
+ pb->size = NR_PIXBLOCK_SIZE_BIG;
+ pb->data.px = nr_new (unsigned char, size);
+ if (clear) memset (pb->data.px, 0x0, size);
+ }
+
+ pb->mode = mode;
+ pb->empty = 1;
+ pb->area.x0 = x0;
+ pb->area.y0 = y0;
+ pb->area.x1 = x1;
+ pb->area.y1 = y1;
+ pb->rs = bpp * w;
+}
+
+/**
+ * Pixbuf initialisation with preset values.
+ *
+ * After copying all parameters into the NRPixBlock struct, the pixel buffer is cleared if the clear flag is set.
+ * \param pb Pointer to the pixbuf struct.
+ * \param mode Indicates grayscale/RGB/RGBA.
+ * \param clear True if buffer should be cleared.
+ * \pre x1>=x0 && y1>=y0 && pb!=NULL
+ */
+void
+nr_pixblock_setup_extern (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, unsigned char *px, int rs, bool empty, bool clear)
+{
+ int w, bpp;
+
+ w = x1 - x0;
+ bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4;
+
+ pb->size = NR_PIXBLOCK_SIZE_STATIC;
+ pb->mode = mode;
+ pb->empty = empty;
+ pb->area.x0 = x0;
+ pb->area.y0 = y0;
+ pb->area.x1 = x1;
+ pb->area.y1 = y1;
+ pb->data.px = px;
+ pb->rs = rs;
+
+ g_assert (pb->data.px != NULL);
+ if (clear) {
+ if (rs == bpp * w) {
+ /// \todo How do you recognise if
+ /// px was an uncleared tiny buffer?
+ if (pb->data.px)
+ memset (pb->data.px, 0x0, bpp * (y1 - y0) * w);
+ } else {
+ int y;
+ for (y = y0; y < y1; y++) {
+ memset (pb->data.px + (y - y0) * rs, 0x0, bpp * w);
+ }
+ }
+ }
+}
+
+/**
+ * Frees memory taken by pixel data in NRPixBlock.
+ * \param pb Pointer to pixblock.
+ * \pre pb and pb->data.px point to valid addresses.
+ *
+ * According to pb->size, one of the functions for freeing the pixelstore
+ * is called. May be called regardless of how pixbuf was set up.
+ */
+void
+nr_pixblock_release (NRPixBlock *pb)
+{
+ switch (pb->size) {
+ case NR_PIXBLOCK_SIZE_TINY:
+ break;
+ case NR_PIXBLOCK_SIZE_4K:
+ nr_pixelstore_4K_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_16K:
+ nr_pixelstore_16K_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_64K:
+ nr_pixelstore_64K_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_256K:
+ nr_pixelstore_256K_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_1M:
+ nr_pixelstore_1M_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_BIG:
+ nr_free (pb->data.px);
+ break;
+ case NR_PIXBLOCK_SIZE_STATIC:
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Allocates NRPixBlock and sets it up.
+ *
+ * \return Pointer to fresh pixblock.
+ * Calls nr_new() and nr_pixblock_setup().
+ */
+NRPixBlock *
+nr_pixblock_new (NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear)
+{
+ NRPixBlock *pb;
+
+ pb = nr_new (NRPixBlock, 1);
+
+ nr_pixblock_setup (pb, mode, x0, y0, x1, y1, clear);
+
+ return pb;
+}
+
+/**
+ * Frees all memory taken by pixblock.
+ *
+ * \return NULL
+ */
+NRPixBlock *
+nr_pixblock_free (NRPixBlock *pb)
+{
+ nr_pixblock_release (pb);
+
+ nr_free (pb);
+
+ return NULL;
+}
+
+/* PixelStore operations */
+
+#define NR_4K_BLOCK 32
+static unsigned char **nr_4K_px = NULL;
+static unsigned int nr_4K_len = 0;
+static unsigned int nr_4K_size = 0;
+
+unsigned char *
+nr_pixelstore_4K_new (bool clear, unsigned char val)
+{
+ unsigned char *px;
+
+ if (nr_4K_len != 0) {
+ nr_4K_len -= 1;
+ px = nr_4K_px[nr_4K_len];
+ } else {
+ px = nr_new (unsigned char, 4096);
+ }
+
+ if (clear) memset (px, val, 4096);
+
+ return px;
+}
+
+void
+nr_pixelstore_4K_free (unsigned char *px)
+{
+ if (nr_4K_len == nr_4K_size) {
+ nr_4K_size += NR_4K_BLOCK;
+ nr_4K_px = nr_renew (nr_4K_px, unsigned char *, nr_4K_size);
+ }
+
+ nr_4K_px[nr_4K_len] = px;
+ nr_4K_len += 1;
+}
+
+#define NR_16K_BLOCK 32
+static unsigned char **nr_16K_px = NULL;
+static unsigned int nr_16K_len = 0;
+static unsigned int nr_16K_size = 0;
+
+unsigned char *
+nr_pixelstore_16K_new (bool clear, unsigned char val)
+{
+ unsigned char *px;
+
+ if (nr_16K_len != 0) {
+ nr_16K_len -= 1;
+ px = nr_16K_px[nr_16K_len];
+ } else {
+ px = nr_new (unsigned char, 16384);
+ }
+
+ if (clear) memset (px, val, 16384);
+
+ return px;
+}
+
+void
+nr_pixelstore_16K_free (unsigned char *px)
+{
+ if (nr_16K_len == nr_16K_size) {
+ nr_16K_size += NR_16K_BLOCK;
+ nr_16K_px = nr_renew (nr_16K_px, unsigned char *, nr_16K_size);
+ }
+
+ nr_16K_px[nr_16K_len] = px;
+ nr_16K_len += 1;
+}
+
+#define NR_64K_BLOCK 32
+static unsigned char **nr_64K_px = NULL;
+static unsigned int nr_64K_len = 0;
+static unsigned int nr_64K_size = 0;
+
+unsigned char *
+nr_pixelstore_64K_new (bool clear, unsigned char val)
+{
+ unsigned char *px;
+
+ if (nr_64K_len != 0) {
+ nr_64K_len -= 1;
+ px = nr_64K_px[nr_64K_len];
+ } else {
+ px = nr_new (unsigned char, 65536);
+ }
+
+ if (clear) memset (px, val, 65536);
+
+ return px;
+}
+
+void
+nr_pixelstore_64K_free (unsigned char *px)
+{
+ if (nr_64K_len == nr_64K_size) {
+ nr_64K_size += NR_64K_BLOCK;
+ nr_64K_px = nr_renew (nr_64K_px, unsigned char *, nr_64K_size);
+ }
+
+ nr_64K_px[nr_64K_len] = px;
+ nr_64K_len += 1;
+}
+
+#define NR_256K_BLOCK 32
+#define NR_256K 262144
+static unsigned char **nr_256K_px = NULL;
+static unsigned int nr_256K_len = 0;
+static unsigned int nr_256K_size = 0;
+
+unsigned char *
+nr_pixelstore_256K_new (bool clear, unsigned char val)
+{
+ unsigned char *px;
+
+ if (nr_256K_len != 0) {
+ nr_256K_len -= 1;
+ px = nr_256K_px[nr_256K_len];
+ } else {
+ px = nr_new (unsigned char, NR_256K);
+ }
+
+ if (clear) memset (px, val, NR_256K);
+
+ return px;
+}
+
+void
+nr_pixelstore_256K_free (unsigned char *px)
+{
+ if (nr_256K_len == nr_256K_size) {
+ nr_256K_size += NR_256K_BLOCK;
+ nr_256K_px = nr_renew (nr_256K_px, unsigned char *, nr_256K_size);
+ }
+
+ nr_256K_px[nr_256K_len] = px;
+ nr_256K_len += 1;
+}
+
+#define NR_1M_BLOCK 32
+#define NR_1M 1048576
+static unsigned char **nr_1M_px = NULL;
+static unsigned int nr_1M_len = 0;
+static unsigned int nr_1M_size = 0;
+
+unsigned char *
+nr_pixelstore_1M_new (bool clear, unsigned char val)
+{
+ unsigned char *px;
+
+ if (nr_1M_len != 0) {
+ nr_1M_len -= 1;
+ px = nr_1M_px[nr_1M_len];
+ } else {
+ px = nr_new (unsigned char, NR_1M);
+ }
+
+ if (clear) memset (px, val, NR_1M);
+
+ return px;
+}
+
+void
+nr_pixelstore_1M_free (unsigned char *px)
+{
+ if (nr_1M_len == nr_1M_size) {
+ nr_1M_size += NR_1M_BLOCK;
+ nr_1M_px = nr_renew (nr_1M_px, unsigned char *, nr_1M_size);
+ }
+
+ nr_1M_px[nr_1M_len] = px;
+ nr_1M_len += 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/libnr/nr-pixblock.h b/src/libnr/nr-pixblock.h
new file mode 100644
index 000000000..2a13dd921
--- /dev/null
+++ b/src/libnr/nr-pixblock.h
@@ -0,0 +1,95 @@
+#ifndef __NR_PIXBLOCK_H__
+#define __NR_PIXBLOCK_H__
+
+/** \file
+ * \brief Pixel block structure. Used for low-level rendering.
+ *
+ * Authors:
+ * (C) 1999-2002 Lauris Kaplinski <lauris@kaplinski.com>
+ * (C) 2005 Ralf Stephan <ralf@ark.in-berlin.de> (some cleanup)
+ *
+ * This code is in the Public Domain.
+ */
+
+#include <libnr/nr-rect-l.h>
+#include <libnr/nr-forward.h>
+
+/// Size indicator. Hardcoded to max. 3 bits.
+typedef enum {
+ NR_PIXBLOCK_SIZE_TINY, ///< Fits in (unsigned char *)
+ NR_PIXBLOCK_SIZE_4K, ///< Pixelstore
+ NR_PIXBLOCK_SIZE_16K, ///< Pixelstore
+ NR_PIXBLOCK_SIZE_64K, ///< Pixelstore
+ NR_PIXBLOCK_SIZE_256K, ///< Pixelstore
+ NR_PIXBLOCK_SIZE_1M, ///< Pixelstore
+ NR_PIXBLOCK_SIZE_BIG, ///< Normally allocated
+ NR_PIXBLOCK_SIZE_STATIC ///< Externally managed
+} NR_PIXBLOCK_SIZE;
+
+/// Mode indicator. Hardcoded to max. 2 bits.
+typedef enum {
+ NR_PIXBLOCK_MODE_A8, ///< Grayscale
+ NR_PIXBLOCK_MODE_R8G8B8, ///< 8 bit RGB
+ NR_PIXBLOCK_MODE_R8G8B8A8N, ///< Normal 8 bit RGBA
+ NR_PIXBLOCK_MODE_R8G8B8A8P ///< Premultiplied 8 bit RGBA
+} NR_PIXBLOCK_MODE;
+
+/// The pixel block struct.
+struct NRPixBlock {
+ NR_PIXBLOCK_SIZE size : 3; ///< Size indicator
+ NR_PIXBLOCK_MODE mode : 2; ///< Mode indicator
+ bool empty : 1; ///< Empty flag
+ unsigned int rs; ///< Size of line in bytes
+ NRRectL area;
+ union {
+ unsigned char *px; ///< Pointer to buffer
+ unsigned char p[sizeof (unsigned char *)]; ///< Tiny buffer
+ } data;
+};
+
+/// Returns number of bytes per pixel (1, 3, or 4).
+inline int
+NR_PIXBLOCK_BPP (NRPixBlock *pb)
+{
+ return ((pb->mode == NR_PIXBLOCK_MODE_A8) ? 1 :
+ (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4);
+}
+
+/// Returns pointer to pixel data.
+inline unsigned char*
+NR_PIXBLOCK_PX (NRPixBlock *pb)
+{
+ return ((pb->size == NR_PIXBLOCK_SIZE_TINY) ?
+ pb->data.p : pb->data.px);
+}
+
+void nr_pixblock_setup (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear);
+void nr_pixblock_setup_fast (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear);
+void nr_pixblock_setup_extern (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, unsigned char *px, int rs, bool empty, bool clear);
+void nr_pixblock_release (NRPixBlock *pb);
+
+NRPixBlock *nr_pixblock_new (NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear);
+NRPixBlock *nr_pixblock_free (NRPixBlock *pb);
+
+unsigned char *nr_pixelstore_4K_new (bool clear, unsigned char val);
+void nr_pixelstore_4K_free (unsigned char *px);
+unsigned char *nr_pixelstore_16K_new (bool clear, unsigned char val);
+void nr_pixelstore_16K_free (unsigned char *px);
+unsigned char *nr_pixelstore_64K_new (bool clear, unsigned char val);
+void nr_pixelstore_64K_free (unsigned char *px);
+unsigned char *nr_pixelstore_256K_new (bool clear, unsigned char val);
+void nr_pixelstore_256K_free (unsigned char *px);
+unsigned char *nr_pixelstore_1M_new (bool clear, unsigned char val);
+void nr_pixelstore_1M_free (unsigned char *px);
+
+#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/libnr/nr-pixops.h b/src/libnr/nr-pixops.h
new file mode 100644
index 000000000..32fc1e1cd
--- /dev/null
+++ b/src/libnr/nr-pixops.h
@@ -0,0 +1,46 @@
+#ifndef __NR_PIXOPS_H__
+#define __NR_PIXOPS_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * This code is in public domain
+ */
+
+#define NR_RGBA32_R(v) (unsigned char) (((v) >> 24) & 0xff)
+#define NR_RGBA32_G(v) (unsigned char) (((v) >> 16) & 0xff)
+#define NR_RGBA32_B(v) (unsigned char) (((v) >> 8) & 0xff)
+#define NR_RGBA32_A(v) (unsigned char) ((v) & 0xff)
+
+#define FAST_DIVIDE_BY_255(v) ((((v) << 8) + (v) + 257) >> 16)
+#define NR_PREMUL(c,a) (FAST_DIVIDE_BY_255(((c) * (a) + 127)))
+#define NR_PREMUL_SINGLE(c) (FAST_DIVIDE_BY_255((c) + 127))
+#define NR_A7(fa,ba) (65025 - (255 - fa) * (255 - ba))
+#define NR_A7_NORMALIZED(fa,ba) (FAST_DIVIDE_BY_255((65025 - (255 - (fa)) * (255 - (ba))) + 127))
+#define NR_COMPOSENNN_A7(fc,fa,bc,ba,a) (((255 - (fa)) * (bc) * (ba) + (fa) * (fc) * 255 + 127) / a)
+#define NR_COMPOSEPNN_A7(fc,fa,bc,ba,a) (((255 - (fa)) * (bc) * (ba) + (fc) * 65025 + 127) / a)
+#define NR_COMPOSENNP(fc,fa,bc,ba) (((255 - (fa)) * (bc) * (ba) + (fa) * (fc) * 255 + 32512) / 65025)
+#define NR_COMPOSEPNP(fc,fa,bc,ba) (((255 - (fa)) * (bc) * (ba) + (fc) * 65025 + 32512) / 65025)
+#define NR_COMPOSENPP(fc,fa,bc,ba) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fa) * (fc) + 127))
+#define NR_COMPOSEPPP(fc,fa,bc,ba) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * 255 + 127))
+#define NR_COMPOSEP11(fc,fa,bc) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * 255 + 127))
+#define NR_COMPOSEN11(fc,fa,bc) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * (fa) + 127))
+
+#define INK_COMPOSE(f,a,b) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) >>8)
+
+#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/libnr/nr-point-fns-test.cpp b/src/libnr/nr-point-fns-test.cpp
new file mode 100644
index 000000000..11181436b
--- /dev/null
+++ b/src/libnr/nr-point-fns-test.cpp
@@ -0,0 +1,107 @@
+#include <cassert>
+#include <cmath>
+#include <glib/gmacros.h>
+#include <stdlib.h>
+
+#include "utest/utest.h"
+#include "libnr/nr-point-fns.h"
+#include "isnan.h"
+
+using NR::Point;
+
+int main(int argc, char *argv[])
+{
+ utest_start("nr-point-fns");
+
+ Point const p3n4(3.0, -4.0);
+ Point const p0(0.0, 0.0);
+ double const small = pow(2.0, -1070);
+ double const inf = 1e400;
+ double const nan = inf - inf;
+
+ Point const small_left(-small, 0.0);
+ Point const small_n3_4(-3.0 * small, 4.0 * small);
+ Point const part_nan(3., nan);
+ Point const inf_left(-inf, 5.0);
+
+ assert(isNaN(nan));
+ assert(!isNaN(small));
+
+ UTEST_TEST("L1") {
+ UTEST_ASSERT( NR::L1(p0) == 0.0 );
+ UTEST_ASSERT( NR::L1(p3n4) == 7.0 );
+ UTEST_ASSERT( NR::L1(small_left) == small );
+ UTEST_ASSERT( NR::L1(inf_left) == inf );
+ UTEST_ASSERT( NR::L1(small_n3_4) == 7.0 * small );
+ UTEST_ASSERT(isNaN(NR::L1(part_nan)));
+ }
+
+ UTEST_TEST("L2") {
+ UTEST_ASSERT( NR::L2(p0) == 0.0 );
+ UTEST_ASSERT( NR::L2(p3n4) == 5.0 );
+ UTEST_ASSERT( NR::L2(small_left) == small );
+ UTEST_ASSERT( NR::L2(inf_left) == inf );
+ UTEST_ASSERT( NR::L2(small_n3_4) == 5.0 * small );
+ UTEST_ASSERT(isNaN(NR::L2(part_nan)));
+ }
+
+ UTEST_TEST("LInfty") {
+ UTEST_ASSERT( NR::LInfty(p0) == 0.0 );
+ UTEST_ASSERT( NR::LInfty(p3n4) == 4.0 );
+ UTEST_ASSERT( NR::LInfty(small_left) == small );
+ UTEST_ASSERT( NR::LInfty(inf_left) == inf );
+ UTEST_ASSERT( NR::LInfty(small_n3_4) == 4.0 * small );
+ UTEST_ASSERT(isNaN(NR::LInfty(part_nan)));
+ }
+
+ UTEST_TEST("is_zero") {
+ UTEST_ASSERT(NR::is_zero(p0));
+ UTEST_ASSERT(!NR::is_zero(p3n4));
+ UTEST_ASSERT(!NR::is_zero(small_left));
+ UTEST_ASSERT(!NR::is_zero(inf_left));
+ UTEST_ASSERT(!NR::is_zero(small_n3_4));
+ UTEST_ASSERT(!NR::is_zero(part_nan));
+ }
+
+ UTEST_TEST("atan2") {
+ UTEST_ASSERT( NR::atan2(p3n4) == atan2(-4.0, 3.0) );
+ UTEST_ASSERT( NR::atan2(small_left) == atan2(0.0, -1.0) );
+ UTEST_ASSERT( NR::atan2(small_n3_4) == atan2(4.0, -3.0) );
+ }
+
+ UTEST_TEST("unit_vector") {
+ UTEST_ASSERT( NR::unit_vector(p3n4) == Point(.6, -0.8) );
+ UTEST_ASSERT( NR::unit_vector(small_left) == Point(-1.0, 0.0) );
+ UTEST_ASSERT( NR::unit_vector(small_n3_4) == Point(-.6, 0.8) );
+ }
+
+ UTEST_TEST("is_unit_vector") {
+ UTEST_ASSERT(!NR::is_unit_vector(p3n4));
+ UTEST_ASSERT(!NR::is_unit_vector(small_left));
+ UTEST_ASSERT(!NR::is_unit_vector(small_n3_4));
+ UTEST_ASSERT(!NR::is_unit_vector(part_nan));
+ UTEST_ASSERT(!NR::is_unit_vector(inf_left));
+ UTEST_ASSERT(!NR::is_unit_vector(Point(.5, 0.5)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(.6, -0.8)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(-.6, 0.8)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(-1.0, 0.0)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(1.0, 0.0)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(0.0, -1.0)));
+ UTEST_ASSERT(NR::is_unit_vector(Point(0.0, 1.0)));
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/nr-point-fns-test.h b/src/libnr/nr-point-fns-test.h
new file mode 100644
index 000000000..509d9d2fa
--- /dev/null
+++ b/src/libnr/nr-point-fns-test.h
@@ -0,0 +1,141 @@
+// nr-point-fns-test.h
+#include <cxxtest/TestSuite.h>
+
+#include <cassert>
+#include <cmath>
+#include <glib/gmacros.h>
+#include <stdlib.h>
+
+#include "libnr/nr-point-fns.h"
+#include "isnan.h"
+
+using NR::Point;
+
+class NrPointFnsTest : public CxxTest::TestSuite
+{
+public:
+ NrPointFnsTest() :
+ setupValid(true),
+ p3n4( 3.0, -4.0 ),
+ p0( 0.0, 0.0 ),
+ small( pow( 2.0, -1070 ) ),
+ inf( 1e400 ),
+ nan( inf - inf ),
+ small_left( -small, 0.0 ),
+ small_n3_4( -3.0 * small, 4.0 * small ),
+ part_nan( 3., nan ),
+ inf_left( -inf, 5.0 )
+ {
+ TS_ASSERT( isNaN(nan) );
+ TS_ASSERT( !isNaN(small) );
+
+ setupValid &= isNaN(nan);
+ setupValid &= !isNaN(small);
+ }
+ virtual ~NrPointFnsTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrPointFnsTest *createSuite() { return new NrPointFnsTest(); }
+ static void destroySuite( NrPointFnsTest *suite ) { delete suite; }
+
+// Called before each test in this suite
+ void setUp()
+ {
+ TS_ASSERT( setupValid );
+ }
+
+ bool setupValid;
+ Point const p3n4;
+ Point const p0;
+ double const small;
+ double const inf;
+ double const nan;
+
+ Point const small_left;
+ Point const small_n3_4;
+ Point const part_nan;
+ Point const inf_left;
+
+
+ void testL1(void)
+ {
+ TS_ASSERT_EQUALS( NR::L1(p0), 0.0 );
+ TS_ASSERT_EQUALS( NR::L1(p3n4), 7.0 );
+ TS_ASSERT_EQUALS( NR::L1(small_left), small );
+ TS_ASSERT_EQUALS( NR::L1(inf_left), inf );
+ TS_ASSERT_EQUALS( NR::L1(small_n3_4), 7.0 * small );
+ TS_ASSERT(isNaN(NR::L1(part_nan)));
+ }
+
+ void testL2(void)
+ {
+ TS_ASSERT_EQUALS( NR::L2(p0), 0.0 );
+ TS_ASSERT_EQUALS( NR::L2(p3n4), 5.0 );
+ TS_ASSERT_EQUALS( NR::L2(small_left), small );
+ TS_ASSERT_EQUALS( NR::L2(inf_left), inf );
+ TS_ASSERT_EQUALS( NR::L2(small_n3_4), 5.0 * small );
+ TS_ASSERT( isNaN(NR::L2(part_nan)) );
+ }
+
+ void testLInfty(void)
+ {
+ TS_ASSERT_EQUALS( NR::LInfty(p0), 0.0 );
+ TS_ASSERT_EQUALS( NR::LInfty(p3n4), 4.0 );
+ TS_ASSERT_EQUALS( NR::LInfty(small_left), small );
+ TS_ASSERT_EQUALS( NR::LInfty(inf_left), inf );
+ TS_ASSERT_EQUALS( NR::LInfty(small_n3_4), 4.0 * small );
+ TS_ASSERT( isNaN(NR::LInfty(part_nan)) );
+ }
+
+ void testIsZero(void)
+ {
+ TS_ASSERT( NR::is_zero(p0) );
+ TS_ASSERT( !NR::is_zero(p3n4) );
+ TS_ASSERT( !NR::is_zero(small_left) );
+ TS_ASSERT( !NR::is_zero(inf_left) );
+ TS_ASSERT( !NR::is_zero(small_n3_4) );
+ TS_ASSERT( !NR::is_zero(part_nan) );
+ }
+
+ void testAtan2(void)
+ {
+ TS_ASSERT_EQUALS( NR::atan2(p3n4), atan2(-4.0, 3.0) );
+ TS_ASSERT_EQUALS( NR::atan2(small_left), atan2(0.0, -1.0) );
+ TS_ASSERT_EQUALS( NR::atan2(small_n3_4), atan2(4.0, -3.0) );
+ }
+
+ void testUnitVector(void)
+ {
+ TS_ASSERT_EQUALS( NR::unit_vector(p3n4), Point(.6, -0.8) );
+ TS_ASSERT_EQUALS( NR::unit_vector(small_left), Point(-1.0, 0.0) );
+ TS_ASSERT_EQUALS( NR::unit_vector(small_n3_4), Point(-.6, 0.8) );
+ }
+
+ void testIsUnitVector(void)
+ {
+ TS_ASSERT( !NR::is_unit_vector(p3n4) );
+ TS_ASSERT( !NR::is_unit_vector(small_left) );
+ TS_ASSERT( !NR::is_unit_vector(small_n3_4) );
+ TS_ASSERT( !NR::is_unit_vector(part_nan) );
+ TS_ASSERT( !NR::is_unit_vector(inf_left) );
+ TS_ASSERT( !NR::is_unit_vector(Point(.5, 0.5)) );
+ TS_ASSERT( NR::is_unit_vector(Point(.6, -0.8)) );
+ TS_ASSERT( NR::is_unit_vector(Point(-.6, 0.8)) );
+ TS_ASSERT( NR::is_unit_vector(Point(-1.0, 0.0)) );
+ TS_ASSERT( NR::is_unit_vector(Point(1.0, 0.0)) );
+ TS_ASSERT( NR::is_unit_vector(Point(0.0, -1.0)) );
+ TS_ASSERT( NR::is_unit_vector(Point(0.0, 1.0)) );
+ }
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-point-fns.cpp b/src/libnr/nr-point-fns.cpp
new file mode 100644
index 000000000..a92c2b00b
--- /dev/null
+++ b/src/libnr/nr-point-fns.cpp
@@ -0,0 +1,74 @@
+#include <libnr/nr-point-fns.h>
+#include <isnan.h>
+
+using NR::Point;
+
+/** Compute the L infinity, or maximum, norm of \a p. */
+NR::Coord NR::LInfty(Point const &p) {
+ NR::Coord const a(fabs(p[0]));
+ NR::Coord const b(fabs(p[1]));
+ return ( a < b || isNaN(b)
+ ? b
+ : a );
+}
+
+/** Returns true iff p is a zero vector, i.e.\ Point(0, 0).
+ *
+ * (NaN is considered non-zero.)
+ */
+bool
+NR::is_zero(Point const &p)
+{
+ return ( p[0] == 0 &&
+ p[1] == 0 );
+}
+
+bool
+NR::is_unit_vector(Point const &p)
+{
+ return fabs(1.0 - L2(p)) <= 1e-4;
+ /* The tolerance of 1e-4 is somewhat arbitrary. NR::Point::normalize is believed to return
+ points well within this tolerance. I'm not aware of any callers that want a small
+ tolerance; most callers would be ok with a tolerance of 0.25. */
+}
+
+NR::Coord NR::atan2(Point const p) {
+ return std::atan2(p[NR::Y], p[NR::X]);
+}
+
+/** Returns a version of \a a scaled to be a unit vector (within rounding error).
+ *
+ * The current version tries to handle infinite coordinates gracefully,
+ * but it's not clear that any callers need that.
+ *
+ * \pre a != Point(0, 0).
+ * \pre Neither coordinate is NaN.
+ * \post L2(ret) very near 1.0.
+ */
+Point NR::unit_vector(Point const &a)
+{
+ Point ret(a);
+ ret.normalize();
+ return ret;
+}
+
+NR::Point abs(NR::Point const &b)
+{
+ NR::Point ret;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ ret[i] = fabs(b[i]);
+ }
+ return ret;
+}
+
+
+/*
+ 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/libnr/nr-point-fns.h b/src/libnr/nr-point-fns.h
new file mode 100644
index 000000000..9f9a8f9e2
--- /dev/null
+++ b/src/libnr/nr-point-fns.h
@@ -0,0 +1,104 @@
+#ifndef __NR_POINT_OPS_H__
+#define __NR_POINT_OPS_H__
+
+#include <libnr/nr-point-ops.h>
+#include <libnr/nr-dim2.h>
+#include <libnr/nr-macros.h>
+
+namespace NR {
+
+/** Compute the L1 norm, or manhattan distance, of \a p. */
+inline Coord L1(Point const &p) {
+ Coord d = 0;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ d += fabs(p[i]);
+ }
+ return d;
+}
+
+/** Compute the L2, or euclidean, norm of \a p. */
+inline Coord L2(Point const &p) {
+ return hypot(p[0], p[1]);
+}
+
+extern double LInfty(Point const &p);
+
+bool is_zero(Point const &p);
+
+bool is_unit_vector(Point const &p);
+
+extern double atan2(Point const p);
+
+inline bool point_equalp(Point const &a, Point const &b, double const eps)
+{
+ return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) &&
+ NR_DF_TEST_CLOSE(a[Y], b[Y], eps) );
+}
+
+/** Returns p * NR::rotate_degrees(90), but more efficient.
+ *
+ * Angle direction in Inkscape code: If you use the traditional mathematics convention that y
+ * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If
+ * you take the common non-mathematical convention that y increases downwards, then positive angles
+ * are clockwise, as is common outside of mathematics.
+ *
+ * There is no rot_neg90 function: use -rot90(p) instead.
+ */
+inline Point rot90(Point const &p)
+{
+ return Point(-p[Y], p[X]);
+}
+
+/** Given two points and a parameter t \in [0, 1], return a point
+ * proportionally from a to b by t. */
+inline Point Lerp(double const t, Point const a, Point const b)
+{
+ return ( ( 1 - t ) * a
+ + t * b );
+}
+
+Point unit_vector(Point const &a);
+
+inline Coord dot(Point const &a, Point const &b)
+{
+ Coord ret = 0;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ ret += a[i] * b[i];
+ }
+ return ret;
+}
+
+inline Coord distance (Point const &a, Point const &b)
+{
+ Coord ret = 0;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ ret += (a[i] - b[i]) * (a[i] - b[i]);
+ }
+ return sqrt (ret);
+}
+
+/** Defined as dot(a, b.cw()). */
+inline Coord cross(Point const &a, Point const &b)
+{
+ Coord ret = 0;
+ ret -= a[0] * b[1];
+ ret += a[1] * b[0];
+ return ret;
+}
+
+Point abs(Point const &b);
+
+} /* namespace NR */
+
+#endif /* !__NR_POINT_OPS_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/libnr/nr-point-l.h b/src/libnr/nr-point-l.h
new file mode 100644
index 000000000..8ddfd5e6f
--- /dev/null
+++ b/src/libnr/nr-point-l.h
@@ -0,0 +1,95 @@
+#ifndef SEEN_NR_POINT_L_H
+#define SEEN_NR_POINT_L_H
+
+#include <stdexcept>
+#include <libnr/nr-i-coord.h>
+#include <libnr/nr-point.h>
+
+struct NRPointL {
+ NR::ICoord x, y;
+};
+
+namespace NR {
+
+class IPoint {
+public:
+ IPoint()
+ { }
+
+ IPoint(ICoord x, ICoord y) {
+ _pt[X] = x;
+ _pt[Y] = y;
+ }
+
+ IPoint(NRPointL const &p) {
+ _pt[X] = p.x;
+ _pt[Y] = p.y;
+ }
+
+ IPoint(IPoint const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ }
+
+ IPoint &operator=(IPoint const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ return *this;
+ }
+
+ operator Point() {
+ return Point(_pt[X], _pt[Y]);
+ }
+
+ ICoord operator[](unsigned i) const throw(std::out_of_range) {
+ if ( i > Y ) {
+ throw std::out_of_range("index out of range");
+ }
+ return _pt[i];
+ }
+
+ ICoord &operator[](unsigned i) throw(std::out_of_range) {
+ if ( i > Y ) {
+ throw std::out_of_range("index out of range");
+ }
+ return _pt[i];
+ }
+
+ ICoord operator[](Dim2 d) const throw() { return _pt[d]; }
+ ICoord &operator[](Dim2 d) throw() { return _pt[d]; }
+
+ IPoint &operator+=(IPoint const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] += o._pt[i];
+ }
+ return *this;
+ }
+
+ IPoint &operator-=(IPoint const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] -= o._pt[i];
+ }
+ return *this;
+ }
+
+private:
+ ICoord _pt[2];
+};
+
+
+} // namespace NR
+
+#endif /* !SEEN_NR_POINT_L_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/libnr/nr-point-matrix-ops.h b/src/libnr/nr-point-matrix-ops.h
new file mode 100644
index 000000000..58e06fc6d
--- /dev/null
+++ b/src/libnr/nr-point-matrix-ops.h
@@ -0,0 +1,47 @@
+/** \file operator functions over (NR::Point, NR::Matrix). */
+#ifndef SEEN_NR_POINT_MATRIX_OPS_H
+#define SEEN_NR_POINT_MATRIX_OPS_H
+
+#include "libnr/nr-point.h"
+#include "libnr/nr-matrix.h"
+
+namespace NR {
+
+inline Point operator*(Point const &v, Matrix const &m)
+{
+#if 1 /* Which code makes it easier to see what's happening? */
+ NR::Point const xform_col0(m[0],
+ m[2]);
+ NR::Point const xform_col1(m[1],
+ m[3]);
+ NR::Point const xlate(m[4], m[5]);
+ return ( Point(dot(v, xform_col0),
+ dot(v, xform_col1))
+ + xlate );
+#else
+ return Point(v[X] * m[0] + v[Y] * m[2] + m[4],
+ v[X] * m[1] + v[Y] * m[3] + m[5]);
+#endif
+}
+
+inline Point &Point::operator*=(Matrix const &m)
+{
+ *this = *this * m;
+ return *this;
+}
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_POINT_MATRIX_OPS_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/libnr/nr-point-ops.h b/src/libnr/nr-point-ops.h
new file mode 100644
index 000000000..03d61fb15
--- /dev/null
+++ b/src/libnr/nr-point-ops.h
@@ -0,0 +1,88 @@
+/* operator functions for NR::Point. */
+#ifndef SEEN_NR_POINT_OPS_H
+#define SEEN_NR_POINT_OPS_H
+
+#include <libnr/nr-point.h>
+
+namespace NR {
+
+inline Point operator+(Point const &a, Point const &b)
+{
+ Point ret;
+ for (int i = 0; i < 2; i++) {
+ ret[i] = a[i] + b[i];
+ }
+ return ret;
+}
+
+inline Point operator-(Point const &a, Point const &b)
+{
+ Point ret;
+ for (int i = 0; i < 2; i++) {
+ ret[i] = a[i] - b[i];
+ }
+ return ret;
+}
+
+/** This is a rotation (sort of). */
+inline Point operator^(Point const &a, Point const &b)
+{
+ Point const ret(a[0] * b[0] - a[1] * b[1],
+ a[1] * b[0] + a[0] * b[1]);
+ return ret;
+}
+
+inline Point operator-(Point const &a)
+{
+ Point ret;
+ for(unsigned i = 0; i < 2; i++) {
+ ret[i] = -a[i];
+ }
+ return ret;
+}
+
+inline Point operator*(double const s, Point const &b)
+{
+ Point ret;
+ for(int i = 0; i < 2; i++) {
+ ret[i] = s * b[i];
+ }
+ return ret;
+}
+
+inline Point operator/(Point const &b, double const d)
+{
+ Point ret;
+ for(int i = 0; i < 2; i++) {
+ ret[i] = b[i] / d;
+ }
+ return ret;
+}
+
+
+inline bool operator==(Point const &a, Point const &b)
+{
+ return ( ( a[X] == b[X] ) && ( a[Y] == b[Y] ) );
+}
+
+inline bool operator!=(Point const &a, Point const &b)
+{
+ return ( ( a[X] != b[X] ) || ( a[Y] != b[Y] ) );
+}
+
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_POINT_OPS_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/libnr/nr-point.h b/src/libnr/nr-point.h
new file mode 100644
index 000000000..f67a66800
--- /dev/null
+++ b/src/libnr/nr-point.h
@@ -0,0 +1,159 @@
+#ifndef SEEN_NR_POINT_H
+#define SEEN_NR_POINT_H
+
+/** \file
+ * Cartesian point class.
+ */
+
+//#include <math.h>
+//#include <stdexcept>
+#include <iostream>
+//#include <iomanip>
+
+#include <libnr/nr-coord.h>
+#include <libnr/nr-dim2.h>
+
+//#include "round.h"
+#include "decimal-round.h"
+
+/// A NRPoint consists of x and y coodinates.
+/// \todo
+/// This class appears to be obsoleted out in favour of NR::Point.
+struct NRPoint {
+ NR::Coord x, y;
+};
+
+namespace NR {
+
+class Matrix;
+
+/// Cartesian point.
+class Point {
+public:
+ inline Point()
+ { _pt[X] = _pt[Y] = 0; }
+
+ inline Point(Coord x, Coord y) {
+ _pt[X] = x;
+ _pt[Y] = y;
+ }
+
+ inline Point(NRPoint const &p) {
+ _pt[X] = p.x;
+ _pt[Y] = p.y;
+ }
+
+ inline Point(Point const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ }
+
+ inline Point &operator=(Point const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ return *this;
+ }
+
+ inline Coord operator[](unsigned i) const {
+ return _pt[i];
+ }
+
+ inline Coord &operator[](unsigned i) {
+ return _pt[i];
+ }
+
+ Coord operator[](Dim2 d) const throw() { return _pt[d]; }
+ Coord &operator[](Dim2 d) throw() { return _pt[d]; }
+
+ /** Return a point like this point but rotated -90 degrees.
+ (If the y axis grows downwards and the x axis grows to the
+ right, then this is 90 degrees counter-clockwise.)
+ **/
+ Point ccw() const {
+ return Point(_pt[Y], -_pt[X]);
+ }
+
+ /** Return a point like this point but rotated +90 degrees.
+ (If the y axis grows downwards and the x axis grows to the
+ right, then this is 90 degrees clockwise.)
+ **/
+ Point cw() const {
+ return Point(-_pt[Y], _pt[X]);
+ }
+
+ /**
+ \brief A function to lower the precision of the point
+ \param places The number of decimal places that should be in
+ the final number.
+ */
+ inline void round (int places = 0) {
+ _pt[X] = (Coord)(Inkscape::decimal_round((double)_pt[X], places));
+ _pt[Y] = (Coord)(Inkscape::decimal_round((double)_pt[Y], places));
+ return;
+ }
+
+ void normalize();
+
+ inline Point &operator+=(Point const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] += o._pt[i];
+ }
+ return *this;
+ }
+
+ inline Point &operator-=(Point const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] -= o._pt[i];
+ }
+ return *this;
+ }
+
+ inline Point &operator/=(double const s) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] /= s;
+ }
+ return *this;
+ }
+
+ inline Point &operator*=(double const s) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] *= s;
+ }
+ return *this;
+ }
+
+ Point &operator*=(Matrix const &m);
+
+ inline int operator == (const Point &in_pnt) {
+ return ((_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y]));
+ }
+
+ friend inline std::ostream &operator<< (std::ostream &out_file, const NR::Point &in_pnt);
+
+private:
+ Coord _pt[2];
+};
+
+/** A function to print out the Point. It just prints out the coords
+ on the given output stream */
+inline std::ostream &operator<< (std::ostream &out_file, const NR::Point &in_pnt) {
+ out_file << "X: " << in_pnt[X] << " Y: " << in_pnt[Y];
+ return out_file;
+}
+
+} /* namespace NR */
+
+#endif /* !SEEN_NR_POINT_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/libnr/nr-rect-l.cpp b/src/libnr/nr-rect-l.cpp
new file mode 100644
index 000000000..40db67a1e
--- /dev/null
+++ b/src/libnr/nr-rect-l.cpp
@@ -0,0 +1,22 @@
+#include <libnr/nr-rect-l.h>
+
+namespace NR {
+
+IRect::IRect(Rect const &r) :
+ _min(int(floor(r.min()[X])), int(floor(r.min()[Y]))),
+ _max(int(ceil(r.min()[X])), int(ceil(r.min()[Y])))
+{
+}
+
+}
+
+/*
+ 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/libnr/nr-rect-l.h b/src/libnr/nr-rect-l.h
new file mode 100644
index 000000000..756a537b0
--- /dev/null
+++ b/src/libnr/nr-rect-l.h
@@ -0,0 +1,131 @@
+#ifndef SEEN_NR_RECT_L_H
+#define SEEN_NR_RECT_L_H
+
+#include <libnr/nr-i-coord.h>
+
+struct NRRectL {
+ NR::ICoord x0, y0, x1, y1;
+};
+
+#include <libnr/nr-rect.h>
+#include <libnr/nr-point-l.h>
+
+namespace NR {
+
+
+class IRect {
+public:
+ IRect(const NRRectL& r) : _min(r.x0, r.y0), _max(r.x1, r.y1) {}
+ IRect(const IRect& r) : _min(r._min), _max(r._max) {}
+ IRect(const IPoint &p0, const IPoint &p1);
+
+ /** as not all Rects are representable by IRects this gives the smallest IRect that contains
+ * r. */
+ IRect(const Rect& r);
+
+ operator Rect() {
+ return Rect(Point(_min), Point(_max));
+ }
+
+ const IPoint &min() const { return _min; }
+ const IPoint &max() const { return _max; }
+
+ /** returns a vector from min to max. */
+ IPoint dimensions() const;
+
+ /** does this rectangle have zero area? */
+ bool isEmpty() const {
+ return isEmpty<X>() && isEmpty<Y>();
+ }
+
+ bool intersects(const IRect &r) const {
+ return intersects<X>(r) && intersects<Y>(r);
+ }
+ bool contains(const IRect &r) const {
+ return contains<X>(r) && contains<Y>(r);
+ }
+ bool contains(const IPoint &p) const {
+ return contains<X>(p) && contains<Y>(p);
+ }
+
+ ICoord maxExtent() const {
+ return MAX(extent<X>(), extent<Y>());
+ }
+
+ ICoord extent(Dim2 axis) const {
+ switch (axis) {
+ case X: return extent<X>();
+ case Y: return extent<Y>();
+ };
+ }
+
+ ICoord extent(unsigned i) const throw(std::out_of_range) {
+ switch (i) {
+ case 0: return extent<X>();
+ case 1: return extent<Y>();
+ default: throw std::out_of_range("Dimension out of range");
+ };
+ }
+
+ /** Translates the rectangle by p. */
+ void offset(IPoint p);
+
+ /** Makes this rectangle large enough to include the point p. */
+ void expandTo(IPoint p);
+
+ /** Makes this rectangle large enough to include the rectangle r. */
+ void expandTo(const IRect &r);
+
+ /** Returns the set of points shared by both rectangles. */
+ static Maybe<IRect> intersection(const IRect &a, const IRect &b);
+
+ /** Returns the smallest rectangle that encloses both rectangles. */
+ static IRect union_bounds(const IRect &a, const IRect &b);
+
+private:
+ IRect() {}
+
+ template <NR::Dim2 axis>
+ ICoord extent() const {
+ return _max[axis] - _min[axis];
+ }
+
+ template <Dim2 axis>
+ bool isEmpty() const {
+ return !( _min[axis] < _max[axis] );
+ }
+
+ template <Dim2 axis>
+ bool intersects(const IRect &r) const {
+ return _max[axis] >= r._min[axis] && _min[axis] <= r._max[axis];
+ }
+
+ template <Dim2 axis>
+ bool contains(const IRect &r) const {
+ return contains(r._min) && contains(r._max);
+ }
+
+ template <Dim2 axis>
+ bool contains(const IPoint &p) const {
+ return p[axis] >= _min[axis] && p[axis] <= _max[axis];
+ }
+
+ IPoint _min, _max;
+};
+
+
+
+} // namespace NR
+
+#endif /* !SEEN_NR_RECT_L_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/libnr/nr-rect-ops.h b/src/libnr/nr-rect-ops.h
new file mode 100644
index 000000000..870091a94
--- /dev/null
+++ b/src/libnr/nr-rect-ops.h
@@ -0,0 +1,51 @@
+#ifndef SEEN_NR_RECT_OPS_H
+#define SEEN_NR_RECT_OPS_H
+
+/*
+ * Rect operators
+ *
+ * Copyright 2004 MenTaLguY <mental@rydia.net>,
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * This code is licensed under the GNU GPL; see COPYING for more information.
+ */
+
+#include <libnr/nr-rect.h>
+
+namespace NR {
+
+inline Rect expand(Rect const &r, double by) {
+ NR::Point const p(by, by);
+ return Rect(r.min() + p, r.max() - p);
+}
+
+inline Rect expand(Rect const &r, NR::Point by) {
+ return Rect(r.min() + by, r.max() - by);
+}
+
+#if 0
+inline ConvexHull operator*(Rect const &r, Matrix const &m) {
+ /* FIXME: no mention of m. Should probably be made non-inline. */
+ ConvexHull points(r.corner(0));
+ for ( unsigned i = 1 ; i < 4 ; i++ ) {
+ points.add(r.corner(i));
+ }
+ return points;
+}
+#endif
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_RECT_OPS_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/libnr/nr-rect.cpp b/src/libnr/nr-rect.cpp
new file mode 100644
index 000000000..0a8287bde
--- /dev/null
+++ b/src/libnr/nr-rect.cpp
@@ -0,0 +1,233 @@
+#define __NR_RECT_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include "nr-rect-l.h"
+
+/**
+ * \param r0 Rectangle.
+ * \param r1 Another rectangle.
+ * \param d Filled in with the intersection of r0 and r1.
+ * \return d.
+ */
+
+NRRectL *nr_rect_l_intersect(NRRectL *d, const NRRectL *r0, const NRRectL *r1)
+{
+ NR::ICoord t;
+ t = std::max(r0->x0, r1->x0);
+ d->x1 = std::min(r0->x1, r1->x1);
+ d->x0 = t;
+ t = std::max(r0->y0, r1->y0);
+ d->y1 = std::min(r0->y1, r1->y1);
+ d->y0 = t;
+
+ return d;
+}
+
+NRRect *
+nr_rect_d_intersect (NRRect *d, const NRRect *r0, const NRRect *r1)
+{
+ NR::Coord t;
+ t = MAX (r0->x0, r1->x0);
+ d->x1 = MIN (r0->x1, r1->x1);
+ d->x0 = t;
+ t = MAX (r0->y0, r1->y0);
+ d->y1 = MIN (r0->y1, r1->y1);
+ d->y0 = t;
+
+ return d;
+}
+
+NRRect *
+nr_rect_d_union (NRRect *d, const NRRect *r0, const NRRect *r1)
+{
+ if (NR_RECT_DFLS_TEST_EMPTY (r0)) {
+ if (NR_RECT_DFLS_TEST_EMPTY (r1)) {
+ nr_rect_d_set_empty (d);
+ } else {
+ *d = *r1;
+ }
+ } else {
+ if (NR_RECT_DFLS_TEST_EMPTY (r1)) {
+ *d = *r0;
+ } else {
+ NR::Coord t;
+ t = MIN (r0->x0, r1->x0);
+ d->x1 = MAX (r0->x1, r1->x1);
+ d->x0 = t;
+ t = MIN (r0->y0, r1->y0);
+ d->y1 = MAX (r0->y1, r1->y1);
+ d->y0 = t;
+ }
+ }
+ return d;
+}
+
+NRRectL *
+nr_rect_l_union (NRRectL *d, const NRRectL *r0, const NRRectL *r1)
+{
+ if (NR_RECT_DFLS_TEST_EMPTY (r0)) {
+ if (NR_RECT_DFLS_TEST_EMPTY (r1)) {
+ nr_rect_l_set_empty (d);
+ } else {
+ *d = *r1;
+ }
+ } else {
+ if (NR_RECT_DFLS_TEST_EMPTY (r1)) {
+ *d = *r0;
+ } else {
+ NR::ICoord t;
+ t = MIN (r0->x0, r1->x0);
+ d->x1 = MAX (r0->x1, r1->x1);
+ d->x0 = t;
+ t = MIN (r0->y0, r1->y0);
+ d->y1 = MAX (r0->y1, r1->y1);
+ d->y0 = t;
+ }
+ }
+ return d;
+}
+
+NRRect *
+nr_rect_union_pt(NRRect *dst, NR::Point const &p)
+{
+ using NR::X;
+ using NR::Y;
+
+ return nr_rect_d_union_xy(dst, p[X], p[Y]);
+}
+
+NRRect *
+nr_rect_d_union_xy (NRRect *d, NR::Coord x, NR::Coord y)
+{
+ if ((d->x0 <= d->x1) && (d->y0 <= d->y1)) {
+ d->x0 = MIN (d->x0, x);
+ d->y0 = MIN (d->y0, y);
+ d->x1 = MAX (d->x1, x);
+ d->y1 = MAX (d->y1, y);
+ } else {
+ d->x0 = d->x1 = x;
+ d->y0 = d->y1 = y;
+ }
+ return d;
+}
+
+NRRect *
+nr_rect_d_matrix_transform(NRRect *d, NRRect const *const s, NR::Matrix const &m)
+{
+ using NR::X;
+ using NR::Y;
+
+ if (nr_rect_d_test_empty(s)) {
+ nr_rect_d_set_empty(d);
+ } else {
+ NR::Point const c00(NR::Point(s->x0, s->y0) * m);
+ NR::Point const c01(NR::Point(s->x0, s->y1) * m);
+ NR::Point const c10(NR::Point(s->x1, s->y0) * m);
+ NR::Point const c11(NR::Point(s->x1, s->y1) * m);
+ d->x0 = std::min(std::min(c00[X], c01[X]),
+ std::min(c10[X], c11[X]));
+ d->y0 = std::min(std::min(c00[Y], c01[Y]),
+ std::min(c10[Y], c11[Y]));
+ d->x1 = std::max(std::max(c00[X], c01[X]),
+ std::max(c10[X], c11[X]));
+ d->y1 = std::max(std::max(c00[Y], c01[Y]),
+ std::max(c10[Y], c11[Y]));
+ }
+ return d;
+}
+
+NRRect *
+nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NRMatrix const *m)
+{
+ return nr_rect_d_matrix_transform(d, s, *m);
+}
+
+namespace NR {
+
+Rect::Rect(const Point &p0, const Point &p1)
+: _min(MIN(p0[X], p1[X]), MIN(p0[Y], p1[Y])),
+ _max(MAX(p0[X], p1[X]), MAX(p0[Y], p1[Y])) {}
+
+/** returns the four corners of the rectangle in the correct winding order */
+Point Rect::corner(unsigned i) const {
+ switch (i % 4) {
+ case 0:
+ return _min;
+ case 1:
+ return Point(_max[X], _min[Y]);
+ case 2:
+ return _max;
+ default: /* i.e. 3 */
+ return Point(_min[X], _max[Y]);
+ }
+}
+
+/** returns the midpoint of this rectangle */
+Point Rect::midpoint() const {
+ return ( _min + _max ) / 2;
+}
+
+/** returns a vector from topleft to bottom right. */
+Point Rect::dimensions() const {
+ return _max - _min;
+}
+
+/** Translates the rectangle by p. */
+void Rect::offset(Point p) {
+ _min += p;
+ _max += p;
+}
+
+/** Makes this rectangle large enough to include the point p. */
+void Rect::expandTo(Point p) {
+ for ( int i=0 ; i < 2 ; i++ ) {
+ _min[i] = MIN(_min[i], p[i]);
+ _max[i] = MAX(_max[i], p[i]);
+ }
+}
+
+/** Returns the set of points shared by both rectangles. */
+Maybe<Rect> Rect::intersection(const Rect &a, const Rect &b) {
+ Rect r;
+ for ( int i=0 ; i < 2 ; i++ ) {
+ r._min[i] = MAX(a._min[i], b._min[i]);
+ r._max[i] = MIN(a._max[i], b._max[i]);
+
+ if ( r._min[i] > r._max[i] ) {
+ return Nothing();
+ }
+ }
+ return r;
+}
+
+/** returns the smallest rectangle containing both rectangles */
+Rect Rect::union_bounds(const Rect &a, const Rect &b) {
+ Rect r;
+ for ( int i=0; i < 2 ; i++ ) {
+ r._min[i] = MIN(a._min[i], b._min[i]);
+ r._max[i] = MAX(a._max[i], b._max[i]);
+ }
+ return r;
+}
+
+} // namespace NR
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnr/nr-rect.h b/src/libnr/nr-rect.h
new file mode 100644
index 000000000..219fbd1ac
--- /dev/null
+++ b/src/libnr/nr-rect.h
@@ -0,0 +1,255 @@
+#ifndef LIBNR_NR_RECT_H_SEEN
+#define LIBNR_NR_RECT_H_SEEN
+
+/** \file
+ * Definitions of NRRect and NR::Rect types, and some associated functions \& macros.
+ */
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * This code is in public domain
+ */
+
+
+#include <stdexcept>
+
+#include "libnr/nr-values.h"
+#include <libnr/nr-coord.h>
+#include <libnr/nr-i-coord.h>
+#include <libnr/nr-dim2.h>
+#include <libnr/nr-point.h>
+#include <libnr/nr-maybe.h>
+#include <libnr/nr-point-matrix-ops.h>
+
+struct NRMatrix;
+namespace NR {
+ struct Matrix;
+}
+
+/* NULL rect is infinite */
+
+struct NRRect {
+ NR::Coord x0, y0, x1, y1;
+};
+
+inline bool empty(NRRect const &r)
+{
+ return ( ( r.x0 > r.x1 ) ||
+ ( r.y0 > r.y1 ) );
+}
+
+#define nr_rect_d_set_empty(r) (*(r) = NR_RECT_EMPTY)
+#define nr_rect_l_set_empty(r) (*(r) = NR_RECT_L_EMPTY)
+
+#define nr_rect_d_test_empty(r) ((r) && NR_RECT_DFLS_TEST_EMPTY(r))
+#define nr_rect_l_test_empty(r) ((r) && NR_RECT_DFLS_TEST_EMPTY(r))
+
+#define nr_rect_d_test_intersect(r0,r1) \
+ (!nr_rect_d_test_empty(r0) && !nr_rect_d_test_empty(r1) && \
+ !((r0) && (r1) && !NR_RECT_DFLS_TEST_INTERSECT(r0, r1)))
+#define nr_rect_l_test_intersect(r0,r1) \
+ (!nr_rect_l_test_empty(r0) && !nr_rect_l_test_empty(r1) && \
+ !((r0) && (r1) && !NR_RECT_DFLS_TEST_INTERSECT(r0, r1)))
+
+#define nr_rect_d_point_d_test_inside(r,p) ((p) && (!(r) || (!NR_RECT_DF_TEST_EMPTY(r) && NR_RECT_DF_POINT_DF_TEST_INSIDE(r,p))))
+
+/* NULL values are OK for r0 and r1, but not for d */
+NRRect *nr_rect_d_intersect(NRRect *d, NRRect const *r0, NRRect const *r1);
+NRRectL *nr_rect_l_intersect(NRRectL *d, NRRectL const *r0, NRRectL const *r1);
+
+NRRect *nr_rect_d_union(NRRect *d, NRRect const *r0, NRRect const *r1);
+NRRectL *nr_rect_l_union(NRRectL *d, NRRectL const *r0, NRRectL const *r1);
+
+NRRect *nr_rect_union_pt(NRRect *dst, NR::Point const &p);
+NRRect *nr_rect_d_union_xy(NRRect *d, NR::Coord x, NR::Coord y);
+NRRectL *nr_rect_l_union_xy(NRRectL *d, NR::ICoord x, NR::ICoord y);
+
+NRRect *nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NR::Matrix const &m);
+NRRect *nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NRMatrix const *m);
+
+namespace NR {
+
+/** A rectangle is always aligned to the X and Y axis. This means it
+ * can be defined using only 4 coordinates, and determining
+ * intersection is very efficient. The points inside a rectangle are
+ * min[dim] <= _pt[dim] <= max[dim]. Emptiness, however, is defined
+ * as having zero area, meaning an empty rectangle may still contain
+ * points. Infinities are also permitted. */
+class Rect {
+public:
+ Rect(NRRect const &r) : _min(r.x0, r.y0), _max(r.x1, r.y1) {}
+ Rect(Rect const &r) : _min(r._min), _max(r._max) {}
+ Rect(Point const &p0, Point const &p1);
+
+ Point const &min() const { return _min; }
+ Point const &max() const { return _max; }
+
+ /** returns the four corners of the rectangle in order
+ * (clockwise if +Y is up, anticlockwise if +Y is down) */
+ Point corner(unsigned i) const;
+
+ /** returns a vector from min to max. */
+ Point dimensions() const;
+
+ /** returns the midpoint of this rect. */
+ Point midpoint() const;
+
+ /** does this rectangle have zero area? */
+ bool isEmpty() const {
+ return isEmpty<X>() || isEmpty<Y>();
+ }
+
+ bool intersects(Rect const &r) const {
+ return intersects<X>(r) && intersects<Y>(r);
+ }
+ bool contains(Rect const &r) const {
+ return contains<X>(r) && contains<Y>(r);
+ }
+ bool contains(Point const &p) const {
+ return contains<X>(p) && contains<Y>(p);
+ }
+
+ double area() const {
+ return extent<X>() * extent<Y>();
+ }
+
+ double maxExtent() const {
+ return MAX(extent<X>(), extent<Y>());
+ }
+
+ double extent(Dim2 const axis) const {
+ switch (axis) {
+ case X: return extent<X>();
+ case Y: return extent<Y>();
+ default: g_error("invalid axis value %d", (int) axis); return 0;
+ };
+ }
+
+ double extent(unsigned i) const throw(std::out_of_range) {
+ switch (i) {
+ case 0: return extent<X>();
+ case 1: return extent<Y>();
+ default: throw std::out_of_range("Dimension out of range");
+ };
+ }
+
+ /**
+ \brief Remove some precision from the Rect
+ \param places The number of decimal places left in the end
+
+ This function just calls round on the \c _min and \c _max points.
+ */
+ inline void round(int places = 0) {
+ _min.round(places);
+ _max.round(places);
+ return;
+ }
+
+ /** Translates the rectangle by p. */
+ void offset(Point p);
+
+ /** Makes this rectangle large enough to include the point p. */
+ void expandTo(Point p);
+
+ /** Makes this rectangle large enough to include the rectangle r. */
+ void expandTo(Rect const &r);
+
+ inline void move_left (gdouble by) {
+ _min[NR::X] += by;
+ }
+ inline void move_right (gdouble by) {
+ _max[NR::X] += by;
+ }
+ inline void move_top (gdouble by) {
+ _min[NR::Y] += by;
+ }
+ inline void move_bottom (gdouble by) {
+ _max[NR::Y] += by;
+ }
+
+ /** Returns the set of points shared by both rectangles. */
+ static Maybe<Rect> intersection(Rect const &a, Rect const &b);
+
+ /** Returns the smallest rectangle that encloses both rectangles. */
+ static Rect union_bounds(Rect const &a, Rect const &b);
+
+ /** Scales the rect by s, with origin at 0, 0 */
+ inline Rect operator*(double const s) const {
+ return Rect(s * min(), s * max());
+ }
+
+ /** Transforms the rect by m. Note that it gives correct results only for scales and translates */
+ inline Rect operator*(Matrix const m) const {
+ return Rect(_min * m, _max * m);
+ }
+
+ inline bool operator==(Rect const &in_rect) {
+ return ((this->min() == in_rect.min()) && (this->max() == in_rect.max()));
+ }
+
+ friend inline std::ostream &operator<<(std::ostream &out_file, NR::Rect const &in_rect);
+
+private:
+ Rect() {}
+
+ template <NR::Dim2 axis>
+ double extent() const {
+ return _max[axis] - _min[axis];
+ }
+
+ template <Dim2 axis>
+ bool isEmpty() const {
+ return !( _min[axis] < _max[axis] );
+ }
+
+ template <Dim2 axis>
+ bool intersects(Rect const &r) const {
+ return _max[axis] >= r._min[axis] && _min[axis] <= r._max[axis];
+ }
+
+ template <Dim2 axis>
+ bool contains(Rect const &r) const {
+ return contains(r._min) && contains(r._max);
+ }
+
+ template <Dim2 axis>
+ bool contains(Point const &p) const {
+ return p[axis] >= _min[axis] && p[axis] <= _max[axis];
+ }
+
+ Point _min, _max;
+
+ /* evil, but temporary */
+ friend class Maybe<Rect>;
+};
+
+/** A function to print out the rectange if sent to an output
+ stream. */
+inline std::ostream
+&operator<<(std::ostream &out_file, NR::Rect const &in_rect)
+{
+ out_file << "Rectangle:\n";
+ out_file << "\tMin Point -> " << in_rect.min() << "\n";
+ out_file << "\tMax Point -> " << in_rect.max() << "\n";
+
+ return out_file;
+}
+
+} /* namespace NR */
+
+
+#endif /* !LIBNR_NR_RECT_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-render.h b/src/libnr/nr-render.h
new file mode 100644
index 000000000..84215b7a3
--- /dev/null
+++ b/src/libnr/nr-render.h
@@ -0,0 +1,25 @@
+#ifndef __NR_RENDER_H__
+#define __NR_RENDER_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+
+struct NRRenderer;
+
+typedef void (* NRRenderFunc) (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
+
+struct NRRenderer {
+ NRRenderFunc render;
+};
+
+#define nr_render(r,pb,m) ((NRRenderer *) (r))->render ((NRRenderer *) (r), (pb), (m))
+
+#endif
diff --git a/src/libnr/nr-rotate-fns-test.cpp b/src/libnr/nr-rotate-fns-test.cpp
new file mode 100644
index 000000000..45b2450e0
--- /dev/null
+++ b/src/libnr/nr-rotate-fns-test.cpp
@@ -0,0 +1,43 @@
+#include <cmath>
+#include <glib/gmacros.h>
+
+#include <libnr/nr-rotate-fns.h>
+#include <utest/utest.h>
+
+int main(int argc, char *argv[])
+{
+ utest_start("rotate-fns");
+
+ UTEST_TEST("rotate_degrees") {
+ double const d[] = {
+ 0, 90, 180, 270, 360, 45, 45.01, 44.99, 134, 135, 136, 314, 315, 317, 359, 361
+ };
+ for (unsigned i = 0; i < G_N_ELEMENTS(d); ++i) {
+ double const degrees = d[i];
+ NR::rotate const rot(rotate_degrees(degrees));
+ NR::rotate const rot_approx( M_PI * ( degrees / 180. ) );
+ UTEST_ASSERT(rotate_equalp(rot, rot_approx, 1e-12));
+
+ NR::rotate const rot_inv(rotate_degrees(-degrees));
+ NR::rotate const rot_compl(rotate_degrees(360 - degrees));
+ UTEST_ASSERT(rotate_equalp(rot_inv, rot_compl, 1e-12));
+
+ UTEST_ASSERT(!rotate_equalp(rot, rotate_degrees(degrees + 1), 1e-5));
+ }
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/nr-rotate-fns-test.h b/src/libnr/nr-rotate-fns-test.h
new file mode 100644
index 000000000..e3bfe3043
--- /dev/null
+++ b/src/libnr/nr-rotate-fns-test.h
@@ -0,0 +1,54 @@
+#include <cxxtest/TestSuite.h>
+
+#include <cmath>
+#include <glib/gmacros.h>
+
+#include <libnr/nr-rotate-fns.h>
+
+class NrRotateFnsTest : public CxxTest::TestSuite
+{
+public:
+
+ NrRotateFnsTest()
+ {
+ }
+ virtual ~NrRotateFnsTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrRotateFnsTest *createSuite() { return new NrRotateFnsTest(); }
+ static void destroySuite( NrRotateFnsTest *suite ) { delete suite; }
+
+
+
+ void testRotateDegrees(void)
+ {
+ double const d[] = {
+ 0, 90, 180, 270, 360, 45, 45.01, 44.99, 134, 135, 136, 314, 315, 317, 359, 361
+ };
+ for ( unsigned i = 0; i < G_N_ELEMENTS(d); ++i ) {
+ double const degrees = d[i];
+ NR::rotate const rot(rotate_degrees(degrees));
+ NR::rotate const rot_approx( M_PI * ( degrees / 180. ) );
+ TS_ASSERT( rotate_equalp(rot, rot_approx, 1e-12) );
+
+ NR::rotate const rot_inv(rotate_degrees(-degrees));
+ NR::rotate const rot_compl(rotate_degrees(360 - degrees));
+ TS_ASSERT( rotate_equalp(rot_inv, rot_compl, 1e-12) );
+
+ TS_ASSERT( !rotate_equalp(rot, rotate_degrees(degrees + 1), 1e-5) );
+ }
+ }
+
+};
+
+/*
+ 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/libnr/nr-rotate-fns.cpp b/src/libnr/nr-rotate-fns.cpp
new file mode 100644
index 000000000..f2669c20c
--- /dev/null
+++ b/src/libnr/nr-rotate-fns.cpp
@@ -0,0 +1,66 @@
+/** \file
+ * Functions to/from NR::rotate.
+ */
+#include <glib.h>
+#include "libnr/nr-rotate-ops.h"
+#include "libnr/nr-rotate-fns.h"
+
+/**
+ * Returns a rotation matrix corresponding by the specified angle about the origin.
+ *
+ * Angle direction in Inkscape code: If you use the traditional mathematics convention that y
+ * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If
+ * you take the common non-mathematical convention that y increases downwards, then positive angles
+ * are clockwise, as is common outside of mathematics.
+ */
+NR::rotate
+rotate_degrees(double degrees)
+{
+ if (degrees < 0) {
+ return rotate_degrees(-degrees).inverse();
+ }
+
+ double const degrees0 = degrees;
+ if (degrees >= 360) {
+ degrees = fmod(degrees, 360);
+ }
+
+ NR::rotate ret(1., 0.);
+
+ if (degrees >= 180) {
+ NR::rotate const rot180(-1., 0.);
+ degrees -= 180;
+ ret = rot180;
+ }
+
+ if (degrees >= 90) {
+ NR::rotate const rot90(0., 1.);
+ degrees -= 90;
+ ret *= rot90;
+ }
+
+ if (degrees == 45) {
+ NR::rotate const rot45(M_SQRT1_2, M_SQRT1_2);
+ ret *= rot45;
+ } else {
+ double const radians = M_PI * ( degrees / 180 );
+ ret *= NR::rotate(cos(radians), sin(radians));
+ }
+
+ NR::rotate const raw_ret( M_PI * ( degrees0 / 180 ) );
+ g_return_val_if_fail(rotate_equalp(ret, raw_ret, 1e-8),
+ raw_ret);
+ return ret;
+}
+
+
+/*
+ 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/libnr/nr-rotate-fns.h b/src/libnr/nr-rotate-fns.h
new file mode 100644
index 000000000..bd075114c
--- /dev/null
+++ b/src/libnr/nr-rotate-fns.h
@@ -0,0 +1,29 @@
+#ifndef SEEN_NR_ROTATE_FNS_H
+#define SEEN_NR_ROTATE_FNS_H
+
+/** \file
+ * Declarations for rotation functions.
+ */
+
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-point-fns.h>
+
+inline bool rotate_equalp(NR::rotate const &a, NR::rotate const &b, double const eps)
+{
+ return point_equalp(a.vec, b.vec, eps);
+}
+
+NR::rotate rotate_degrees(double degrees);
+
+#endif /* !SEEN_NR_ROTATE_FNS_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/libnr/nr-rotate-matrix-ops.cpp b/src/libnr/nr-rotate-matrix-ops.cpp
new file mode 100644
index 000000000..dd3851643
--- /dev/null
+++ b/src/libnr/nr-rotate-matrix-ops.cpp
@@ -0,0 +1,19 @@
+#include <libnr/nr-matrix-ops.h>
+
+NR::Matrix
+operator*(NR::rotate const &a, NR::Matrix const &b)
+{
+ return NR::Matrix(a) * b;
+}
+
+
+/*
+ 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/libnr/nr-rotate-matrix-ops.h b/src/libnr/nr-rotate-matrix-ops.h
new file mode 100644
index 000000000..d2f0eadba
--- /dev/null
+++ b/src/libnr/nr-rotate-matrix-ops.h
@@ -0,0 +1,21 @@
+#ifndef SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_H
+#define SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_H
+
+#include <libnr/nr-forward.h>
+
+
+NR::Matrix operator*(NR::rotate const &a, NR::Matrix const &b);
+
+
+#endif /* !SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_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/libnr/nr-rotate-ops.h b/src/libnr/nr-rotate-ops.h
new file mode 100644
index 000000000..4b60b9d0c
--- /dev/null
+++ b/src/libnr/nr-rotate-ops.h
@@ -0,0 +1,43 @@
+#ifndef SEEN_NR_ROTATE_OPS_H
+#define SEEN_NR_ROTATE_OPS_H
+#include <libnr/nr-rotate.h>
+
+namespace NR {
+
+inline Point operator*(Point const &v, rotate const &r)
+{
+ return Point(r.vec[X] * v[X] - r.vec[Y] * v[Y],
+ r.vec[Y] * v[X] + r.vec[X] * v[Y]);
+}
+
+inline rotate operator*(rotate const &a, rotate const &b)
+{
+ return rotate( a.vec * b );
+}
+
+inline rotate &rotate::operator*=(rotate const &b)
+{
+ *this = *this * b;
+ return *this;
+}
+
+inline rotate operator/(rotate const &numer, rotate const &denom)
+{
+ return numer * denom.inverse();
+}
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_ROTATE_OPS_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/libnr/nr-rotate-test.cpp b/src/libnr/nr-rotate-test.cpp
new file mode 100644
index 000000000..63b9d1737
--- /dev/null
+++ b/src/libnr/nr-rotate-test.cpp
@@ -0,0 +1,89 @@
+#include <cmath>
+#include <utest/utest.h>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-fns.h> /* identity, matrix_equalp */
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-point-fns.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-rotate-fns.h>
+#include <libnr/nr-rotate-ops.h>
+using NR::X;
+using NR::Y;
+
+int main(int argc, char *argv[])
+{
+ utest_start("rotate");
+
+ NR::Matrix const m_id(NR::identity());
+ NR::rotate const r_id(0.0);
+ NR::rotate const rot234(.234);
+ UTEST_TEST("constructors, comparisons") {
+ UTEST_ASSERT( r_id == r_id );
+ UTEST_ASSERT( rot234 == rot234 );
+ UTEST_ASSERT( rot234 != r_id );
+ UTEST_ASSERT( r_id == NR::rotate(NR::Point(1.0, 0.0)) );
+ UTEST_ASSERT( NR::Matrix(r_id) == m_id );
+ UTEST_ASSERT( NR::Matrix(r_id).test_identity() );
+
+ UTEST_ASSERT(rotate_equalp(rot234, NR::rotate(NR::Point(cos(.234), sin(.234))), 1e-12));
+ }
+
+ UTEST_TEST("operator=") {
+ NR::rotate rot234_eq(r_id);
+ rot234_eq = rot234;
+ UTEST_ASSERT( rot234 == rot234_eq );
+ UTEST_ASSERT( rot234_eq != r_id );
+ }
+
+ UTEST_TEST("inverse") {
+ UTEST_ASSERT( r_id.inverse() == r_id );
+ UTEST_ASSERT( rot234.inverse() == NR::rotate(-.234) );
+ }
+
+ NR::Point const b(-2.0, 3.0);
+ NR::rotate const rot180(NR::Point(-1.0, 0.0));
+ UTEST_TEST("operator*(Point, rotate)") {
+ UTEST_ASSERT( b * r_id == b );
+ UTEST_ASSERT( b * rot180 == -b );
+ UTEST_ASSERT( b * rot234 == b * NR::Matrix(rot234) );
+ UTEST_ASSERT(point_equalp(b * NR::rotate(M_PI / 2),
+ NR::rot90(b),
+ 1e-14));
+ UTEST_ASSERT( b * rotate_degrees(90.) == NR::rot90(b) );
+ }
+
+ UTEST_TEST("operator*(rotate, rotate)") {
+ UTEST_ASSERT( r_id * r_id == r_id );
+ UTEST_ASSERT( rot180 * rot180 == r_id );
+ UTEST_ASSERT( rot234 * r_id == rot234 );
+ UTEST_ASSERT( r_id * rot234 == rot234 );
+ UTEST_ASSERT(rotate_equalp(rot234 * rot234.inverse(), r_id, 1e-14));
+ UTEST_ASSERT(rotate_equalp(rot234.inverse() * rot234, r_id, 1e-14));
+ UTEST_ASSERT(rotate_equalp(( NR::rotate(0.25) * NR::rotate(.5) ),
+ NR::rotate(.75),
+ 1e-10));
+ }
+
+ UTEST_TEST("operator/(rotate, rotate)") {
+ UTEST_ASSERT( rot234 / r_id == rot234 );
+ UTEST_ASSERT( rot234 / rot180 == rot234 * rot180 );
+ UTEST_ASSERT(rotate_equalp(rot234 / rot234, r_id, 1e-14));
+ UTEST_ASSERT(rotate_equalp(r_id / rot234, rot234.inverse(), 1e-14));
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/nr-rotate-test.h b/src/libnr/nr-rotate-test.h
new file mode 100644
index 000000000..252b25022
--- /dev/null
+++ b/src/libnr/nr-rotate-test.h
@@ -0,0 +1,112 @@
+#include <cxxtest/TestSuite.h>
+
+#include <cmath>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-matrix-fns.h> /* identity, matrix_equalp */
+#include <libnr/nr-point-fns.h>
+#include <libnr/nr-rotate.h>
+#include <libnr/nr-rotate-fns.h>
+#include <libnr/nr-rotate-ops.h>
+using NR::X;
+using NR::Y;
+
+
+class NrRotateTest : public CxxTest::TestSuite
+{
+public:
+
+ NrRotateTest() :
+ m_id( NR::identity() ),
+ r_id( 0.0 ),
+ rot234( .234 ),
+ b( -2.0, 3.0 ),
+ rot180( NR::Point(-1.0, 0.0) )
+ {
+ }
+ virtual ~NrRotateTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrRotateTest *createSuite() { return new NrRotateTest(); }
+ static void destroySuite( NrRotateTest *suite ) { delete suite; }
+
+ NR::Matrix const m_id;
+ NR::rotate const r_id;
+ NR::rotate const rot234;
+ NR::Point const b;
+ NR::rotate const rot180;
+
+
+
+
+ void testCtorsCompares(void)
+ {
+ TS_ASSERT_EQUALS( r_id, r_id );
+ TS_ASSERT_EQUALS( rot234, rot234 );
+ TS_ASSERT_DIFFERS( rot234, r_id );
+ TS_ASSERT_EQUALS( r_id, NR::rotate(NR::Point(1.0, 0.0)) );
+ TS_ASSERT_EQUALS( NR::Matrix(r_id), m_id );
+ TS_ASSERT( NR::Matrix(r_id).test_identity() );
+
+ TS_ASSERT(rotate_equalp(rot234, NR::rotate(NR::Point(cos(.234), sin(.234))), 1e-12));
+ }
+
+ void testAssignmentOp(void)
+ {
+ NR::rotate rot234_eq(r_id);
+ rot234_eq = rot234;
+ TS_ASSERT_EQUALS( rot234, rot234_eq );
+ TS_ASSERT_DIFFERS( rot234_eq, r_id );
+ }
+
+ void testInverse(void)
+ {
+ TS_ASSERT_EQUALS( r_id.inverse(), r_id );
+ TS_ASSERT_EQUALS( rot234.inverse(), NR::rotate(-.234) );
+ }
+
+ void testOpStarPointRotate(void)
+ {
+ TS_ASSERT_EQUALS( b * r_id, b );
+ TS_ASSERT_EQUALS( b * rot180, -b );
+ TS_ASSERT_EQUALS( b * rot234, b * NR::Matrix(rot234) );
+ TS_ASSERT(point_equalp(b * NR::rotate(M_PI / 2),
+ NR::rot90(b),
+ 1e-14));
+ TS_ASSERT_EQUALS( b * rotate_degrees(90.), NR::rot90(b) );
+ }
+
+ void testOpStarRotateRotate(void)
+ {
+ TS_ASSERT_EQUALS( r_id * r_id, r_id );
+ TS_ASSERT_EQUALS( rot180 * rot180, r_id );
+ TS_ASSERT_EQUALS( rot234 * r_id, rot234 );
+ TS_ASSERT_EQUALS( r_id * rot234, rot234 );
+ TS_ASSERT( rotate_equalp(rot234 * rot234.inverse(), r_id, 1e-14) );
+ TS_ASSERT( rotate_equalp(rot234.inverse() * rot234, r_id, 1e-14) );
+ TS_ASSERT( rotate_equalp(( NR::rotate(0.25) * NR::rotate(.5) ),
+ NR::rotate(.75),
+ 1e-10) );
+ }
+
+ void testOpDivRotateRotate(void)
+ {
+ TS_ASSERT_EQUALS( rot234 / r_id, rot234 );
+ TS_ASSERT_EQUALS( rot234 / rot180, rot234 * rot180 );
+ TS_ASSERT( rotate_equalp(rot234 / rot234, r_id, 1e-14) );
+ TS_ASSERT( rotate_equalp(r_id / rot234, rot234.inverse(), 1e-14) );
+ }
+
+};
+
+/*
+ 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/libnr/nr-rotate.h b/src/libnr/nr-rotate.h
new file mode 100644
index 000000000..051372ce6
--- /dev/null
+++ b/src/libnr/nr-rotate.h
@@ -0,0 +1,66 @@
+#ifndef SEEN_NR_ROTATE_H
+#define SEEN_NR_ROTATE_H
+
+/** \file
+ * Rotation about the origin.
+ */
+
+#include <libnr/nr-point.h>
+#include <libnr/nr-point-fns.h>
+#include <libnr/nr-point-ops.h>
+
+namespace NR {
+
+/** Notionally an NR::Matrix corresponding to rotation about the origin.
+ Behaves like NR::Matrix for multiplication.
+**/
+class rotate {
+public:
+ Point vec;
+
+private:
+ rotate();
+
+public:
+ explicit rotate(Coord theta);
+ explicit rotate(Point const &p) : vec(p) {}
+ explicit rotate(Coord const x, Coord const y) : vec(x, y) {}
+
+ bool operator==(rotate const &o) const {
+ return vec == o.vec;
+ }
+
+ bool operator!=(rotate const &o) const {
+ return vec != o.vec;
+ }
+
+ inline rotate &operator*=(rotate const &b);
+ /* Defined in nr-rotate-ops.h. */
+
+ rotate inverse() const {
+ /** \todo
+ * In the usual case that vec is a unit vector (within rounding error),
+ * dividing by len_sq is either a noop or numerically harmful.
+ * Make a unit_rotate class (or the like) that knows its length is 1.
+ */
+ double const len_sq = dot(vec, vec);
+ return rotate( Point(vec[X], -vec[Y])
+ / len_sq );
+ }
+};
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_ROTATE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-scale-matrix-ops.cpp b/src/libnr/nr-scale-matrix-ops.cpp
new file mode 100644
index 000000000..ce5120079
--- /dev/null
+++ b/src/libnr/nr-scale-matrix-ops.cpp
@@ -0,0 +1,26 @@
+#include "libnr/nr-matrix-ops.h"
+
+NR::Matrix
+operator*(NR::scale const &s, NR::Matrix const &m)
+{
+ using NR::X; using NR::Y;
+ NR::Matrix ret(m);
+ ret[0] *= s[X];
+ ret[1] *= s[X];
+ ret[2] *= s[Y];
+ ret[3] *= s[Y];
+ assert_close( ret, NR::Matrix(s) * m );
+ return ret;
+}
+
+
+/*
+ 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/libnr/nr-scale-matrix-ops.h b/src/libnr/nr-scale-matrix-ops.h
new file mode 100644
index 000000000..bf1b498c9
--- /dev/null
+++ b/src/libnr/nr-scale-matrix-ops.h
@@ -0,0 +1,13 @@
+#ifndef SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H
+#define SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H
+/** \file
+ * Declarations (and definition if inline) of operator
+ * blah (NR::scale, NR::Matrix).
+ */
+
+#include "libnr/nr-forward.h"
+
+NR::Matrix operator*(NR::scale const &s, NR::Matrix const &m);
+
+
+#endif /* !SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H */
diff --git a/src/libnr/nr-scale-ops.h b/src/libnr/nr-scale-ops.h
new file mode 100644
index 000000000..da1fea64c
--- /dev/null
+++ b/src/libnr/nr-scale-ops.h
@@ -0,0 +1,40 @@
+#ifndef SEEN_NR_SCALE_OPS_H
+#define SEEN_NR_SCALE_OPS_H
+
+#include <libnr/nr-scale.h>
+
+namespace NR {
+
+inline Point operator*(Point const &p, scale const &s)
+{
+ return Point(p[X] * s[X],
+ p[Y] * s[Y]);
+}
+
+inline scale operator*(scale const &a, scale const &b)
+{
+ return scale(a[X] * b[X],
+ a[Y] * b[Y]);
+}
+
+inline scale operator/(scale const &numer, scale const &denom)
+{
+ return scale(numer[X] / denom[X],
+ numer[Y] / denom[Y]);
+}
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_SCALE_OPS_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/libnr/nr-scale-test.cpp b/src/libnr/nr-scale-test.cpp
new file mode 100644
index 000000000..cc255d02a
--- /dev/null
+++ b/src/libnr/nr-scale-test.cpp
@@ -0,0 +1,71 @@
+#include <utest/utest.h>
+#include <libnr/nr-scale.h>
+#include <libnr/nr-scale-ops.h>
+using NR::X;
+using NR::Y;
+
+int main(int argc, char *argv[])
+{
+ utest_start("NR::scale");
+
+ NR::scale const sa(1.5, 2.0);
+ UTEST_TEST("x,y constructor and operator[] const") {
+ UTEST_ASSERT(sa[X] == 1.5);
+ UTEST_ASSERT(sa[Y] == 2.0);
+ UTEST_ASSERT(sa[0u] == 1.5);
+ UTEST_ASSERT(sa[1u] == 2.0);
+ }
+
+ NR::Point const b(-2.0, 3.0);
+ NR::scale const sb(b);
+
+ UTEST_TEST("copy constructor, operator==, operator!=") {
+ NR::scale const sa_copy(sa);
+ UTEST_ASSERT( sa == sa_copy );
+ UTEST_ASSERT(!( sa != sa_copy ));
+ UTEST_ASSERT( sa != sb );
+ }
+
+ UTEST_TEST("operator=") {
+ NR::scale sa_eq(sb);
+ sa_eq = sa;
+ UTEST_ASSERT( sa == sa_eq );
+ }
+
+ UTEST_TEST("point constructor") {
+ UTEST_ASSERT(sb[X] == b[X]);
+ UTEST_ASSERT(sb[Y] == b[Y]);
+ }
+
+ UTEST_TEST("operator*(Point, scale)") {
+ NR::Point const ab( b * sa );
+ UTEST_ASSERT( ab == NR::Point(-3.0, 6.0) );
+ }
+
+ UTEST_TEST("operator*(scale, scale)") {
+ NR::scale const sab( sa * sb );
+ UTEST_ASSERT( sab == NR::scale(-3.0, 6.0) );
+ }
+
+ UTEST_TEST("operator/(scale, scale)") {
+ NR::scale const sa_b( sa / sb );
+ NR::scale const exp_sa_b(-0.75, 2./3.);
+ UTEST_ASSERT( sa_b[0] == exp_sa_b[0] );
+ UTEST_ASSERT( fabs( sa_b[1] - exp_sa_b[1] ) < 1e-10 );
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/nr-scale-test.h b/src/libnr/nr-scale-test.h
new file mode 100644
index 000000000..dc5e6ef28
--- /dev/null
+++ b/src/libnr/nr-scale-test.h
@@ -0,0 +1,92 @@
+#include <cxxtest/TestSuite.h>
+
+#include <libnr/nr-scale.h>
+#include <libnr/nr-scale-ops.h>
+using NR::X;
+using NR::Y;
+
+class NrScaleTest : public CxxTest::TestSuite
+{
+public:
+
+ NrScaleTest() :
+ sa( 1.5, 2.0 ),
+ b( -2.0, 3.0 ),
+ sb( b )
+ {
+ }
+ virtual ~NrScaleTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrScaleTest *createSuite() { return new NrScaleTest(); }
+ static void destroySuite( NrScaleTest *suite ) { delete suite; }
+
+ NR::scale const sa;
+ NR::Point const b;
+ NR::scale const sb;
+
+
+
+ void testXY_CtorArrayOperator(void)
+ {
+ TS_ASSERT_EQUALS( sa[X], 1.5 );
+ TS_ASSERT_EQUALS( sa[Y], 2.0 );
+ TS_ASSERT_EQUALS( sa[0u], 1.5 );
+ TS_ASSERT_EQUALS( sa[1u], 2.0 );
+ }
+
+
+ void testCopyCtor_AssignmentOp_NotEquals(void)
+ {
+ NR::scale const sa_copy(sa);
+ TS_ASSERT_EQUALS( sa, sa_copy );
+ TS_ASSERT(!( sa != sa_copy ));
+ TS_ASSERT( sa != sb );
+ }
+
+ void testAssignmentOp(void)
+ {
+ NR::scale sa_eq(sb);
+ sa_eq = sa;
+ TS_ASSERT_EQUALS( sa, sa_eq );
+ }
+
+ void testPointCtor(void)
+ {
+ TS_ASSERT_EQUALS( sb[X], b[X] );
+ TS_ASSERT_EQUALS( sb[Y], b[Y] );
+ }
+
+ void testOpStarPointScale(void)
+ {
+ NR::Point const ab( b * sa );
+ TS_ASSERT_EQUALS( ab, NR::Point(-3.0, 6.0) );
+ }
+
+ void testOpStarScaleScale(void)
+ {
+ NR::scale const sab( sa * sb );
+ TS_ASSERT_EQUALS( sab, NR::scale(-3.0, 6.0) );
+ }
+
+ void testOpDivScaleScale(void)
+ {
+ NR::scale const sa_b( sa / sb );
+ NR::scale const exp_sa_b(-0.75, 2./3.);
+ TS_ASSERT_EQUALS( sa_b[0], exp_sa_b[0] );
+// TS_ASSERT_EQUALS( fabs( sa_b[1] - exp_sa_b[1] ) < 1e-10 );
+ TS_ASSERT_DELTA( sa_b[1], exp_sa_b[1], 1e-10 );
+ }
+};
+
+/*
+ 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/libnr/nr-scale-translate-ops.cpp b/src/libnr/nr-scale-translate-ops.cpp
new file mode 100644
index 000000000..911c92e5b
--- /dev/null
+++ b/src/libnr/nr-scale-translate-ops.cpp
@@ -0,0 +1,19 @@
+#include "libnr/nr-matrix-translate-ops.h"
+
+NR::Matrix
+operator*(NR::scale const &s, NR::translate const &t)
+{
+ return NR::Matrix(s) * t;
+}
+
+
+/*
+ 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/libnr/nr-scale-translate-ops.h b/src/libnr/nr-scale-translate-ops.h
new file mode 100644
index 000000000..2f6f23c2c
--- /dev/null
+++ b/src/libnr/nr-scale-translate-ops.h
@@ -0,0 +1,20 @@
+#ifndef SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_H
+#define SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_H
+
+#include "libnr/nr-forward.h"
+
+NR::Matrix operator*(NR::scale const &s, NR::translate const &t);
+
+
+#endif /* !SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_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/libnr/nr-scale.h b/src/libnr/nr-scale.h
new file mode 100644
index 000000000..c9de871de
--- /dev/null
+++ b/src/libnr/nr-scale.h
@@ -0,0 +1,50 @@
+#ifndef SEEN_NR_SCALE_H
+#define SEEN_NR_SCALE_H
+#include <libnr/nr-point.h>
+#include <libnr/nr-point-ops.h>
+
+namespace NR {
+
+class scale {
+private:
+ Point _p;
+
+private:
+ scale();
+
+public:
+ explicit scale(Point const &p) : _p(p) {}
+ scale(double const x, double const y) : _p(x, y) {}
+ explicit scale(double const s) : _p(s, s) {}
+ inline Coord operator[](Dim2 const d) const { return _p[d]; }
+ inline Coord operator[](unsigned const d) const { return _p[d]; }
+ inline Coord &operator[](Dim2 const d) { return _p[d]; }
+ inline Coord &operator[](unsigned const d) { return _p[d]; }
+
+ bool operator==(scale const &o) const {
+ return _p == o._p;
+ }
+
+ bool operator!=(scale const &o) const {
+ return _p != o._p;
+ }
+ scale inverse() const {
+ return scale(1/_p[0], 1/_p[1]);
+ }
+};
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_SCALE_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/libnr/nr-svp-private.h b/src/libnr/nr-svp-private.h
new file mode 100644
index 000000000..dbddcd7ca
--- /dev/null
+++ b/src/libnr/nr-svp-private.h
@@ -0,0 +1,30 @@
+#ifndef __NR_SVP_PRIVATE_H__
+#define __NR_SVP_PRIVATE_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-svp.h>
+
+#define NR_QUANT_X 16.0F
+#define NR_QUANT_Y 16.0F
+#define NR_COORD_X_FROM_ART(v) (floor (NR_QUANT_X * (v) + 0.5F) / NR_QUANT_X)
+#define NR_COORD_Y_FROM_ART(v) (floor (NR_QUANT_Y * (v) + 0.5F) / NR_QUANT_Y)
+#define NR_COORD_TO_ART(v) (v)
+
+/* NRVertex */
+
+NRVertex *nr_vertex_new (void);
+NRVertex *nr_vertex_new_xy (NR::Coord x, NR::Coord y);
+void nr_vertex_free_one (NRVertex *v);
+void nr_vertex_free_list (NRVertex *v);
+
+NRVertex *nr_vertex_reverse_list (NRVertex *v);
+
+#endif
diff --git a/src/libnr/nr-svp-render.cpp b/src/libnr/nr-svp-render.cpp
new file mode 100644
index 000000000..0c3b391d5
--- /dev/null
+++ b/src/libnr/nr-svp-render.cpp
@@ -0,0 +1,615 @@
+#define __NR_SVP_RENDER_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#define NR_SVPSEG_Y0(s,i) ((s)->points[(s)->segments[i].start].y)
+#define NR_SVPSEG_Y1(s,i) ((s)->points[(s)->segments[i].start + (s)->segments[i].length - 1].y)
+
+#define noNR_VERBOSE
+
+#include "nr-svp-render.h"
+
+static void nr_svp_render (NRSVP *svp, unsigned char *px, unsigned int bpp, unsigned int rs, int x0, int y0, int x1, int y1,
+ void (* run) (unsigned char *px, int len, int c0_24, int s0_24, void *data), void *data);
+
+static void nr_svp_run_A8_OR (unsigned char *d, int len, int c0_24, int s0_24, void *data);
+
+/* Renders graymask of svl into buffer */
+
+void
+nr_pixblock_render_svp_mask_or (NRPixBlock *d, NRSVP *svp)
+{
+ nr_svp_render (svp, NR_PIXBLOCK_PX (d), 1, d->rs,
+ d->area.x0, d->area.y0, d->area.x1, d->area.y1,
+ nr_svp_run_A8_OR, NULL);
+}
+
+static void
+nr_svp_run_A8_OR (unsigned char *d, int len, int c0_24, int s0_24, void *data)
+{
+ if ((c0_24 >= 0xff0000) && (s0_24 == 0x0)) {
+ /* Simple copy */
+ while (len > 0) {
+ d[0] = 255;
+ d += 1;
+ len -= 1;
+ }
+ } else {
+ while (len > 0) {
+ unsigned int ca, da;
+ /* Draw */
+ ca = c0_24 >> 16;
+ da = 65025 - (255 - ca) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ d += 1;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+}
+
+
+struct NRRun {
+ NRRun *next;
+ NR::Coord x0, y0, x1, y1;
+ float step;
+ float final;
+ NR::Coord x;
+ NR::Coord value;
+};
+
+static NRRun *nr_run_new (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, int wind);
+static NRRun *nr_run_free_one (NRRun *run);
+static void nr_run_free_list (NRRun *run);
+static NRRun *nr_run_insort (NRRun *start, NRRun *run);
+
+struct NRSlice {
+ NRSlice *next;
+ int wind;
+ NRPoint *points;
+ unsigned int current;
+ unsigned int last;
+ NR::Coord x;
+ NR::Coord y;
+ NR::Coord stepx;
+};
+
+static NRSlice *nr_slice_new (int wind, NRPoint *points, unsigned int length, NR::Coord y);
+static NRSlice *nr_slice_free_one (NRSlice *s);
+static void nr_slice_free_list (NRSlice *s);
+static NRSlice *nr_slice_insort (NRSlice *start, NRSlice *slice);
+static int nr_slice_compare (NRSlice *l, NRSlice *r);
+
+static void
+nr_svp_render (NRSVP *svp, unsigned char *px, unsigned int bpp, unsigned int rs, int iX0, int iY0, int iX1, int iY1,
+ void (* run) (unsigned char *px, int len, int c0_24, int s0_24, void *data), void *data)
+{
+ NR::Coord dX0, dY0, dX1, dY1;
+ NRSlice *slices;
+ unsigned int sidx;
+ int ystart;
+ unsigned char *rowbuffer;
+ int iy0;
+
+ if (!svp || !svp->length) return;
+
+ /* Find starting pixel row */
+ /* g_assert (svl->bbox.y0 == svl->vertex->y); */
+ sidx = 0;
+ while (NR_SVPSEG_IS_FLAT (svp, sidx) && (sidx < svp->length)) sidx += 1;
+ if (sidx >= svp->length) return;
+ ystart = (int) floor (NR_SVPSEG_Y0 (svp, sidx));
+ if (ystart > iY0) {
+ if (ystart >= iY1) return;
+ px += (ystart - iY0) * rs;
+ iY0 = ystart;
+ }
+
+ dX0 = iX0;
+ dY0 = iY0;
+ dX1 = iX1;
+ dY1 = iY1;
+
+ /* Construct initial slice list */
+ slices = NULL;
+ while (sidx < svp->length) {
+ if (!NR_SVPSEG_IS_FLAT (svp, sidx)) {
+ NRSVPSegment *seg;
+ /* It is real renderable segment */
+ /* Postpone if starts above initial slice */
+ if (NR_SVPSEG_Y0 (svp, sidx) > dY0) break;
+ seg = svp->segments + sidx;
+ if (seg->wind && (NR_SVPSEG_Y1 (svp, sidx) > dY0)) {
+ /* We are renderable and cross initial slice */
+ NRSlice *newslice;
+ newslice = nr_slice_new (seg->wind, svp->points + seg->start, seg->length, dY0);
+ slices = nr_slice_insort (slices, newslice);
+ }
+ }
+ sidx += 1;
+ }
+ if (!slices && (sidx >= svp->length)) return;
+
+ /* Row buffer */
+ /* fixme: not needed */
+ rowbuffer = px;
+
+ /* Main iteration */
+ for (iy0 = iY0; iy0 < iY1; iy0 += 1) {
+ NR::Coord dy0, dy1;
+ NRSlice *ss, *cs;
+ NRRun *runs;
+ int xstart;
+ float globalval;
+ unsigned char *d;
+ int ix0;
+
+ dy0 = iy0;
+ dy1 = dy0 + 1.0;
+
+ /* Add possible new svls to slice list */
+ while (sidx < svp->length) {
+ if (!NR_SVPSEG_IS_FLAT (svp, sidx)) {
+ NRSVPSegment *seg;
+ /* It is real renderable segment */
+ /* Postpone if starts above ending slice */
+ if (NR_SVPSEG_Y0 (svp, sidx) > dy1) break;
+ seg = svp->segments + sidx;
+ if (seg->wind) {
+ NR::Coord y;
+ NRSlice *newslice;
+ /* We are renderable */
+ /* fixme: we should use safely nsvl->vertex->y here */
+ y = MAX (dy0, NR_SVPSEG_Y0 (svp, sidx));
+ newslice = nr_slice_new (seg->wind, svp->points + seg->start, seg->length, y);
+ slices = nr_slice_insort (slices, newslice);
+ }
+ }
+ sidx += 1;
+ }
+ /* Construct runs, stretching slices */
+ /* fixme: This step can be optimized by continuing long runs and adding only new ones (Lauris) */
+ runs = NULL;
+ ss = NULL;
+ cs = slices;
+ while (cs) {
+ /* g_assert (cs->y >= y0); */
+ /* g_assert (cs->y < (y + 1)); */
+ while ((cs->y < dy1) && (cs->current < cs->last)) {
+ NR::Coord rx0, ry0, rx1, ry1;
+ NRRun * newrun;
+ rx0 = cs->x;
+ ry0 = cs->y;
+ if (cs->points[cs->current + 1].y > dy1) {
+ /* The same slice continues */
+ rx1 = rx0 + (dy1 - ry0) * cs->stepx;
+ ry1 = dy1;
+ cs->x = rx1;
+ cs->y = ry1;
+ } else {
+ /* Subpixel height run */
+ cs->current += 1;
+ rx1 = cs->points[cs->current].x;
+ ry1 = cs->points[cs->current].y;
+ cs->x = rx1;
+ cs->y = ry1;
+ if (cs->current < cs->last) {
+ cs->stepx = (cs->points[cs->current + 1].x - rx1) / (cs->points[cs->current + 1].y - ry1);
+ }
+ }
+ newrun = nr_run_new (rx0, ry0, rx1, ry1, cs->wind);
+ /* fixme: we should use walking forward/backward instead */
+ runs = nr_run_insort (runs, newrun);
+ }
+ if (cs->current < cs->last) {
+ ss = cs;
+ cs = cs->next;
+ } else {
+ /* Slice is exhausted */
+ cs = nr_slice_free_one (cs);
+ if (ss) {
+ ss->next = cs;
+ } else {
+ slices = cs;
+ }
+ }
+ }
+ /* Slices are expanded to next scanline */
+ /* Run list is generated */
+ /* Globalval is the sum of all finished runs */
+ globalval = 0.0;
+ if ((runs) && (dX0 < runs->x0)) {
+ /* First run starts right from x0 */
+ xstart = (int) floor (runs->x0);
+ } else {
+ NRRun *sr, *cr;
+ /* First run starts left from x0 */
+ xstart = iX0;
+ sr = NULL;
+ cr = runs;
+ while ((cr) && (cr->x0 < dX0)) {
+ if (cr->x1 <= dX0) {
+ globalval += cr->final;
+ /* Remove exhausted current run */
+ cr = nr_run_free_one (cr);
+ if (sr) {
+ sr->next = cr;
+ } else {
+ runs = cr;
+ }
+ } else {
+ cr->x = dX0;
+ cr->value = (dX0 - cr->x0) * cr->step;
+ sr = cr;
+ cr = cr->next;
+ }
+ }
+ }
+
+ /* Running buffer */
+ d = rowbuffer + bpp * (xstart - iX0);
+
+ for (ix0 = xstart; (runs) && (ix0 < iX1); ix0++) {
+ NR::Coord dx0, dx1;
+ int ix1;
+ NRRun *sr, *cr;
+ float localval;
+ unsigned int fill;
+ float fillstep;
+ int rx1;
+ int c24;
+
+ dx0 = ix0;
+ dx1 = dx0 + 1.0;
+ ix1 = ix0 + 1;
+
+ /* process runs */
+ localval = globalval;
+ sr = NULL;
+ cr = runs;
+ fill = TRUE;
+ fillstep = 0.0;
+ rx1 = iX1;
+ while ((cr) && (cr->x0 < dx1)) {
+ if (cr->x1 <= dx1) {
+ /* Run ends here */
+ /* No fill */
+ fill = FALSE;
+ /* Continue with final value */
+ globalval += cr->final;
+ /* Add initial trapezoid */
+ localval += (float) (0.5 * (cr->x1 - cr->x) * (cr->value + cr->final));
+ /* Add final rectangle */
+ localval += (float) ((dx1 - cr->x1) * cr->final);
+ /* Remove exhausted run */
+ cr = nr_run_free_one (cr);
+ if (sr) {
+ sr->next = cr;
+ } else {
+ runs = cr;
+ }
+ } else {
+ /* Run continues through xnext */
+ if (fill) {
+ if (cr->x0 > ix0) {
+ fill = FALSE;
+ } else {
+ rx1 = MIN (rx1, (int) floor (cr->x1));
+ fillstep += cr->step;
+ }
+ }
+ localval += (float) ((dx1 - cr->x) * (cr->value + (dx1 - cr->x) * cr->step / 2.0));
+ cr->x = dx1;
+ cr->value = (dx1 - cr->x0) * cr->step;
+ sr = cr;
+ cr = cr->next;
+ }
+ }
+ if (fill) {
+ if (cr) rx1 = MIN (rx1, (int) floor (cr->x0));
+ }
+#ifdef NR_VERBOSE
+ if ((localval < -0.01) || (localval > 1.01)) {
+ printf ("Weird localval %g : gv %g\n", localval, globalval); // localizing ok
+ }
+#endif
+ localval = CLAMP (localval, 0.0F, 1.0F);
+ c24 = (int) floor (16777215 * localval + 0.5);
+ if (fill && (rx1 > ix1)) {
+ NRRun *r;
+ int s24;
+ s24 = (int) floor (16777215 * fillstep + 0.5);
+ if ((s24 != 0) || (c24 > 65535)) {
+ run (d, rx1 - ix0, c24, s24, data);
+ }
+ /* We have to rewind run positions as well */
+ for (r = runs; r && (r->x0 < dx1); r = r->next) {
+ r->x = rx1;
+ r->value = (rx1 - r->x0) * r->step;
+ }
+ d += bpp * (rx1 - ix0);
+ ix0 = rx1 - 1;
+ } else {
+ run (d, 1, c24, 0, data);
+ d += bpp;
+ }
+ }
+ nr_run_free_list (runs);
+ rowbuffer += rs;
+ }
+ if (slices) nr_slice_free_list (slices);
+}
+
+/* Slices */
+
+#define NR_SLICE_ALLOC_SIZE 32
+static NRSlice *ffslice = NULL;
+
+static NRSlice *
+nr_slice_new (int wind, NRPoint *points, unsigned int length, NR::Coord y)
+{
+ NRSlice *s;
+ NRPoint *p;
+
+ /* g_assert (svl); */
+ /* g_assert (svl->vertex); */
+ /* fixme: not sure, whether correct */
+ /* g_assert (y == NR_COORD_SNAP (y)); */
+ /* Slices startpoints are included, endpoints excluded */
+ /* g_return_val_if_fail (y >= svl->bbox.y0, NULL); */
+ /* g_return_val_if_fail (y < svl->bbox.y1, NULL); */
+
+ s = ffslice;
+
+ if (s == NULL) {
+ int i;
+ s = nr_new (NRSlice, NR_SLICE_ALLOC_SIZE);
+ for (i = 1; i < (NR_SLICE_ALLOC_SIZE - 1); i++) s[i].next = &s[i + 1];
+ s[NR_SLICE_ALLOC_SIZE - 1].next = NULL;
+ ffslice = s + 1;
+ } else {
+ ffslice = s->next;
+ }
+
+ s->next = NULL;
+ s->wind = wind;
+ s->points = points;
+ s->current = 0;
+ s->last = length - 1;
+
+ while ((s->current < s->last) && (s->points[s->current + 1].y <= y)) s->current += 1;
+ p = s->points + s->current;
+
+ if (s->points[s->current].y == y) {
+ s->x = p[0].x;
+ } else {
+ s->x = p[0].x + (p[1].x - p[0].x) * (y - p[0].y) / (p[1].y - p[0].y);
+ }
+ s->y = y;
+ s->stepx = (p[1].x - p[0].x) / (p[1].y - p[0].y);
+
+ return s;
+}
+
+static NRSlice *
+nr_slice_free_one (NRSlice *slice)
+{
+ NRSlice *next;
+ next = slice->next;
+ slice->next = ffslice;
+ ffslice = slice;
+ return next;
+}
+
+static void
+nr_slice_free_list (NRSlice *slice)
+{
+ NRSlice *l;
+
+ if (!slice) return;
+
+ for (l = slice; l->next != NULL; l = l->next);
+
+ l->next = ffslice;
+ ffslice = slice;
+}
+
+static NRSlice *
+nr_slice_insort (NRSlice * start, NRSlice * slice)
+{
+ NRSlice * s, * l;
+
+ if (!start) return slice;
+ if (!slice) return start;
+
+ if (nr_slice_compare (slice, start) <= 0) {
+ slice->next = start;
+ return slice;
+ }
+
+ s = start;
+ for (l = start->next; l != NULL; l = l->next) {
+ if (nr_slice_compare (slice, l) <= 0) {
+ slice->next = l;
+ s->next = slice;
+ return start;
+ }
+ s = l;
+ }
+
+ slice->next = NULL;
+ s->next = slice;
+
+ return start;
+}
+
+static int
+nr_slice_compare (NRSlice *l, NRSlice *r)
+{
+ if (l->y == r->y) {
+ if (l->x < r->x) return -1;
+ if (l->x > r->x) return 1;
+ if (l->stepx < r->stepx) return -1;
+ if (l->stepx > r->stepx) return 1;
+ } else if (l->y > r->y) {
+ unsigned int pidx;
+ NRPoint *p;
+ NR::Coord x, ldx, rdx;
+ /* This is bitch - we have to determine r values at l->y */
+ pidx = 0;
+ while ((pidx < r->last) && (r->points[pidx + 1].y <= l->y)) pidx += 1;
+ /* If v is last vertex, r ends before l starts */
+ if (pidx >= r->last) return 1;
+ p = r->points + pidx;
+ if (p[0].y == l->y) {
+ x = p[0].x;
+ } else {
+ x = p[0].x + (p[1].x - p[0].x) * (l->y - p[0].y) / (p[1].y - p[0].y);
+ }
+ if (l->x < x) return -1;
+ if (l->x > x) return 1;
+ ldx = l->stepx * (p[1].y - p[0].y);
+ rdx = p[1].x - p[0].x;
+ if (ldx < rdx) return -1;
+ if (ldx > rdx) return 1;
+ } else {
+ unsigned int pidx;
+ NRPoint *p;
+ NR::Coord x, ldx, rdx;
+ /* This is bitch - we have to determine l value at r->y */
+ pidx = 0;
+ while ((pidx < l->last) && (l->points[pidx + 1].y <= r->y)) pidx += 1;
+ /* If v is last vertex, l ends before r starts */
+ if (pidx >= l->last) return 1;
+ p = l->points + pidx;
+ if (p[0].y == r->y) {
+ x = p[0].x;
+ } else {
+ x = p[0].x + (p[1].x - p[0].x) * (r->y - p[0].y) / (p[1].y - p[0].y);
+ }
+ if (x < r->x) return -1;
+ if (x > r->x) return 1;
+ ldx = l->stepx * (p[1].y - p[0].y);
+ rdx = p[1].x - p[0].x;
+ if (ldx < rdx) return -1;
+ if (ldx > rdx) return 1;
+ }
+ return 0;
+}
+
+/*
+ * Memory management stuff follows (remember goals?)
+ */
+
+#define NR_RUN_ALLOC_SIZE 32
+static NRRun *ffrun = NULL;
+
+static NRRun *
+nr_run_new (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, int wind)
+{
+ NRRun * r;
+
+ r = ffrun;
+
+ if (r == NULL) {
+ int i;
+ r = nr_new (NRRun, NR_RUN_ALLOC_SIZE);
+ for (i = 1; i < (NR_RUN_ALLOC_SIZE - 1); i++) (r + i)->next = (r + i + 1);
+ (r + NR_RUN_ALLOC_SIZE - 1)->next = NULL;
+ ffrun = r + 1;
+ } else {
+ ffrun = r->next;
+ }
+
+ r->next = NULL;
+
+ if (x0 <= x1) {
+ r->x0 = x0;
+ r->x1 = x1;
+ r->y0 = y0;
+ r->y1 = y1;
+ r->step = (x0 == x1) ? 0.0F : (float) (wind * (y1 - y0) / (x1 - x0));
+ } else {
+ r->x0 = x1;
+ r->x1 = x0;
+ r->y0 = y1;
+ r->y1 = y0;
+ r->step = (float) (wind * (y1 - y0) / (x0 - x1));
+ }
+
+ r->final = (float) (wind * (y1 - y0));
+
+ r->value = 0.0;
+ r->x = r->x0;
+
+ return r;
+}
+
+static NRRun *
+nr_run_free_one (NRRun *run)
+{
+ NRRun *next;
+ next = run->next;
+ run->next = ffrun;
+ ffrun = run;
+ return next;
+}
+
+static void
+nr_run_free_list (NRRun * run)
+{
+ NRRun * l;
+
+ if (!run) return;
+
+ for (l = run; l->next != NULL; l = l->next);
+ l->next = ffrun;
+ ffrun = run;
+}
+
+static NRRun *
+nr_run_insort (NRRun * start, NRRun * run)
+{
+ NRRun * s, * l;
+
+ if (!start) return run;
+ if (!run) return start;
+
+ if (run->x0 < start->x0) {
+ run->next = start;
+ return run;
+ }
+
+ s = start;
+ for (l = start->next; l != NULL; l = l->next) {
+ if (run->x0 < l->x0) {
+ run->next = l;
+ s->next = run;
+ return start;
+ }
+ s = l;
+ }
+
+ s->next = run;
+
+ return start;
+}
+
+/*
+ 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/libnr/nr-svp-render.h b/src/libnr/nr-svp-render.h
new file mode 100644
index 000000000..1638fb286
--- /dev/null
+++ b/src/libnr/nr-svp-render.h
@@ -0,0 +1,30 @@
+#ifndef __NR_SVP_RENDER_H__
+#define __NR_SVP_RENDER_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-pixblock.h>
+#include <libnr/nr-svp.h>
+
+/* Renders graymask of svp into buffer */
+void nr_pixblock_render_svp_mask_or (NRPixBlock *d, NRSVP *svp);
+
+#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/libnr/nr-svp.cpp b/src/libnr/nr-svp.cpp
new file mode 100644
index 000000000..a1484397a
--- /dev/null
+++ b/src/libnr/nr-svp.cpp
@@ -0,0 +1,180 @@
+#define __NR_SVP_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#define noNR_VERBOSE
+
+#define NR_SVP_LENGTH_MAX 128
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_IEEEFP_H
+# include <ieeefp.h>
+#endif
+
+#include "nr-rect.h"
+#include "nr-svp-private.h"
+
+/* Sorted vector paths */
+
+void
+nr_svp_free (NRSVP *svp)
+{
+ if (svp->points) nr_free (svp->points);
+ free (svp);
+}
+
+void
+nr_svp_bbox (NRSVP *svp, NRRect *bbox, unsigned int clear)
+{
+ unsigned int sidx;
+ float x0, y0, x1, y1;
+
+ x0 = y0 = NR_HUGE;
+ x1 = y1 = -NR_HUGE;
+
+ for (sidx = 0; sidx < svp->length; sidx++) {
+ NRSVPSegment *seg;
+ seg = svp->segments + sidx;
+ if (seg->length) {
+ x0 = MIN (x0, seg->x0);
+ y0 = MIN (y0, svp->points[seg->start].y);
+ x1 = MAX (x1, seg->x1);
+ y1 = MAX (y1, svp->points[seg->start + seg->length - 1].y);
+ }
+ }
+
+ if ((x1 > x0) && (y1 > y0)) {
+ if (clear || (bbox->x1 <= bbox->x0) || (bbox->y1 <= bbox->y0)) {
+ bbox->x0 = x0;
+ bbox->y0 = y0;
+ bbox->x1 = x1;
+ bbox->y1 = y1;
+ } else {
+ bbox->x0 = MIN (bbox->x0, x0);
+ bbox->y0 = MIN (bbox->y0, y0);
+ bbox->x1 = MAX (bbox->x1, x1);
+ bbox->y1 = MAX (bbox->y1, y1);
+ }
+ }
+}
+
+/* NRVertex */
+
+#define NR_VERTEX_ALLOC_SIZE 4096
+static NRVertex *ffvertex = NULL;
+
+NRVertex *
+nr_vertex_new (void)
+{
+ NRVertex * v;
+#ifndef NR_VERTEX_ALLOC
+
+ v = ffvertex;
+
+ if (v == NULL) {
+ int i;
+ v = nr_new (NRVertex, NR_VERTEX_ALLOC_SIZE);
+ for (i = 1; i < (NR_VERTEX_ALLOC_SIZE - 1); i++) v[i].next = &v[i + 1];
+ v[NR_VERTEX_ALLOC_SIZE - 1].next = NULL;
+ ffvertex = v + 1;
+ } else {
+ ffvertex = v->next;
+ }
+#else
+ v = nr_new (NRVertex, 1);
+#endif
+
+ v->next = NULL;
+
+ return v;
+}
+
+NRVertex *
+nr_vertex_new_xy (NR::Coord x, NR::Coord y)
+{
+ NRVertex * v;
+
+ if (!finite(x) || !finite(y)) {
+ g_critical("nr_vertex_new_xy: BUG: Coordinates are not finite");
+ x = y = 0;
+ } else if (!( fabs(x) < 1e17 && fabs(y) < 1e17 )) {
+ g_critical("nr_vertex_new_xy: Coordinates out of range");
+ x = y = 0;
+ }
+
+ v = nr_vertex_new ();
+
+ v->x = x;
+ v->y = y;
+
+ return v;
+}
+
+void
+nr_vertex_free_one (NRVertex * v)
+{
+#ifndef NR_VERTEX_ALLOC
+ v->next = ffvertex;
+ ffvertex = v;
+#else
+ nr_free (v);
+#endif
+}
+
+void
+nr_vertex_free_list (NRVertex * v)
+{
+#ifndef NR_VERTEX_ALLOC
+ NRVertex * l;
+ for (l = v; l->next != NULL; l = l->next);
+ l->next = ffvertex;
+ ffvertex = v;
+#else
+ NRVertex *l, *n;
+ l = v;
+ while (l) {
+ n = l->next;
+ nr_free (l);
+ l = n;
+ }
+#endif
+}
+
+NRVertex *
+nr_vertex_reverse_list (NRVertex * v)
+{
+ NRVertex * p;
+
+ p = NULL;
+
+ while (v) {
+ NRVertex * n;
+ n = v->next;
+ v->next = p;
+ p = v;
+ v = n;
+ }
+
+ return p;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-svp.h b/src/libnr/nr-svp.h
new file mode 100644
index 000000000..ca1521f29
--- /dev/null
+++ b/src/libnr/nr-svp.h
@@ -0,0 +1,65 @@
+#ifndef __NR_SVP_H__
+#define __NR_SVP_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+/* Sorted vector paths */
+
+#include <glib/gtypes.h>
+#include <libnr/nr-coord.h>
+
+struct NRPoint;
+
+struct NRSVPSegment {
+ gint16 wind;
+ guint16 length;
+ guint32 start;
+ float x0, x1;
+};
+
+struct NRSVPFlat {
+ gint16 wind;
+ guint16 length;
+ float y;
+ float x0, x1;
+};
+
+struct NRSVP {
+ unsigned int length;
+ NRPoint *points;
+ NRSVPSegment segments[1];
+};
+
+#define NR_SVPSEG_IS_FLAT(s,i) (!(s)->segments[i].length)
+
+void nr_svp_free (NRSVP *svp);
+
+void nr_svp_bbox (NRSVP *svp, NRRect *bbox, unsigned int clear);
+
+/* Sorted vertex lists */
+
+struct NRVertex {
+ NRVertex *next;
+ NR::Coord x, y;
+};
+
+
+#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/libnr/nr-translate-matrix-ops.cpp b/src/libnr/nr-translate-matrix-ops.cpp
new file mode 100644
index 000000000..d03caf4ff
--- /dev/null
+++ b/src/libnr/nr-translate-matrix-ops.cpp
@@ -0,0 +1,27 @@
+#include "libnr/nr-matrix-ops.h"
+
+namespace NR {
+
+Matrix
+operator*(translate const &t, Matrix const &m)
+{
+ Matrix ret(m);
+ ret[4] += m[0] * t[X] + m[2] * t[Y];
+ ret[5] += m[1] * t[X] + m[3] * t[Y];
+ assert_close( ret, Matrix(t) * m );
+ return ret;
+}
+
+} /* namespace NR */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-translate-matrix-ops.h b/src/libnr/nr-translate-matrix-ops.h
new file mode 100644
index 000000000..aceb123d1
--- /dev/null
+++ b/src/libnr/nr-translate-matrix-ops.h
@@ -0,0 +1,22 @@
+#ifndef SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_H
+#define SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_H
+
+#include "libnr/nr-forward.h"
+
+namespace NR {
+Matrix operator*(translate const &t, Matrix const &m);
+}
+
+
+#endif /* !SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_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/libnr/nr-translate-ops.h b/src/libnr/nr-translate-ops.h
new file mode 100644
index 000000000..14ab6d1ed
--- /dev/null
+++ b/src/libnr/nr-translate-ops.h
@@ -0,0 +1,43 @@
+#ifndef SEEN_NR_TRANSLATE_OPS_H
+#define SEEN_NR_TRANSLATE_OPS_H
+
+#include <libnr/nr-translate.h>
+#include <libnr/nr-point-ops.h>
+
+namespace NR {
+
+inline bool operator==(translate const &a, translate const &b)
+{
+ return a.offset == b.offset;
+}
+
+inline bool operator!=(translate const &a, translate const &b)
+{
+ return !( a == b );
+}
+
+inline translate operator*(translate const &a, translate const &b)
+{
+ return translate( a.offset + b.offset );
+}
+
+inline Point operator*(Point const &v, translate const &t)
+{
+ return t.offset + v;
+}
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_TRANSLATE_OPS_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/libnr/nr-translate-rotate-ops.cpp b/src/libnr/nr-translate-rotate-ops.cpp
new file mode 100644
index 000000000..35f60c10d
--- /dev/null
+++ b/src/libnr/nr-translate-rotate-ops.cpp
@@ -0,0 +1,20 @@
+#include <libnr/nr-matrix-translate-ops.h>
+#include <libnr/nr-rotate-ops.h>
+
+NR::Matrix
+operator*(NR::translate const &a, NR::rotate const &b)
+{
+ return NR::Matrix(b) * NR::translate(a.offset * b);
+}
+
+
+/*
+ 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/libnr/nr-translate-rotate-ops.h b/src/libnr/nr-translate-rotate-ops.h
new file mode 100644
index 000000000..0716f21cc
--- /dev/null
+++ b/src/libnr/nr-translate-rotate-ops.h
@@ -0,0 +1,21 @@
+#ifndef SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_H
+#define SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_H
+
+#include <libnr/nr-forward.h>
+
+
+NR::Matrix operator*(NR::translate const &a, NR::rotate const &b);
+
+
+#endif /* !SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_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/libnr/nr-translate-scale-ops.cpp b/src/libnr/nr-translate-scale-ops.cpp
new file mode 100644
index 000000000..b995bbad5
--- /dev/null
+++ b/src/libnr/nr-translate-scale-ops.cpp
@@ -0,0 +1,25 @@
+#include "libnr/nr-matrix-ops.h"
+
+NR::Matrix
+operator*(NR::translate const &t, NR::scale const &s)
+{
+ using NR::X; using NR::Y;
+
+ NR::Matrix ret(s);
+ ret[4] = t[X] * s[X];
+ ret[5] = t[Y] * s[Y];
+ assert_close( ret, NR::Matrix(t) * NR::Matrix(s) );
+ return ret;
+}
+
+
+/*
+ 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/libnr/nr-translate-scale-ops.h b/src/libnr/nr-translate-scale-ops.h
new file mode 100644
index 000000000..c72665857
--- /dev/null
+++ b/src/libnr/nr-translate-scale-ops.h
@@ -0,0 +1,20 @@
+#ifndef SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_H
+#define SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_H
+
+#include "libnr/nr-forward.h"
+
+NR::Matrix operator*(NR::translate const &t, NR::scale const &s);
+
+
+#endif /* !SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_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/libnr/nr-translate-test.cpp b/src/libnr/nr-translate-test.cpp
new file mode 100644
index 000000000..9e1ef1166
--- /dev/null
+++ b/src/libnr/nr-translate-test.cpp
@@ -0,0 +1,65 @@
+#include <utest/utest.h>
+#include <libnr/nr-point-ops.h>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-translate.h>
+#include <libnr/nr-translate-ops.h>
+using NR::X;
+using NR::Y;
+
+
+int main(int argc, char *argv[])
+{
+ utest_start("translate");
+
+ NR::Point const b(-2.0, 3.0);
+ NR::translate const tb(b);
+ NR::translate const tc(-3.0, -2.0);
+ UTEST_TEST("constructors, operator[]") {
+ UTEST_ASSERT( tc[X] == -3.0 && tc[Y] == -2.0 );
+ UTEST_ASSERT( tb[0] == b[X] && tb[1] == b[Y] );
+ }
+
+ UTEST_TEST("operator=") {
+ NR::translate tb_eq(tc);
+ tb_eq = tb;
+ UTEST_ASSERT( tb == tb_eq );
+ UTEST_ASSERT( tb_eq != tc );
+ }
+
+ NR::translate const tbc( tb * tc );
+ UTEST_TEST("operator*(translate, translate)") {
+ UTEST_ASSERT( tbc.offset == NR::Point(-5.0, 1.0) );
+ UTEST_ASSERT( tbc.offset == ( tc * tb ).offset );
+ UTEST_ASSERT( NR::Matrix(tbc) == NR::Matrix(tb) * NR::Matrix(tc) );
+ }
+
+ UTEST_TEST("operator*(Point, translate)") {
+ UTEST_ASSERT( tbc.offset == b * tc );
+ UTEST_ASSERT( b * tc == b * NR::Matrix(tc) );
+ }
+
+ NR::translate const t_id(0.0, 0.0);
+ NR::Matrix const m_id(NR::identity());
+ UTEST_TEST("identity") {
+ UTEST_ASSERT( b * t_id == b );
+ UTEST_ASSERT( NR::Matrix(t_id) == m_id );
+ }
+
+ return ( utest_end()
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+/*
+ 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/libnr/nr-translate-test.h b/src/libnr/nr-translate-test.h
new file mode 100644
index 000000000..20b537a45
--- /dev/null
+++ b/src/libnr/nr-translate-test.h
@@ -0,0 +1,87 @@
+#include <cxxtest/TestSuite.h>
+
+#include <libnr/nr-point-ops.h>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-matrix-fns.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-point-matrix-ops.h>
+#include <libnr/nr-translate.h>
+#include <libnr/nr-translate-ops.h>
+using NR::X;
+using NR::Y;
+
+class NrTranslateTest : public CxxTest::TestSuite
+{
+public:
+
+ NrTranslateTest() :
+ b( -2.0, 3.0 ),
+ tb( b ),
+ tc( -3.0, -2.0 ),
+ tbc( tb * tc ),
+ t_id( 0.0, 0.0 ),
+ m_id( NR::identity() )
+ {
+ }
+ virtual ~NrTranslateTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrTranslateTest *createSuite() { return new NrTranslateTest(); }
+ static void destroySuite( NrTranslateTest *suite ) { delete suite; }
+
+ NR::Point const b;
+ NR::translate const tb;
+ NR::translate const tc;
+ NR::translate const tbc;
+ NR::translate const t_id;
+ NR::Matrix const m_id;
+
+
+ void testCtorsArrayOperator(void)
+ {
+ TS_ASSERT_EQUALS( tc[X], -3.0 );
+ TS_ASSERT_EQUALS( tc[Y], -2.0 );
+
+ TS_ASSERT_EQUALS( tb[0], b[X] );
+ TS_ASSERT_EQUALS( tb[1], b[Y] );
+ }
+
+ void testAssignmentOperator(void)
+ {
+ NR::translate tb_eq(tc);
+ tb_eq = tb;
+ TS_ASSERT_EQUALS( tb, tb_eq );
+ TS_ASSERT_DIFFERS( tb_eq, tc );
+ }
+
+ void testOpStarTranslateTranslate(void)
+ {
+ TS_ASSERT_EQUALS( tbc.offset, NR::Point(-5.0, 1.0) );
+ TS_ASSERT_EQUALS( tbc.offset, ( tc * tb ).offset );
+ TS_ASSERT_EQUALS( NR::Matrix(tbc), NR::Matrix(tb) * NR::Matrix(tc) );
+ }
+
+ void testOpStarPointTranslate(void)
+ {
+ TS_ASSERT_EQUALS( tbc.offset, b * tc );
+ TS_ASSERT_EQUALS( b * tc, b * NR::Matrix(tc) );
+ }
+
+ void testIdentity(void)
+ {
+ TS_ASSERT_EQUALS( b * t_id, b );
+ TS_ASSERT_EQUALS( NR::Matrix(t_id), m_id );
+ }
+};
+
+/*
+ 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/libnr/nr-translate.h b/src/libnr/nr-translate.h
new file mode 100644
index 000000000..c1ea927e0
--- /dev/null
+++ b/src/libnr/nr-translate.h
@@ -0,0 +1,34 @@
+#ifndef SEEN_NR_TRANSLATE_H
+#define SEEN_NR_TRANSLATE_H
+
+#include <libnr/nr-point.h>
+
+namespace NR {
+
+class translate {
+public:
+ Point offset;
+private:
+ translate();
+public:
+ explicit translate(Point const &p) : offset(p) {}
+ explicit translate(Coord const x, Coord const y) : offset(x, y) {}
+ Coord operator[](Dim2 const dim) const { return offset[dim]; }
+ Coord operator[](unsigned const dim) const { return offset[dim]; }
+};
+
+} /* namespace NR */
+
+
+#endif /* !SEEN_NR_TRANSLATE_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/libnr/nr-types-test.cpp b/src/libnr/nr-types-test.cpp
new file mode 100644
index 000000000..87a35fc68
--- /dev/null
+++ b/src/libnr/nr-types-test.cpp
@@ -0,0 +1,105 @@
+#include "../utest/utest.h"
+#include <libnr/nr-types.h>
+#include <libnr/nr-point-fns.h>
+#include <cmath>
+using NR::Point;
+using NR::X;
+using NR::Y;
+
+
+int main(int argc, char *argv[]) {
+ utest_start("Basic NR::Point operations");
+
+ UTEST_TEST("X,Y values") {
+ UTEST_ASSERT(X == 0);
+ UTEST_ASSERT(Y == 1);
+ }
+
+ NR::Point const a(1.5, 2.0);
+ UTEST_TEST("x,y constructor and operator[] const") {
+ UTEST_ASSERT(a[X] == 1.5);
+ UTEST_ASSERT(a[Y] == 2.0);
+ }
+
+ NR::Point const b(-2.0, 3.0);
+
+ UTEST_TEST("copy constructor") {
+ NR::Point a_copy(a);
+ UTEST_ASSERT(a == a_copy);
+ UTEST_ASSERT(!(a != a_copy));
+ }
+
+ UTEST_TEST("non-const operator[]") {
+ NR::Point a_copy(a);
+ a_copy[X] = -2.0;
+ UTEST_ASSERT(a_copy != a);
+ UTEST_ASSERT(a_copy != b);
+ a_copy[Y] = 3.0;
+ UTEST_ASSERT(a_copy == b);
+ }
+
+ NR::Point const ab(-0.5, 5.0);
+ UTEST_TEST("binary +, -") {
+ UTEST_ASSERT(a != b);
+ UTEST_ASSERT(a + b == ab);
+ UTEST_ASSERT(ab - a == b);
+ UTEST_ASSERT(ab - b == a);
+ UTEST_ASSERT(ab + a != b);
+ }
+
+ UTEST_TEST("unary-") {
+ UTEST_ASSERT(-a == Point(-a[X], -a[Y]));
+ }
+
+ UTEST_TEST("scale, divide") {
+ UTEST_ASSERT(-a == -1.0 * a);
+ UTEST_ASSERT(a + a + a == 3.0 * a);
+ UTEST_ASSERT(a / .5 == 2.0 * a);
+ }
+
+ UTEST_TEST("dot") {
+ UTEST_ASSERT( dot(a, b) == ( a[X] * b[X] +
+ a[Y] * b[Y] ) );
+ UTEST_ASSERT( dot(a, NR::rot90(a)) == 0.0 );
+ UTEST_ASSERT( dot(-a, NR::rot90(a)) == 0.0 );
+ }
+
+ double const small = pow(2.0, -1070);
+
+ Point const small_left(-small, 0.0);
+ Point const smallish_3_neg4(3.0 * small, -4.0 * small);
+
+ UTEST_TEST("L1, L2, LInfty norms") {
+ UTEST_ASSERT(L1(small_left) == small);
+ UTEST_ASSERT(L2(small_left) == small);
+ UTEST_ASSERT(LInfty(small_left) == small);
+
+ UTEST_ASSERT(L1(smallish_3_neg4) == 7.0 * small);
+ UTEST_ASSERT(L2(smallish_3_neg4) == 5.0 * small);
+ UTEST_ASSERT(LInfty(smallish_3_neg4) == 4.0 * small);
+ }
+
+ UTEST_TEST("operator+=") {
+ Point x(a);
+ x += b;
+ UTEST_ASSERT(x == ab);
+ }
+
+ UTEST_TEST("operator/=") {
+ Point x(a);
+ x /= .5;
+ UTEST_ASSERT(x == a + a);
+ }
+
+ UTEST_TEST("normalize") {
+ Point x(small_left);
+ x.normalize();
+ UTEST_ASSERT(x == Point(-1.0, 0.0));
+
+ x = smallish_3_neg4;
+ x.normalize();
+ UTEST_ASSERT(x == Point(0.6, -0.8));
+ }
+
+ return utest_end() ? 0 : 1;
+}
diff --git a/src/libnr/nr-types-test.h b/src/libnr/nr-types-test.h
new file mode 100644
index 000000000..d51db1be7
--- /dev/null
+++ b/src/libnr/nr-types-test.h
@@ -0,0 +1,145 @@
+// nr-types-test.h
+#include <cxxtest/TestSuite.h>
+
+#include "libnr/nr-types.h"
+#include "libnr/nr-point-fns.h"
+#include <cmath>
+using NR::Point;
+using NR::X;
+using NR::Y;
+
+class NrTypesTest : public CxxTest::TestSuite
+{
+public:
+ NrTypesTest() :
+ a( 1.5, 2.0 ),
+ b(-2.0, 3.0),
+ ab(-0.5, 5.0),
+ small(pow(2.0, -1070)),
+ small_left(-small, 0.0),
+ smallish_3_neg4(3.0 * small, -4.0 * small)
+ {}
+ virtual ~NrTypesTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static NrTypesTest *createSuite() { return new NrTypesTest(); }
+ static void destroySuite( NrTypesTest *suite ) { delete suite; }
+
+ NR::Point const a;
+ NR::Point const b;
+ NR::Point const ab;
+ double const small;
+ Point const small_left;
+ Point const smallish_3_neg4;
+
+
+ void testXYValues( void )
+ {
+ TS_ASSERT_EQUALS( X, 0 );
+ TS_ASSERT_EQUALS( Y, 1 );
+ }
+
+ void testXYCtorAndArrayConst(void)
+ {
+ TS_ASSERT_EQUALS( a[X], 1.5 );
+ TS_ASSERT_EQUALS( a[Y], 2.0 );
+ }
+
+ void testCopyCtor(void)
+ {
+ NR::Point a_copy(a);
+
+ TS_ASSERT_EQUALS( a, a_copy );
+ TS_ASSERT( !(a != a_copy) );
+ }
+
+ void testNonConstArrayOperator(void)
+ {
+ NR::Point a_copy(a);
+ a_copy[X] = -2.0;
+ TS_ASSERT_DIFFERS( a_copy, a );
+ TS_ASSERT_DIFFERS( a_copy, b );
+ a_copy[Y] = 3.0;
+ TS_ASSERT_EQUALS( a_copy, b );
+ }
+
+ void testBinaryPlusMinus(void)
+ {
+ TS_ASSERT_DIFFERS( a, b );
+ TS_ASSERT_EQUALS( a + b, ab );
+ TS_ASSERT_EQUALS( ab - a, b );
+ TS_ASSERT_EQUALS( ab - b, a );
+ TS_ASSERT_DIFFERS( ab + a, b );
+ }
+
+ void testUnaryMinus(void)
+ {
+ TS_ASSERT_EQUALS( -a, Point(-a[X], -a[Y]) );
+ }
+
+ void tetScaleDivide(void)
+ {
+ TS_ASSERT_EQUALS( -a, -1.0 * a );
+ TS_ASSERT_EQUALS( a + a + a, 3.0 * a );
+ TS_ASSERT_EQUALS( a / .5, 2.0 * a );
+ }
+
+ void testDot(void)
+ {
+ TS_ASSERT_EQUALS( dot(a, b), ( a[X] * b[X] +
+ a[Y] * b[Y] ) );
+ TS_ASSERT_EQUALS( dot(a, NR::rot90(a)), 0.0 );
+ TS_ASSERT_EQUALS( dot(-a, NR::rot90(a)), 0.0 );
+ }
+
+ void testL1L2LInftyNorms(void)
+ {
+ // TODO look at TS_ASSERT_DELTA
+
+ TS_ASSERT_EQUALS( L1(small_left), small );
+ TS_ASSERT_EQUALS( L2(small_left), small );
+ TS_ASSERT_EQUALS( LInfty(small_left), small );
+
+ TS_ASSERT_EQUALS( L1(smallish_3_neg4), 7.0 * small );
+ TS_ASSERT_EQUALS( L2(smallish_3_neg4), 5.0 * small );
+ TS_ASSERT_EQUALS( LInfty(smallish_3_neg4), 4.0 * small );
+ }
+
+ void testOperatorPlusEquals(void)
+ {
+ Point x(a);
+ x += b;
+ TS_ASSERT_EQUALS( x, ab );
+ }
+
+ void tetOperatorDivEquals(void)
+ {
+ Point x(a);
+ x /= .5;
+ TS_ASSERT_EQUALS( x, a + a );
+ }
+
+ void testNormalize(void)
+ {
+ Point x(small_left);
+ x.normalize();
+ TS_ASSERT_EQUALS( x, Point(-1.0, 0.0) );
+
+ x = smallish_3_neg4;
+ x.normalize();
+ TS_ASSERT_EQUALS( x, Point(0.6, -0.8) );
+ }
+
+};
+
+/*
+ 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/libnr/nr-types.cpp b/src/libnr/nr-types.cpp
new file mode 100644
index 000000000..98054a551
--- /dev/null
+++ b/src/libnr/nr-types.cpp
@@ -0,0 +1,68 @@
+/** \file
+ * Implements NR::Point::normalize()
+ */
+
+#include <libnr/nr-types.h>
+
+#include "isnan.h" //temporary fix for isnan()
+
+/** Scales this vector to make it a unit vector (within rounding error).
+ *
+ * The current version tries to handle infinite coordinates gracefully,
+ * but it's not clear that any callers need that.
+ *
+ * \pre *this != Point(0, 0).
+ * \pre Neither coordinate is NaN.
+ * \post L2(*this) very near 1.0.
+ */
+void NR::Point::normalize() {
+ double len = hypot(_pt[0], _pt[1]);
+ g_return_if_fail(len != 0);
+ g_return_if_fail(!isNaN(len));
+ static double const inf = 1e400;
+ if(len != inf) {
+ *this /= len;
+ } else {
+ unsigned n_inf_coords = 0;
+ /* Delay updating pt in case neither coord is infinite. */
+ NR::Point tmp;
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ if ( _pt[i] == inf ) {
+ ++n_inf_coords;
+ tmp[i] = 1.0;
+ } else if ( _pt[i] == -inf ) {
+ ++n_inf_coords;
+ tmp[i] = -1.0;
+ } else {
+ tmp[i] = 0.0;
+ }
+ }
+ switch (n_inf_coords) {
+ case 0:
+ /* Can happen if both coords are near +/-DBL_MAX. */
+ *this /= 4.0;
+ len = hypot(_pt[0], _pt[1]);
+ g_assert(len != inf);
+ *this /= len;
+ break;
+
+ case 1:
+ *this = tmp;
+ break;
+
+ case 2:
+ *this = sqrt(0.5) * tmp;
+ break;
+ }
+ }
+}
+/*
+ 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/libnr/nr-types.h b/src/libnr/nr-types.h
new file mode 100644
index 000000000..4802f5e0c
--- /dev/null
+++ b/src/libnr/nr-types.h
@@ -0,0 +1,47 @@
+#ifndef __NR_TYPES_H__
+#define __NR_TYPES_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Class-ifying NRPoint, Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This code is in public domain
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libnr/nr-coord.h>
+#include <libnr/nr-dim2.h>
+#include <libnr/nr-i-coord.h>
+#include <libnr/nr-matrix.h>
+#include <libnr/nr-point.h>
+#include <libnr/nr-point-l.h>
+#include <libnr/nr-point-ops.h>
+#include <libnr/nr-rect.h>
+#include <libnr/nr-rect-l.h>
+
+namespace NR {
+
+class Rect;
+class Matrix;
+
+} /* namespace NR */
+
+
+#endif /* !__NR_TYPES_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnr/nr-values.cpp b/src/libnr/nr-values.cpp
new file mode 100644
index 000000000..bb310cc49
--- /dev/null
+++ b/src/libnr/nr-values.cpp
@@ -0,0 +1,23 @@
+#define __NR_VALUES_C__
+
+#include <libnr/nr-rect-l.h>
+
+
+/*
+The following predefined objects are for reference
+and comparison.
+*/
+NRMatrix NR_MATRIX_IDENTITY =
+ {{1.0, 0.0, 0.0, 1.0, 0.0, 0.0}};
+NRRect NR_RECT_EMPTY =
+ {NR_HUGE, NR_HUGE, -NR_HUGE, -NR_HUGE};
+NRRectL NR_RECT_L_EMPTY =
+ {NR_HUGE_L, NR_HUGE_L, -NR_HUGE_L, -NR_HUGE_L};
+NRRectL NR_RECT_S_EMPTY =
+ {NR_HUGE_S, NR_HUGE_S, -NR_HUGE_S, -NR_HUGE_S};
+
+/** component_vectors[i] is like $e_i$ in common mathematical usage;
+ or equivalently $I_i$ (where $I$ is the identity matrix). */
+NR::Point const component_vectors[] = {NR::Point(1., 0.),
+ NR::Point(0., 1.)};
+
diff --git a/src/libnr/nr-values.h b/src/libnr/nr-values.h
new file mode 100644
index 000000000..7fa00d809
--- /dev/null
+++ b/src/libnr/nr-values.h
@@ -0,0 +1,45 @@
+#ifndef __NR_VALUES_H__
+#define __NR_VALUES_H__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#include <libnr/nr-forward.h>
+
+#define NR_EPSILON 1e-18
+
+#define NR_HUGE 1e18
+#define NR_HUGE_L (0x7fffffff)
+#define NR_HUGE_S (0x7fff)
+
+/*
+The following predefined objects are for reference
+and comparison. They are defined in nr-values.cpp
+*/
+extern NRMatrix NR_MATRIX_IDENTITY;
+extern NRRect NR_RECT_EMPTY;
+extern NRRectL NR_RECT_L_EMPTY;
+extern NRRectL NR_RECT_S_EMPTY;
+
+/** component_vectors[i] has 1.0 at position i, and 0.0 elsewhere
+ (i.e. in the other position). */
+extern NR::Point const component_vectors[2];
+
+#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/libnr/nr_config.h.mingw b/src/libnr/nr_config.h.mingw
new file mode 100644
index 000000000..6992cc6fc
--- /dev/null
+++ b/src/libnr/nr_config.h.mingw
@@ -0,0 +1,12 @@
+#define NR_SIZEOF_CHAR 1
+#define NR_SIZEOF_SHORT 2
+#define NR_SIZEOF_INT 4
+#define NR_SIZEOF_LONG 4
+
+typedef signed char NRByte;
+typedef unsigned char NRUByte;
+typedef signed short NRShort;
+typedef unsigned short NRUShort;
+typedef signed int NRLong;
+typedef unsigned long NRULong;
+
diff --git a/src/libnr/nr_config.h.win32 b/src/libnr/nr_config.h.win32
new file mode 100644
index 000000000..e0bfbda3f
--- /dev/null
+++ b/src/libnr/nr_config.h.win32
@@ -0,0 +1,14 @@
+#define NR_SIZEOF_CHAR 1
+#define NR_SIZEOF_SHORT 2
+#define NR_SIZEOF_INT 4
+#define NR_SIZEOF_LONG 4
+
+typedef signed char NRByte;
+typedef unsigned char NRUByte;
+typedef signed short NRShort;
+typedef unsigned short NRUShort;
+typedef signed int NRLong;
+typedef unsigned long NRULong;
+
+
+
diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S b/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S
new file mode 100644
index 000000000..db2cbec5a
--- /dev/null
+++ b/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S
@@ -0,0 +1,125 @@
+ .file "nr-compose.c"
+
+# Ensure Inkscape is execshield protected
+ .section .note.GNU-stack
+ .previous
+
+ .text
+ .align 2
+.globl nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP
+ .type nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP,@function
+
+/*
+ * This code is in public domain
+ *
+ * c 32(%ebp)
+ * srs 28(%ebp)
+ * spx 24(%ebp)
+ * rs 20(%ebp)
+ * h 16(%ebp)
+ * w 12(%ebp)
+ * px 8(%ebp)
+ * r -8(%ebp)
+ * g -12(%ebp)
+ * b -16(%ebp)
+ * a -20(%ebp)
+ * s -24(%ebp) -> %esi
+ * d -28(%ebp) -> %edi
+ * x -32(%ebp) -> %ebx
+ * y -36(%ebp)
+ * ca -40(%ebp)
+ *
+ * mm0 Fg
+ * mm1 FgA
+ * mm2 FgPre
+ * mm3
+ * mm4
+ * mm5
+ * mm6 128
+ * mm7 0
+ *
+*/
+
+nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP:
+ pushl %ebp
+ movl %esp, %ebp
+ pushl %ebx
+ subl $36, %esp
+ pushl %edi
+ pushl %esi
+
+/* Load %mm7 with [0 0 0 0] */
+ movl $0, %eax
+ movd %eax, %mm7
+
+/* Load %mm6 with [128 128 128 128] */
+ movl $0x80808080, %eax
+ movd %eax, %mm6
+ punpcklbw %mm7, %mm6
+
+/* FgC -> %mm0 */
+ movl 32(%ebp), %eax
+ movd (%eax), %mm0
+ punpcklbw %mm7, %mm0
+
+/* for (y = ...) */
+ movl 16(%ebp), %ecx
+.fory:
+
+/* d = px */
+/* s = spx */
+ movl 8(%ebp), %edi
+ movl 24(%ebp), %esi
+
+/* for (x = ...) */
+ movl 12(%ebp), %ebx
+.forx:
+
+/* [m m m m] -> %mm1 */
+ movzbl (%esi), %eax
+ testb $0xff, %al
+ jz .clip
+ movd %eax, %mm1
+ punpcklwd %mm1, %mm1
+ punpckldq %mm1, %mm1
+
+/* Fg -> mm2 */
+ movq %mm0, %mm2
+ pmullw %mm1, %mm2
+ paddw %mm6, %mm2
+ movq %mm2, %mm3
+ psrlw $8, %mm3
+ paddw %mm3, %mm2
+ psrlw $8, %mm2
+
+/* Store pixel */
+ packuswb %mm2, %mm2
+ movd %mm2, (%edi)
+
+.clip:
+ addl $4, %edi
+ incl %esi
+
+ decl %ebx
+ jnz .forx
+
+ movl 20(%ebp), %eax
+ addl %eax, 8(%ebp)
+ movl 28(%ebp), %eax
+ addl %eax, 24(%ebp)
+
+ decl %ecx
+ jnz .fory
+
+.exit:
+ emms
+ popl %esi
+ popl %edi
+ addl $36, %esp
+ popl %ebx
+ popl %ebp
+ ret
+
+.Lfe1:
+ .size nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP,.Lfe1-nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP
+ .ident "GCC: (GNU) 3.2"
diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S
new file mode 100644
index 000000000..fe1d9be57
--- /dev/null
+++ b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S
@@ -0,0 +1,231 @@
+ .file "nr-compose.c"
+
+# Ensure Inkscape is execshield protected
+ .section .note.GNU-stack
+ .previous
+
+ .text
+ .align 2
+.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP
+ .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP,@function
+
+/*
+ * This code is in public domain
+ *
+ * c 32(%ebp)
+ * srs 28(%ebp)
+ * spx 24(%ebp)
+ * rs 20(%ebp)
+ * h 16(%ebp)
+ * w 12(%ebp)
+ * px 8(%ebp)
+ * r -8(%ebp)
+ * g -12(%ebp)
+ * b -16(%ebp)
+ * a -20(%ebp)
+ * s -24(%ebp) -> %esi
+ * d -28(%ebp) -> %edi
+ * x -32(%ebp) -> %ebx
+ * y -36(%ebp)
+ * ca -40(%ebp)
+ *
+ * mm0 Fg
+ * mm1 MMMM
+ * mm2 FgM
+ * mm3
+ * mm4
+ * mm5 255
+ * mm6 128
+ * mm7 0
+ *
+*/
+
+nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP:
+ pushl %ebp
+ movl %esp, %ebp
+ pushl %ebx
+ subl $36, %esp
+ pushl %edi
+ pushl %esi
+
+/* Load %mm7 with [0 0 0 0] */
+ movl $0, %eax
+ movd %eax, %mm7
+
+/* Load %mm6 with [128 128 128 128] */
+ movl $0x80808080, %eax
+ movd %eax, %mm6
+ punpcklbw %mm7, %mm6
+
+/* Load %mm5 with [255 255 255 255] */
+ movl $0xffffffff, %eax
+ movd %eax, %mm5
+ punpcklbw %mm7, %mm5
+
+/* FgC -> %mm0 */
+ movl 32(%ebp), %eax
+ movd (%eax), %mm0
+ punpcklbw %mm7, %mm0
+
+/* Check full opacity */
+ cmpb $0xff, %al
+ jz .opaque
+
+/* for (y = ...) */
+ movl 16(%ebp), %ecx
+.fory:
+
+/* d = px */
+/* s = spx */
+ movl 8(%ebp), %edi
+ movl 24(%ebp), %esi
+
+/* for (x = ...) */
+ movl 12(%ebp), %ebx
+.forx:
+
+/* [m m m m] -> %mm1 */
+ movzbl (%esi), %eax
+ testb $0xff, %al
+ jz .clip
+ movd %eax, %mm1
+ punpcklwd %mm1, %mm1
+ punpckldq %mm1, %mm1
+
+/* Fg -> mm2 */
+ movq %mm0, %mm2
+ pmullw %mm1, %mm2
+ paddw %mm6, %mm2
+ movq %mm2, %mm3
+ psrlw $8, %mm3
+ paddw %mm3, %mm2
+ psrlw $8, %mm2
+
+/* [255 - FgA] -> mm1 */
+ movq %mm2, %mm1
+ punpckhwd %mm1, %mm1
+ punpckhdq %mm1, %mm1
+ pxor %mm5, %mm1
+
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+ pmullw %mm1, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm2, %mm3
+
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, (%edi)
+
+.clip:
+ addl $4, %edi
+ incl %esi
+
+ decl %ebx
+ jnz .forx
+
+ movl 20(%ebp), %eax
+ addl %eax, 8(%ebp)
+ movl 28(%ebp), %eax
+ addl %eax, 24(%ebp)
+
+ decl %ecx
+ jnz .fory
+
+.exit:
+ emms
+ popl %esi
+ popl %edi
+ addl $36, %esp
+ popl %ebx
+ popl %ebp
+ ret
+
+.opaque:
+/* for (y = ...) */
+ movl 16(%ebp), %ecx
+.o_fory:
+
+/* d = px */
+/* s = spx */
+ movl 8(%ebp), %edi
+ movl 24(%ebp), %esi
+
+/* for (x = ...) */
+ movl 12(%ebp), %ebx
+.o_forx:
+
+/* [m m m m] -> %mm1 */
+ movzbl (%esi), %eax
+ testb $0xff, %al
+ jz .o_clip
+ cmpb $0xff, %al
+ jz .o_full
+ movd %eax, %mm1
+ punpcklwd %mm1, %mm1
+ punpckldq %mm1, %mm1
+
+/* Fg -> mm2 */
+ movq %mm0, %mm2
+ pmullw %mm1, %mm2
+ paddw %mm6, %mm2
+ movq %mm2, %mm3
+ psrlw $8, %mm3
+ paddw %mm3, %mm2
+ psrlw $8, %mm2
+
+/* [255 - FgA] -> mm1 */
+ movq %mm2, %mm1
+ punpckhwd %mm1, %mm1
+ punpckhdq %mm1, %mm1
+ pxor %mm5, %mm1
+
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+ pmullw %mm1, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm2, %mm3
+
+ jmp .o_store
+
+.o_full:
+ movq %mm0, %mm3
+
+.o_store:
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, (%edi)
+
+.o_clip:
+ addl $4, %edi
+ incl %esi
+
+ decl %ebx
+ jnz .o_forx
+
+ movl 20(%ebp), %eax
+ addl %eax, 8(%ebp)
+ movl 28(%ebp), %eax
+ addl %eax, 24(%ebp)
+
+ decl %ecx
+ jnz .o_fory
+ jmp .exit
+
+.Lfe1:
+ .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP,.Lfe1-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP
+ .ident "GCC: (GNU) 3.2"
diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S
new file mode 100644
index 000000000..e30056af2
--- /dev/null
+++ b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S
@@ -0,0 +1,414 @@
+ .file "nr-compose-transform.c"
+
+# Ensure Inkscape is execshield protected
+ .section .note.GNU-stack
+ .previous
+
+ .text
+ .align 2
+.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0
+ .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,@function
+
+/*
+ * This code is in public domain
+ *
+ */
+
+nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0:
+ pushl %ebp
+ movl %esp, %ebp
+ pushl %ebx
+ subl $48, %esp
+ pushl %edi
+ pushl %esi
+
+/* Load %mm7 with [0 0 0 0] */
+ movl $0, %eax
+ movd %eax, %mm7
+
+/* Load %mm6 with [128 128 128 128] */
+ movl $0x80808080, %eax
+ movd %eax, %mm6
+ punpcklbw %mm7, %mm6
+
+/* Load %mm5 with [255 255 255 255] */
+ movl $0xffffffff, %eax
+ movd %eax, %mm5
+ punpcklbw %mm7, %mm5
+
+/* Load %mm0 with [a a a a] */
+ movzbl 44(%ebp), %eax
+ movd %eax, %mm0
+ punpcklwd %mm0, %mm0
+ punpckldq %mm0, %mm0
+
+ movl 8(%ebp), %eax
+ movl %eax, -8(%ebp)
+ movl 40(%ebp), %eax
+ addl $16, %eax
+ movl (%eax), %eax
+ movl %eax, -12(%ebp)
+ movl 40(%ebp), %eax
+ addl $20, %eax
+ movl (%eax), %eax
+ movl %eax, -16(%ebp)
+ movl $0, -24(%ebp)
+.L29:
+ movl -24(%ebp), %eax
+ cmpl 16(%ebp), %eax
+ jl .L32
+ jmp .L28
+.L32:
+ movl -8(%ebp), %edi
+
+ movl -12(%ebp), %eax
+ movl %eax, %esi
+ movl -16(%ebp), %eax
+ movl %eax, -36(%ebp)
+
+ movl 12(%ebp), %ebx
+.for_x_0:
+
+ movl %esi, %ecx
+ cmpl $0, %ecx
+ js .clip_0
+ sarl $12, %ecx
+ cmpl 28(%ebp), %ecx
+ jge .clip_0
+ shll $2, %ecx
+
+ movl -36(%ebp), %eax
+ cmpl $0, %eax
+ js .clip_0
+ sarl $12, %eax
+ cmpl 32(%ebp), %eax
+ jge .clip_0
+ imull 36(%ebp), %eax
+
+ addl %ecx, %eax
+ addl 24(%ebp), %eax
+
+/* Fg -> %mm1 */
+ movl (%eax), %eax
+ testl $0xff000000, %eax
+ jz .clip_0
+ movd %eax, %mm1
+ punpcklbw %mm7, %mm1
+
+/* [a a a 255] -> %mm3 */
+ shrl $24, %eax
+ movl $0x10101, %edx
+ mull %edx
+ orl $0xff000000, %eax
+ movd %eax, %mm3
+ punpcklbw %mm7, %mm3
+
+/* [Fg * a] -> mm1 */
+ pmullw %mm3, %mm1
+ paddw %mm6, %mm1
+ movq %mm1, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm1
+ psrlw $8, %mm1
+
+/* Multiply by alpha */
+ pmullw %mm0, %mm1
+ paddw %mm6, %mm1
+ movq %mm1, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm1
+ psrlw $8, %mm1
+
+/* [255 - FgA] -> mm2 */
+ movq %mm1, %mm2
+ punpckhwd %mm2, %mm2
+ punpckhdq %mm2, %mm2
+ pxor %mm5, %mm2
+
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+
+ pmullw %mm2, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm1, %mm3
+
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, (%edi)
+
+.clip_0:
+.L37:
+ movl 40(%ebp), %ecx
+ movl (%ecx), %edx
+ addl %edx, %esi
+ movl 4(%ecx), %edx
+ addl %edx, -36(%ebp)
+
+ addl $4, %edi
+
+ decl %ebx
+ jnz .for_x_0
+
+.L34:
+ movl 8(%ecx), %edx
+ addl %edx, -12(%ebp)
+ movl 12(%ecx), %edx
+ addl %edx, -16(%ebp)
+
+ movl 20(%ebp), %edx
+ leal -8(%ebp), %eax
+ addl %edx, (%eax)
+ leal -24(%ebp), %eax
+ incl (%eax)
+ jmp .L29
+.L28:
+ emms
+ popl %esi
+ popl %edi
+ addl $48, %esp
+ popl %ebx
+ popl %ebp
+ ret
+.Lfe2:
+ .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,.Lfe2-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0
+
+/*
+ *
+ * dbits 52(%ebp)
+ * alpha 48(%ebp)
+ * FF_S 44(%ebp)
+ *
+ * d -32(%ebp) -> %edi
+ * i -60(%ebp) -> %esi
+ * sx -64(%ebp) -> %ebx
+ * sy -68(%ebp)
+ * s -72(%ebp)
+ *
+ * %mm0 a a a a
+ * %mm1 FgA
+ * %mm2 SumFgA
+ * %mm3 a a a 255
+ * %mm4
+*/
+
+ .align 2
+.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n
+ .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,@function
+nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n:
+ pushl %ebp
+ movl %esp, %ebp
+ pushl %ebx
+ subl $72, %esp
+ pushl %edi
+ pushl %esi
+
+/* Load %mm7 with [0 0 0 0] */
+ movl $0, %eax
+ movd %eax, %mm7
+
+/* Load %mm6 with [128 128 128 128] */
+ movl $0x80808080, %eax
+ movd %eax, %mm6
+ punpcklbw %mm7, %mm6
+
+/* Load %mm5 with [255 255 255 255] */
+ movl $0xffffffff, %eax
+ movd %eax, %mm5
+ punpcklbw %mm7, %mm5
+
+/* Load %mm0 with [a a a a] */
+ movzbl 48(%ebp), %eax
+ movd %eax, %mm0
+ punpcklwd %mm0, %mm0
+ punpckldq %mm0, %mm0
+
+ movl $1, %eax
+ movzbl 52(%ebp), %ecx
+ sall %cl, %eax
+ movl %eax, -8(%ebp)
+ movl 8(%ebp), %eax
+ movl %eax, -12(%ebp)
+ movl 40(%ebp), %eax
+ addl $16, %eax
+ movl (%eax), %eax
+ movl %eax, -16(%ebp)
+ movl 40(%ebp), %eax
+ addl $20, %eax
+ movl (%eax), %eax
+ movl %eax, -20(%ebp)
+ movl $0, -28(%ebp)
+.L44:
+ movl -28(%ebp), %eax
+ cmpl 16(%ebp), %eax
+ jl .L47
+ jmp .exit_n
+.L47:
+ movl -12(%ebp), %eax
+ movl %eax, -32(%ebp)
+ movl -16(%ebp), %eax
+ movl %eax, -36(%ebp)
+ movl -20(%ebp), %eax
+ movl %eax, -40(%ebp)
+ movl $0, -24(%ebp)
+.L48:
+ movl -24(%ebp), %eax
+ cmpl 12(%ebp), %eax
+ jl .L51
+ jmp .L49
+.L51:
+
+/* Zero accumulator */
+ movq %mm7, %mm2
+
+/* Set i to dptr (size - 1) */
+ movl -8(%ebp), %esi
+ sub $1, %esi
+ shll $3, %esi
+
+ movl 44(%ebp), %edi
+ movl -36(%ebp), %ecx
+
+.for_i_n:
+ movl (%edi,%esi), %ebx
+ addl %ecx, %ebx
+/* Test negative before shift */
+ cmpl $0, %ebx
+ js .next_i_n
+ sarl $12, %ebx
+ cmpl 28(%ebp), %ebx
+ jge .next_i_n
+/* We multiply sx by 4 here */
+ shll $2, %ebx
+
+ movl 4(%edi,%esi), %eax
+ addl -40(%ebp), %eax
+/* Test negative before shift */
+ cmpl $0, %eax
+ js .next_i_n
+ sarl $12, %eax
+ cmpl 32(%ebp), %eax
+ jge .next_i_n
+/* We multiply sy by srs here */
+ imull 36(%ebp), %eax
+
+ addl %ebx, %eax
+ addl 24(%ebp), %eax
+
+/* Fg -> %mm1 */
+ movl (%eax), %eax
+ testl $0xff000000, %eax
+ jz .next_i_n
+ movd %eax, %mm1
+ punpcklbw %mm7, %mm1
+
+/* [a a a 255] -> %mm3 */
+ shrl $24, %eax
+ movl $0x10101, %edx
+ mull %edx
+ orl $0xff000000, %eax
+ movd %eax, %mm3
+ punpcklbw %mm7, %mm3
+
+/* [Fg * a] -> mm1 */
+ pmullw %mm3, %mm1
+ paddw %mm6, %mm1
+ movq %mm1, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm1
+ psrlw $8, %mm1
+
+/* Add to accumulator */
+ paddw %mm1, %mm2
+
+.next_i_n:
+ subl $8, %esi
+ jnb .for_i_n
+
+/* Divide components by sample size */
+ movd 52(%ebp), %mm3
+ psrlw %mm3, %mm2
+
+/* Multiply by alpha */
+ pmullw %mm0, %mm2
+ paddw %mm6, %mm2
+ movq %mm2, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm2
+ psrlw $8, %mm2
+
+/* [255 - FgA] -> mm1 */
+ movq %mm2, %mm1
+ punpckhwd %mm1, %mm1
+ punpckhdq %mm1, %mm1
+ pxor %mm5, %mm1
+
+ movl -32(%ebp), %edi
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+
+ pmullw %mm1, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm2, %mm3
+
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, (%edi)
+
+.L58:
+ movl 40(%ebp), %eax
+ movl (%eax), %edx
+ leal -36(%ebp), %eax
+ addl %edx, (%eax)
+ movl 40(%ebp), %eax
+ addl $4, %eax
+ movl (%eax), %edx
+ leal -40(%ebp), %eax
+ addl %edx, (%eax)
+ leal -32(%ebp), %eax
+ addl $4, (%eax)
+ leal -24(%ebp), %eax
+ incl (%eax)
+ jmp .L48
+.L49:
+ movl 40(%ebp), %eax
+ addl $8, %eax
+ movl (%eax), %edx
+ leal -16(%ebp), %eax
+ addl %edx, (%eax)
+ movl 40(%ebp), %eax
+ addl $12, %eax
+ movl (%eax), %edx
+ leal -20(%ebp), %eax
+ addl %edx, (%eax)
+ movl 20(%ebp), %edx
+ leal -12(%ebp), %eax
+ addl %edx, (%eax)
+ leal -28(%ebp), %eax
+ incl (%eax)
+ jmp .L44
+
+.exit_n:
+ emms
+ popl %esi
+ popl %edi
+ addl $72, %esp
+ popl %ebx
+ popl %ebp
+ ret
+.Lfe3:
+ .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,.Lfe3-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n
+ .ident "GCC: (GNU) 3.2"
diff --git a/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S b/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S
new file mode 100644
index 000000000..37261e572
--- /dev/null
+++ b/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S
@@ -0,0 +1,227 @@
+ .file "nr-compose.c"
+
+# Ensure Inkscape is execshield protected
+ .section .note.GNU-stack
+ .previous
+
+ .text
+ .align 2
+.globl nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P
+ .type nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P,@function
+
+/*
+ * This code is in public domain
+ *
+ * alpha 32(%ebp)
+ * srs 28(%ebp)
+ * spx 24(%ebp)
+ * rs 20(%ebp)
+ * h 16(%ebp)
+ * w 12(%ebp)
+ * px 8(%ebp)
+ * r -8(%ebp)
+ * g -12(%ebp)
+ * b -16(%ebp)
+ * a -20(%ebp)
+ * s -24(%ebp) -> %esi
+ * d -28(%ebp) -> %edi
+ * x -32(%ebp) -> %ebx
+ * y -36(%ebp)
+ * ca -40(%ebp)
+ *
+ * mm0 A
+ * mm1 FgA
+ * mm2 FgPre
+ * mm3
+ * mm4
+ * mm5 255
+ * mm6 128
+ * mm7 0
+ *
+*/
+
+nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P:
+ pushl %ebp
+ movl %esp, %ebp
+ pushl %ebx
+ subl $36, %esp
+ pushl %edi
+ pushl %esi
+
+/* Load %mm7 with [0 0 0 0] */
+ movl $0, %eax
+ movd %eax, %mm7
+
+/* Load %mm6 with [128 128 128 128] */
+ movl $0x80808080, %eax
+ movd %eax, %mm6
+ punpcklbw %mm7, %mm6
+
+/* Load %mm5 with [255 255 255 255] */
+ movl $0xffffffff, %eax
+ movd %eax, %mm5
+ punpcklbw %mm7, %mm5
+
+/* Load %mm0 with [a a a a] */
+/* Check full opacity */
+ movzbl 32(%ebp), %eax
+ cmpb $0xff, %al
+ jz .opaque
+ movd %eax, %mm0
+ punpcklwd %mm0, %mm0
+ punpckldq %mm0, %mm0
+
+/* for (y = ...) */
+ movl 16(%ebp), %ecx
+.fory:
+
+/* d = px */
+/* s = spx */
+ movl 8(%ebp), %edi
+ movl 24(%ebp), %esi
+
+/* for (x = ...) */
+ movl 12(%ebp), %ebx
+.forx:
+
+/* Fg -> %mm1 */
+/* fixme: Do we have to bother about alignment here? (Lauris) */
+ movl (%esi), %eax
+ testl $0xff000000, %eax
+ jz .clip
+ movd %eax, %mm1
+ punpcklbw %mm7, %mm1
+
+/* [Fg * a] -> mm1 */
+ pmullw %mm0, %mm1
+ paddw %mm6, %mm1
+ movq %mm1, %mm2
+ psrlw $8, %mm2
+ paddw %mm2, %mm1
+ psrlw $8, %mm1
+
+/* [255 - FgA] -> mm2 */
+ movq %mm1, %mm2
+ punpckhwd %mm2, %mm2
+ punpckhdq %mm2, %mm2
+ pxor %mm5, %mm2
+
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+ pmullw %mm2, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm1, %mm3
+
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, %eax
+ movb %al, 0(%edi)
+ shrl $8, %eax
+ movb %al, 1(%edi)
+ shrl $8, %eax
+ movb %al, 2(%edi)
+
+.clip:
+ addl $3, %edi
+ addl $4, %esi
+
+ decl %ebx
+ jnz .forx
+
+ movl 20(%ebp), %eax
+ addl %eax, 8(%ebp)
+ movl 28(%ebp), %eax
+ addl %eax, 24(%ebp)
+
+ decl %ecx
+ jnz .fory
+
+.exit:
+ emms
+ popl %esi
+ popl %edi
+ addl $36, %esp
+ popl %ebx
+ popl %ebp
+ ret
+
+.opaque:
+/* for (y = ...) */
+ movl 16(%ebp), %ecx
+.o_fory:
+
+/* d = px */
+/* s = spx */
+ movl 8(%ebp), %edi
+ movl 24(%ebp), %esi
+
+/* for (x = ...) */
+ movl 12(%ebp), %ebx
+.o_forx:
+
+/* Fg -> %mm1 */
+/* fixme: Do we have to bother about alignment here? (Lauris) */
+ movl (%esi), %eax
+ testl $0xff000000, %eax
+ jz .o_clip
+ cmpl $0xff000000, %eax
+ jnb .o_store
+ movd %eax, %mm1
+ punpcklbw %mm7, %mm1
+
+/* [255 - FgA] -> mm2 */
+ movq %mm1, %mm2
+ punpckhwd %mm2, %mm2
+ punpckhdq %mm2, %mm2
+ pxor %mm5, %mm2
+
+/* Bg -> mm3 */
+ movd (%edi), %mm3
+ punpcklbw %mm7, %mm3
+
+/* Fg + ((255 - FgA) * Bg) / 255 */
+ pmullw %mm2, %mm3
+ paddw %mm6, %mm3
+ movq %mm3, %mm4
+ psrlw $8, %mm4
+ paddw %mm4, %mm3
+ psrlw $8, %mm3
+ paddw %mm1, %mm3
+
+/* Store pixel */
+ packuswb %mm3, %mm3
+ movd %mm3, %eax
+.o_store:
+ movb %al, 0(%edi)
+ shrl $8, %eax
+ movb %al, 1(%edi)
+ shrl $8, %eax
+ movb %al, 2(%edi)
+
+.o_clip:
+ addl $3, %edi
+ addl $4, %esi
+
+ decl %ebx
+ jnz .o_forx
+
+ movl 20(%ebp), %eax
+ addl %eax, 8(%ebp)
+ movl 28(%ebp), %eax
+ addl %eax, 24(%ebp)
+
+ decl %ecx
+ jnz .o_fory
+
+ jmp .exit
+
+.Lfe1:
+ .size nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P,.Lfe1-nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P
+ .ident "GCC: (GNU) 3.2"
diff --git a/src/libnr/testnr.cpp b/src/libnr/testnr.cpp
new file mode 100644
index 000000000..12dce4c52
--- /dev/null
+++ b/src/libnr/testnr.cpp
@@ -0,0 +1,92 @@
+#define __TESTNR_C__
+
+/*
+ * Pixel buffer rendering library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+#if defined (_WIN32) || defined (__WIN32__)
+# include <windows.h>
+#include <glib.h>
+#endif
+
+
+#include "nr-blit.h"
+
+static double
+get_time (void)
+{
+ GTimeVal tv;
+ g_get_current_time (&tv);
+ return tv.tv_sec + 1e-6 * tv.tv_usec;
+}
+
+static unsigned int
+rand_byte (void)
+{
+ return (int) (256.0 * rand () / (RAND_MAX + 1.0));
+}
+
+int
+main (int argc, const char **argv)
+{
+ double start, end;
+ NRPixBlock d, m[16];
+ int count, i;
+
+ srand (time (NULL));
+
+ printf ("Initializing buffers\n");
+
+ /* Destination */
+ nr_pixblock_setup_fast (&d, NR_PIXBLOCK_MODE_R8G8B8A8P, 0, 0, 64, 64, 1);
+ d.empty = 0;
+
+ /* Masks */
+ for (i = 0; i < 16; i++) {
+ int r, b, c;
+ nr_pixblock_setup_fast (&m[i], NR_PIXBLOCK_MODE_A8, 0, 0, 64, 64, 0);
+ for (r = 0; r < 64; r++) {
+ unsigned int q;
+ unsigned char *p;
+ p = NR_PIXBLOCK_PX (&m[i]) + r * m[i].rs;
+ for (b = 0; b < 8; b++) {
+ q = rand_byte ();
+ if (q < 120) {
+ for (c = 0; c < 8; c++) *p++ = 0;
+ } else if (q < 240) {
+ for (c = 0; c < 8; c++) *p++ = 255;
+ } else {
+ for (c = 0; c < 8; c++) *p++ = rand_byte ();
+ }
+ }
+ }
+ m[i].empty = 0;
+ }
+
+ printf ("Random transparency\n");
+ count = 0;
+ start = end = get_time ();
+ while ((end - start) < 5.0) {
+ unsigned char r, g, b, a;
+ r = rand_byte ();
+ g = rand_byte ();
+ b = rand_byte ();
+ a = rand_byte ();
+
+ for (i = 0; i < 16; i++) {
+ nr_blit_pixblock_mask_rgba32 (&d, &m[i], (a << 24) | (g << 16) | (b << 8) | a);
+ count += 1;
+ }
+ end = get_time ();
+ }
+ printf ("Did %d [64x64] random buffers in %f sec\n", count, end - start); // localizing ok
+ printf ("%f buffers per second\n", count / (end - start)); // localizing ok
+ printf ("%f pixels per second\n", count * (64 * 64) / (end - start)); // localizing ok
+
+ return 0;
+}
diff --git a/src/libnrtype/.cvsignore b/src/libnrtype/.cvsignore
new file mode 100644
index 000000000..e8014d011
--- /dev/null
+++ b/src/libnrtype/.cvsignore
@@ -0,0 +1,5 @@
+Makefile
+Makefile.in
+.deps
+makefile
+.dirstamp
diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp
new file mode 100644
index 000000000..5fc7e0b3b
--- /dev/null
+++ b/src/libnrtype/FontFactory.cpp
@@ -0,0 +1,630 @@
+/*
+ * FontFactory.cpp
+ * testICU
+ *
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ */
+
+#include "FontFactory.h"
+#include <libnrtype/font-instance.h>
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glibmm/i18n.h> // _()
+
+/* Freetype2 */
+# include <pango/pangoft2.h>
+
+
+// need to avoid using the size field
+size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
+ int h = 0;
+ h *= 1128467;
+ char const *theF = pango_font_description_get_family(x);
+ h += (theF)?g_str_hash(theF):0;
+ h *= 1128467;
+ h += (int)pango_font_description_get_style(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_variant(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_weight(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_stretch(x);
+ return h;
+}
+bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) {
+ //if ( pango_font_description_equal(a,b) ) return true;
+ char const *fa = pango_font_description_get_family(a);
+ char const *fb = pango_font_description_get_family(b);
+ if ( ( fa && fb == NULL ) || ( fb && fa == NULL ) ) return false;
+ if ( fa && fb && strcmp(fa,fb) != 0 ) return false;
+ if ( pango_font_description_get_style(a) != pango_font_description_get_style(b) ) return false;
+ if ( pango_font_description_get_variant(a) != pango_font_description_get_variant(b) ) return false;
+ if ( pango_font_description_get_weight(a) != pango_font_description_get_weight(b) ) return false;
+ if ( pango_font_description_get_stretch(a) != pango_font_description_get_stretch(b) ) return false;
+ return true;
+}
+
+/////////////////// helper functions
+
+/**
+ * A wrapper for strcasestr that also provides an implementation for Win32.
+ */
+static bool
+ink_strstr(char const *haystack, char const *pneedle)
+{
+ // windows has no strcasestr implementation, so here is ours...
+ // stolen from nmap
+ /* FIXME: This is broken for e.g. ink_strstr("aab", "ab"). Report to nmap.
+ *
+ * Also, suggest use of g_ascii_todown instead of buffer stuff, and g_ascii_tolower instead
+ * of tolower. Given that haystack is a font name (i.e. fairly short), it should be ok to
+ * do g_ascii_strdown on both haystack and pneedle, and do normal strstr.
+ *
+ * Rather than fixing in inkscape, consider getting rid of this routine, instead using
+ * strdown and plain strstr at caller. We have control over the needle values, so we can
+ * modify the callers rather than calling strdown there.
+ */
+ char buf[512];
+ register char const *p;
+ char *needle, *q, *foundto;
+ if (!*pneedle) return true;
+ if (!haystack) return false;
+
+ needle = buf;
+ p = pneedle; q = needle;
+ while ((*q++ = tolower(*p++)))
+ ;
+ p = haystack - 1; foundto = needle;
+ while (*++p) {
+ if (tolower(*p) == *foundto) {
+ if (!*++foundto) {
+ /* Yeah, we found it */
+ return true;
+ }
+ } else foundto = needle;
+ }
+ return false;
+}
+
+/**
+ * Regular fonts are 'Regular', 'Roman', 'Normal', or 'Plain'
+ */
+// FIXME: make this UTF8, add non-English style names
+static bool
+is_regular(char const *s)
+{
+ if (ink_strstr(s, "Regular")) return true;
+ if (ink_strstr(s, "Roman")) return true;
+ if (ink_strstr(s, "Normal")) return true;
+ if (ink_strstr(s, "Plain")) return true;
+ return false;
+}
+
+/**
+ * Non-bold fonts are 'Medium' or 'Book'
+ */
+static bool
+is_nonbold(char const *s)
+{
+ if (ink_strstr(s, "Medium")) return true;
+ if (ink_strstr(s, "Book")) return true;
+ return false;
+}
+
+/**
+ * Italic fonts are 'Italic', 'Oblique', or 'Slanted'
+ */
+static bool
+is_italic(char const *s)
+{
+ if (ink_strstr(s, "Italic")) return true;
+ if (ink_strstr(s, "Oblique")) return true;
+ if (ink_strstr(s, "Slanted")) return true;
+ return false;
+}
+
+/**
+ * Bold fonts are 'Bold'
+ */
+static bool
+is_bold(char const *s)
+{
+ if (ink_strstr(s, "Bold")) return true;
+ return false;
+}
+
+/**
+ * Caps fonts are 'Caps'
+ */
+static bool
+is_caps(char const *s)
+{
+ if (ink_strstr(s, "Caps")) return true;
+ return false;
+}
+
+#if 0 /* FIXME: These are all unused. Please delete them or use them (presumably in
+* style_name_compare). */
+/**
+ * Monospaced fonts are 'Mono'
+ */
+static bool
+is_mono(char const *s)
+{
+ if (ink_strstr(s, "Mono")) return true;
+ return false;
+}
+
+/**
+ * Rounded fonts are 'Round'
+ */
+static bool
+is_round(char const *s)
+{
+ if (ink_strstr(s, "Round")) return true;
+ return false;
+}
+
+/**
+ * Outline fonts are 'Outline'
+ */
+static bool
+is_outline(char const *s)
+{
+ if (ink_strstr(s, "Outline")) return true;
+ return false;
+}
+
+/**
+ * Swash fonts are 'Swash'
+ */
+static bool
+is_swash(char const *s)
+{
+ if (ink_strstr(s, "Swash")) return true;
+ return false;
+}
+#endif
+
+/**
+ * Determines if two style names match. This allows us to match
+ * based on the type of style rather than simply doing string matching,
+ * because for instance 'Plain' and 'Normal' mean the same thing.
+ *
+ * Q: Shouldn't this include the other tests such as is_outline, etc.?
+ * Q: Is there a problem with strcasecmp on Win32? Should it use stricmp?
+ */
+static int
+style_name_compare(void const *aa, void const *bb)
+{
+ char const *a = (char const *) aa;
+ char const *b = (char const *) bb;
+
+ if (is_regular(a) && !is_regular(b)) return -1;
+ if (is_regular(b) && !is_regular(a)) return 1;
+
+ if (is_bold(a) && !is_bold(b)) return 1;
+ if (is_bold(b) && !is_bold(a)) return -1;
+
+ if (is_italic(a) && !is_italic(b)) return 1;
+ if (is_italic(b) && !is_italic(a)) return -1;
+
+ if (is_nonbold(a) && !is_nonbold(b)) return 1;
+ if (is_nonbold(b) && !is_nonbold(a)) return -1;
+
+ if (is_caps(a) && !is_caps(b)) return 1;
+ if (is_caps(b) && !is_caps(a)) return -1;
+
+ return strcasecmp(a, b);
+}
+
+static int
+style_record_compare(void const *aa, void const *bb)
+{
+ NRStyleRecord const *a = (NRStyleRecord const *) aa;
+ NRStyleRecord const *b = (NRStyleRecord const *) bb;
+
+ return (style_name_compare(a->name, b->name));
+}
+
+static void font_factory_name_list_destructor(NRNameList *list)
+{
+ for (unsigned int i = 0; i < list->length; i++)
+ free(list->names[i]);
+ if ( list->names ) nr_free(list->names);
+}
+
+static void font_factory_style_list_destructor(NRStyleList *list)
+{
+ for (unsigned int i = 0; i < list->length; i++) {
+ free((void *) (list->records)[i].name);
+ free((void *) (list->records)[i].descr);
+ }
+ if ( list->records ) nr_free(list->records);
+}
+
+/**
+ * On Win32 performs a stricmp(a,b), otherwise does a strcasecmp(a,b)
+ */
+static int
+family_name_compare(void const *a, void const *b)
+{
+#ifndef WIN32
+ return strcasecmp((*((char const **) a)), (*((char const **) b)));
+#else
+ return stricmp((*((char const **) a)), (*((char const **) b)));
+#endif
+}
+
+void noop(...) {}
+//#define PANGO_DEBUG g_print
+#define PANGO_DEBUG noop
+
+
+
+///////////////////// FontFactory
+#ifndef USE_PANGO_WIN32
+// the substitute function to tell fontconfig to enforce outline fonts
+void FactorySubstituteFunc(FcPattern *pattern,gpointer /*data*/)
+{
+ FcPatternAddBool(pattern, "FC_OUTLINE",FcTrue);
+ //char *fam = NULL;
+ //FcPatternGetString(pattern, "FC_FAMILY",0, &fam);
+ //printf("subst_f on %s\n",fam);
+}
+#endif
+
+
+font_factory *font_factory::lUsine = NULL;
+
+font_factory *font_factory::Default(void)
+{
+ if ( lUsine == NULL ) lUsine = new font_factory;
+ return lUsine;
+}
+
+font_factory::font_factory(void)
+{
+ fontSize = 512;
+ nbEnt = 0;
+ maxEnt = 32;
+ ents = (font_entry*)malloc(maxEnt*sizeof(font_entry));
+
+#ifdef USE_PANGO_WIN32
+ hScreenDC = pango_win32_get_dc();
+ fontServer = pango_win32_font_map_for_display();
+ fontContext = pango_win32_get_context();
+ pangoFontCache = pango_win32_font_map_get_font_cache(fontServer);
+#else
+ fontServer = pango_ft2_font_map_new();
+ pango_ft2_font_map_set_resolution((PangoFT2FontMap*)fontServer, 72, 72);
+ fontContext = pango_ft2_font_map_create_context((PangoFT2FontMap*)fontServer);
+ pango_ft2_font_map_set_default_substitute((PangoFT2FontMap*)fontServer,FactorySubstituteFunc,this,NULL);
+#endif
+}
+
+font_factory::~font_factory(void)
+{
+ for (int i = 0;i < nbEnt;i++) ents[i].f->Unref();
+ if ( ents ) free(ents);
+
+ g_object_unref(fontServer);
+#ifdef USE_PANGO_WIN32
+ pango_win32_shutdown_display();
+#else
+ //pango_ft2_shutdown_display();
+#endif
+ //g_object_unref(fontContext);
+}
+
+font_instance *font_factory::FaceFromDescr(char const *family, char const *style)
+{
+ PangoFontDescription *temp_descr = pango_font_description_from_string(style);
+ pango_font_description_set_family(temp_descr,family);
+ font_instance *res = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ return res;
+}
+
+font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail)
+{
+#ifdef USE_PANGO_WIN32
+ // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
+ pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround)
+#else
+ pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround)
+#endif
+
+ font_instance *res = NULL;
+
+ if ( loadedFaces.find(descr) == loadedFaces.end() ) {
+ // not yet loaded
+ PangoFont *nFace = NULL;
+
+ // workaround for bug #1025565.
+ // fonts without families blow up Pango.
+ if (pango_font_description_get_family(descr) != NULL) {
+ nFace = pango_font_map_load_font(fontServer,fontContext,descr);
+ }
+ else {
+ g_warning(_("Ignoring font without family that will crash Pango"));
+ }
+
+ if ( nFace ) {
+ // duplicate FcPattern, the hard way
+ res = new font_instance();
+ // store the descr of the font we asked for, since this is the key where we intend to put the font_instance at
+ // in the hash_map. the descr of the returned pangofont may differ from what was asked, so we don't know (at this
+ // point) whether loadedFaces[that_descr] is free or not (and overwriting an entry will bring deallocation problems)
+ res->descr = pango_font_description_copy(descr);
+ res->daddy = this;
+ res->InstallFace(nFace);
+ if ( res->pFont == NULL ) {
+ // failed to install face -> bitmap font
+ // printf("face failed\n");
+ res->daddy = NULL;
+ delete res;
+ res = NULL;
+ if ( canFail ) {
+ char *tc = pango_font_description_to_string(descr);
+ PANGO_DEBUG("falling back from %s to Sans because InstallFace failed\n",tc);
+ free(tc);
+ pango_font_description_set_family(descr,"Sans");
+ res = Face(descr,false);
+ }
+ } else {
+ loadedFaces[res->descr]=res;
+ res->Ref();
+ AddInCache(res);
+ }
+ } else {
+ // no match
+ if ( canFail ) {
+ PANGO_DEBUG("falling back to Sans\n");
+ pango_font_description_set_family(descr,"Sans");
+ res = Face(descr,false);
+ }
+ }
+ } else {
+ // already here
+ res = loadedFaces[descr];
+ res->Ref();
+ AddInCache(res);
+ }
+ res->InitTheFace();
+ return res;
+}
+
+font_instance *font_factory::Face(char const *family, int variant, int style, int weight, int stretch, int /*size*/, int /*spacing*/)
+{
+ PangoFontDescription *temp_descr = pango_font_description_new();
+ pango_font_description_set_family(temp_descr,family);
+ pango_font_description_set_weight(temp_descr,(PangoWeight)weight);
+ pango_font_description_set_stretch(temp_descr,(PangoStretch)stretch);
+ pango_font_description_set_style(temp_descr,(PangoStyle)style);
+ pango_font_description_set_variant(temp_descr,(PangoVariant)variant);
+ font_instance *res = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ return res;
+}
+
+font_instance *font_factory::Face(char const *family, NRTypePosDef apos)
+{
+ PangoFontDescription *temp_descr = pango_font_description_new();
+
+ pango_font_description_set_family(temp_descr, family);
+
+ if ( apos.variant == NR_POS_VARIANT_SMALLCAPS ) {
+ pango_font_description_set_variant(temp_descr, PANGO_VARIANT_SMALL_CAPS);
+ } else {
+ pango_font_description_set_variant(temp_descr, PANGO_VARIANT_NORMAL);
+ }
+
+ if ( apos.italic ) {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_ITALIC);
+ } else if ( apos.oblique ) {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_OBLIQUE);
+ } else {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_NORMAL);
+ }
+
+ if ( apos.weight <= NR_POS_WEIGHT_ULTRA_LIGHT ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRALIGHT);
+ } else if ( apos.weight <= NR_POS_WEIGHT_LIGHT ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_LIGHT);
+ } else if ( apos.weight <= NR_POS_WEIGHT_NORMAL ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_NORMAL);
+ } else if ( apos.weight <= NR_POS_WEIGHT_BOLD ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_BOLD);
+ } else if ( apos.weight <= NR_POS_WEIGHT_ULTRA_BOLD ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRABOLD);
+ } else {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_HEAVY);
+ }
+
+ if ( apos.stretch <= NR_POS_STRETCH_ULTRA_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_WEIGHT_NORMAL ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_NORMAL);
+ } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_EXPANDED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_EXPANDED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_EXPANDED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXPANDED);
+ } else {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_EXPANDED);
+ }
+
+ font_instance *res = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ return res;
+}
+
+void font_factory::UnrefFace(font_instance *who)
+{
+ if ( who == NULL ) return;
+ if ( loadedFaces.find(who->descr) == loadedFaces.end() ) {
+ // not found
+ char *tc = pango_font_description_to_string(who->descr);
+ g_warning("unrefFace %p=%s: failed\n",who,tc);
+ free(tc);
+ } else {
+ loadedFaces.erase(loadedFaces.find(who->descr));
+ // printf("unrefFace %p: success\n",who);
+ }
+}
+
+NRNameList *font_factory::Families(NRNameList *flist)
+{
+ PangoFontFamily** fams = NULL;
+ int nbFam = 0;
+ pango_font_map_list_families(fontServer, &fams, &nbFam);
+
+ PANGO_DEBUG("got %d families\n", nbFam);
+
+ flist->length = nbFam;
+ flist->names = (guchar **)malloc(nbFam*sizeof(guchar*));
+ flist->destructor = font_factory_name_list_destructor;
+
+ for (int i = 0;i < nbFam;i++) {
+// Note: on Windows, pango_font_family_get_name always returns lowercase name.
+// As a result the list of fonts in the dialog is lowercase.
+// We could work around by loading the font and taking pango_font_description_get_family from its descr (that gives correct case),
+// but this is slow, and it's better to fix Pango instead.
+ flist->names[i]=(guchar*)strdup(pango_font_family_get_name(fams[i]));
+ }
+
+ qsort(flist->names, nbFam, sizeof(guchar *), family_name_compare);
+
+ g_free(fams);
+
+ return flist;
+}
+
+NRStyleList *font_factory::Styles(gchar const *family, NRStyleList *slist)
+{
+ PangoFontFamily *theFam = NULL;
+
+ // search available families
+ {
+ PangoFontFamily** fams = NULL;
+ int nbFam = 0;
+ pango_font_map_list_families(fontServer, &fams, &nbFam);
+
+ for (int i = 0;i < nbFam;i++) {
+ char const *fname = pango_font_family_get_name(fams[i]);
+ if ( fname && strcmp(family,fname) == 0 ) {
+ theFam = fams[i];
+ break;
+ }
+ }
+
+ g_free(fams);
+ }
+
+ // nothing found
+ if ( theFam == NULL ) {
+ slist->length = 0;
+ slist->records = NULL;
+ slist->destructor = NULL;
+ return slist;
+ }
+
+ // search faces in the found family
+ PangoFontFace** faces = NULL;
+ int nFaces = 0;
+ pango_font_family_list_faces(theFam, &faces, &nFaces);
+
+ slist->records = (NRStyleRecord *) malloc(nFaces * sizeof(NRStyleRecord));
+ slist->destructor = font_factory_style_list_destructor;
+
+ int nr = 0;
+ for (int i = 0; i < nFaces; i++) {
+
+ // no unnamed faces
+ if (pango_font_face_get_face_name(faces[i]) == NULL)
+ continue;
+ PangoFontDescription *nd = pango_font_face_describe(faces[i]);
+ if (nd == NULL)
+ continue;
+ char const *descr = pango_font_description_to_string(nd);
+ if (descr == NULL) {
+ pango_font_description_free(nd);
+ continue;
+ }
+
+ char const *name = g_strdup(pango_font_face_get_face_name(faces[i]));
+ pango_font_description_free(nd);
+
+ slist->records[nr].name = name;
+ slist->records[nr].descr = descr;
+ nr ++;
+ }
+
+ slist->length = nr;
+
+ qsort(slist->records, slist->length, sizeof(NRStyleRecord), style_record_compare);
+ /* effic: Consider doing strdown and all the is_italic etc. tests once off and store the
+ * results in a table, rather than having the sort invoke multiple is_italic tests per
+ * record.
+ */
+
+ g_free(faces);
+
+ return slist;
+}
+
+void font_factory::AddInCache(font_instance *who)
+{
+ if ( who == NULL ) return;
+ for (int i = 0;i < nbEnt;i++) ents[i].age *= 0.9;
+ for (int i = 0;i < nbEnt;i++) {
+ if ( ents[i].f == who ) {
+ // printf("present\n");
+ ents[i].age += 1.0;
+ return;
+ }
+ }
+ if ( nbEnt > maxEnt ) {
+ printf("cache sur-plein?\n");
+ return;
+ }
+ who->Ref();
+ if ( nbEnt == maxEnt ) {
+ int bi = 0;
+ double ba = ents[bi].age;
+ for (int i = 1;i < nbEnt;i++) {
+ if ( ents[i].age < ba ) {
+ bi = i;
+ ba = ents[bi].age;
+ }
+ }
+ ents[bi].f->Unref();
+ ents[bi]=ents[--nbEnt];
+ }
+ ents[nbEnt].f = who;
+ ents[nbEnt].age = 1.0;
+ nbEnt++;
+}
+
+
+/*
+ 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/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h
new file mode 100644
index 000000000..2b4c6be0f
--- /dev/null
+++ b/src/libnrtype/FontFactory.h
@@ -0,0 +1,112 @@
+/*
+ * FontFactory.h
+ * testICU
+ *
+ */
+
+#ifndef my_font_factory
+#define my_font_factory
+
+#include <functional>
+#include <algorithm>
+#include <ext/hash_map>
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#ifdef _WIN32
+#define USE_PANGO_WIN32
+#endif
+
+#include <pango/pango.h>
+#include "nr-type-primitives.h"
+#include "nr-type-pos-def.h"
+#include <libnrtype/nrtype-forward.h>
+
+/* Freetype */
+#ifdef USE_PANGO_WIN32
+#include <pango/pangowin32.h>
+#else
+#include <pango/pangoft2.h>
+#include <freetype/freetype.h>
+#endif
+
+// the font_factory keeps a hashmap of all the loaded font_instances, and uses the PangoFontDescription
+// as index (nota: since pango already does that, using the PangoFont could work too)
+struct font_descr_hash : public std::unary_function<PangoFontDescription*,size_t> {
+ size_t operator()(PangoFontDescription *const &x) const;
+};
+struct font_descr_equal : public std::binary_function<PangoFontDescription*, PangoFontDescription*, bool> {
+ bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b);
+};
+
+class font_factory {
+public:
+ static font_factory *lUsine; /**< The default font_factory; i cannot think of why we would
+ * need more than one.
+ *
+ * ("l'usine" is french for "the factory".)
+ */
+
+ /** A little cache for fonts, so that you don't loose your time looking up fonts in the font list
+ * each font in the cache is refcounted once (and deref'd when removed from the cache). */
+ struct font_entry {
+ font_instance *f;
+ double age;
+ };
+ int nbEnt; ///< Number of entries.
+ int maxEnt; ///< Cache size.
+ font_entry *ents;
+
+ // Pango data. Backend-specific structures are cast to these opaque types.
+ PangoFontMap *fontServer;
+ PangoContext *fontContext;
+#ifdef USE_PANGO_WIN32
+ PangoWin32FontCache *pangoFontCache;
+ HDC hScreenDC;
+#endif
+ double fontSize; /**< The huge fontsize used as workaround for hinting.
+ * Different between freetype and win32. */
+
+ __gnu_cxx::hash_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> loadedFaces;
+
+ font_factory();
+ ~font_factory();
+
+ /// Returns the default font_factory.
+ static font_factory* Default();
+
+ // Various functions to get a font_instance from different descriptions.
+ font_instance* FaceFromDescr(char const *family, char const *style);
+ font_instance* Face(PangoFontDescription *descr, bool canFail=true);
+ font_instance* Face(char const *family,
+ int variant=PANGO_VARIANT_NORMAL, int style=PANGO_STYLE_NORMAL,
+ int weight=PANGO_WEIGHT_NORMAL, int stretch=PANGO_STRETCH_NORMAL,
+ int size=10, int spacing=0);
+ font_instance* Face(char const *family, NRTypePosDef apos);
+
+ /// Semi-private: tells the font_factory taht the font_instance 'who' has died and should be removed from loadedFaces
+ void UnrefFace(font_instance* who);
+
+ // Queries for the font-selector.
+ NRNameList* Families(NRNameList *flist);
+ NRStyleList* Styles(const gchar *family, NRStyleList *slist);
+
+ // internal
+ void AddInCache(font_instance *who);
+};
+
+
+#endif /* my_font_factory */
+
+
+/*
+ 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/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp
new file mode 100644
index 000000000..d2b19d0f2
--- /dev/null
+++ b/src/libnrtype/FontInstance.cpp
@@ -0,0 +1,763 @@
+/*
+ * FontInstance.cpp
+ * testICU
+ *
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <libnr/nr-rect.h>
+#include <libnrtype/font-glyph.h>
+#include <libnrtype/font-instance.h>
+
+/* #include <layout/LEGlyphStorage.h> */
+
+#include <livarot/Path.h>
+
+#include "RasterFont.h"
+
+/* Freetype 2 */
+# include <freetype/ftoutln.h>
+# include <freetype/ftbbox.h>
+# include <freetype/internal/tttypes.h>
+# include <freetype/internal/ftstream.h>
+# include <freetype/tttags.h>
+# include <pango/pangoft2.h>
+
+
+
+size_t font_style_hash::operator()(const font_style &x) const {
+ int h=0,n;
+ for (int i=0;i<6;i++) {
+ n=(int)floor(100*x.transform[i]);
+ h*=12186;
+ h+=n;
+ }
+ n=(int)floor(100*x.stroke_width);
+ h*=12186;
+ h+=n;
+ n=(x.vertical)?1:0;
+ h*=12186;
+ h+=n;
+ if ( x.stroke_width >= 0.01 ) {
+ n=x.stroke_cap*10+x.stroke_join+(int)(x.stroke_miter_limit*100);
+ h*=12186;
+ h+=n;
+ if ( x.nbDash > 0 ) {
+ n=x.nbDash;
+ h*=12186;
+ h+=n;
+ n=(int)floor(100*x.dash_offset);
+ h*=12186;
+ h+=n;
+ for (int i=0;i<x.nbDash;i++) {
+ n=(int)floor(100*x.dashes[i]);
+ h*=12186;
+ h+=n;
+ }
+ }
+ }
+ return h;
+}
+
+bool font_style_equal::operator()(const font_style &a,const font_style &b) {
+ NR::Matrix diff=a.transform.inverse();
+ diff*=b.transform;
+ if ( diff.is_translation(0.01) ) {
+ if ( fabs(diff[4]) < 0.01 && fabs(diff[5]) < 0.01 ) {
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ if ( a.vertical && b.vertical == false ) return false;
+ if ( a.vertical == false && b.vertical ) return false;
+ if ( a.stroke_width > 0.01 && b.stroke_width <= 0.01 ) return false;
+ if ( a.stroke_width <= 0.01 && b.stroke_width > 0.01 ) return false;
+ if ( a.stroke_width <= 0.01 && b.stroke_width <= 0.01 ) return true;
+
+ if ( a.stroke_cap != b.stroke_cap ) return false;
+ if ( a.stroke_join != b.stroke_join ) return false;
+ if ( fabs(a.stroke_miter_limit-b.stroke_miter_limit) > 0.01) return false;
+ if ( a.nbDash != b.nbDash ) return false;
+ if ( a.nbDash <= 0 ) return true;
+ if ( fabs(a.dash_offset-b.dash_offset) < 0.01 ) {
+ for (int i=0;i<a.nbDash;i++) {
+ if ( fabs(a.dashes[i]-b.dashes[i]) >= 0.01 ) return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+#ifndef USE_PANGO_WIN32
+/*
+ * Outline extraction
+ */
+typedef struct ft2_to_liv {
+ Path* theP;
+ double scale;
+ NR::Point last;
+} ft2_to_liv;
+
+// outline as returned by freetype -> livarot Path
+// see nr-type-ft2.cpp for the freetype -> artBPath on which this code is based
+static int ft2_move_to (FT_Vector * to, void * i_user) {
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y);
+ // printf("m t=%f %f\n",p[0],p[1]);
+ user->theP->MoveTo(p);
+ user->last=p;
+ return 0;
+}
+
+static int ft2_line_to (FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y);
+ // printf("l t=%f %f\n",p[0],p[1]);
+ user->theP->LineTo(p);
+ user->last=p;
+ return 0;
+}
+
+static int ft2_conic_to (FT_Vector * control, FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y),c(user->scale*control->x,user->scale*control->y);
+ // printf("b c=%f %f t=%f %f\n",c[0],c[1],p[0],p[1]);
+ user->theP->BezierTo(p);
+ user->theP->IntermBezierTo(c);
+ user->theP->EndBezierTo();
+ user->last=p;
+ return 0;
+}
+
+static int ft2_cubic_to (FT_Vector * control1, FT_Vector * control2, FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y),
+ c1(user->scale*control1->x,user->scale*control1->y),
+ c2(user->scale*control2->x,user->scale*control2->y);
+ // printf("c c1=%f %f c2=%f %f t=%f %f\n",c1[0],c1[1],c2[0],c2[1],p[0],p[1]);
+ user->theP->CubicTo(p,3*(c1-user->last),3*(p-c2));
+ user->last=p;
+ return 0;
+}
+#endif
+
+/*
+ *
+ */
+
+font_instance::font_instance(void)
+{
+ //printf("font instance born\n");
+ descr=NULL;
+ pFont=NULL;
+ refCount=0;
+ daddy=NULL;
+ nbGlyph=maxGlyph=0;
+ glyphs=NULL;
+ theFace=NULL;
+}
+
+font_instance::~font_instance(void)
+{
+ if ( daddy ) daddy->UnrefFace(this);
+ //printf("font instance death\n");
+ if ( pFont ) g_object_unref(pFont);
+ pFont=NULL;
+ if ( descr ) pango_font_description_free(descr);
+ descr=NULL;
+ // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch
+ theFace=NULL;
+
+ for (int i=0;i<nbGlyph;i++) {
+ if ( glyphs[i].outline ) delete glyphs[i].outline;
+ if ( glyphs[i].artbpath ) free(glyphs[i].artbpath);
+ }
+ if ( glyphs ) free(glyphs);
+ nbGlyph=maxGlyph=0;
+ glyphs=NULL;
+}
+
+void font_instance::Ref(void)
+{
+ refCount++;
+ //char *tc=pango_font_description_to_string(descr);
+ //printf("font %x %s ref'd %i\n",this,tc,refCount);
+ //free(tc);
+}
+
+void font_instance::Unref(void)
+{
+ refCount--;
+ //char *tc=pango_font_description_to_string(descr);
+ //printf("font %x %s unref'd %i\n",this,tc,refCount);
+ //free(tc);
+ if ( refCount <= 0 ) {
+ if ( daddy ) daddy->UnrefFace(this);
+ daddy=NULL;
+ delete this;
+ }
+}
+
+unsigned int font_instance::Name(gchar *str, unsigned int size)
+{
+ return Attribute("name", str, size);
+}
+
+unsigned int font_instance::Family(gchar *str, unsigned int size)
+{
+ return Attribute("family", str, size);
+}
+
+unsigned int font_instance::PSName(gchar *str, unsigned int size)
+{
+ return Attribute("psname", str, size);
+}
+
+unsigned int font_instance::Attribute(const gchar *key, gchar *str, unsigned int size)
+{
+ if ( descr == NULL ) {
+ if ( size > 0 ) str[0]=0;
+ return 0;
+ }
+ char* res=NULL;
+ bool free_res=false;
+
+ if ( strcmp(key,"name") == 0 ) {
+ PangoFontDescription* td=pango_font_description_copy(descr);
+ pango_font_description_unset_fields (td, PANGO_FONT_MASK_SIZE);
+ res=pango_font_description_to_string (td);
+ pango_font_description_free(td);
+ free_res=true;
+ } else if ( strcmp(key,"psname") == 0 ) {
+#ifndef USE_PANGO_WIN32
+ res = (char *) FT_Get_Postscript_Name (theFace); // that's the main method, seems to always work
+#endif
+ free_res=false;
+ if (res == NULL) { // a very limited workaround, only bold, italic, and oblique will work
+ PangoStyle style=pango_font_description_get_style(descr);
+ bool i = (style == PANGO_STYLE_ITALIC);
+ bool o = (style == PANGO_STYLE_OBLIQUE);
+ PangoWeight weight=pango_font_description_get_weight(descr);
+ bool b = (weight >= PANGO_WEIGHT_BOLD);
+
+ res = g_strdup_printf ("%s%s%s%s",
+ pango_font_description_get_family(descr),
+ (b || i || o) ? "-" : "",
+ (b) ? "Bold" : "",
+ (i) ? "Italic" : ((o) ? "Oblique" : "") );
+ free_res = true;
+ }
+ } else if ( strcmp(key,"family") == 0 ) {
+ res=(char*)pango_font_description_get_family(descr);
+ free_res=false;
+ } else if ( strcmp(key,"style") == 0 ) {
+ PangoStyle v=pango_font_description_get_style(descr);
+ if ( v == PANGO_STYLE_ITALIC ) {
+ res="italic";
+ } else if ( v == PANGO_STYLE_OBLIQUE ) {
+ res="oblique";
+ } else {
+ res="normal";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"weight") == 0 ) {
+ PangoWeight v=pango_font_description_get_weight(descr);
+ if ( v <= PANGO_WEIGHT_ULTRALIGHT ) {
+ res="200";
+ } else if ( v <= PANGO_WEIGHT_LIGHT ) {
+ res="300";
+ } else if ( v <= PANGO_WEIGHT_NORMAL ) {
+ res="normal";
+ } else if ( v <= PANGO_WEIGHT_BOLD ) {
+ res="bold";
+ } else {
+ res="800";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"stretch") == 0 ) {
+ PangoStretch v=pango_font_description_get_stretch(descr);
+ if ( v <= PANGO_STRETCH_EXTRA_CONDENSED ) {
+ res="extra-condensed";
+ } else if ( v <= PANGO_STRETCH_CONDENSED ) {
+ res="condensed";
+ } else if ( v <= PANGO_STRETCH_SEMI_CONDENSED ) {
+ res="semi-condensed";
+ } else if ( v <= PANGO_STRETCH_NORMAL ) {
+ res="normal";
+ } else if ( v <= PANGO_STRETCH_SEMI_EXPANDED ) {
+ res="semi-expanded";
+ } else if ( v <= PANGO_STRETCH_EXPANDED ) {
+ res="expanded";
+ } else {
+ res="extra-expanded";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"variant") == 0 ) {
+ PangoVariant v=pango_font_description_get_variant(descr);
+ if ( v == PANGO_VARIANT_SMALL_CAPS ) {
+ res="small-caps";
+ } else {
+ res="normal";
+ }
+ free_res=false;
+ } else {
+ res = NULL;
+ free_res=false;
+ }
+ if ( res == NULL ) {
+ if ( size > 0 ) str[0]=0;
+ return 0;
+ }
+
+ if (res) {
+ unsigned int len=strlen(res);
+ unsigned int rlen=(size-1<len)?size-1:len;
+ if ( str ) {
+ if ( rlen > 0 ) memcpy(str,res,rlen);
+ if ( size > 0 ) str[rlen]=0;
+ }
+ if (free_res) free(res);
+ return len;
+ }
+ return 0;
+}
+
+void font_instance::InitTheFace()
+{
+#ifdef USE_PANGO_WIN32
+ if ( !theFace ) {
+ LOGFONT *lf=pango_win32_font_logfont(pFont);
+ g_assert(lf != NULL);
+ theFace=pango_win32_font_cache_load(daddy->pangoFontCache,lf);
+ g_free(lf);
+ }
+ XFORM identity = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
+ SetWorldTransform(daddy->hScreenDC, &identity);
+ SetGraphicsMode(daddy->hScreenDC, GM_COMPATIBLE);
+ SelectObject(daddy->hScreenDC,theFace);
+#else
+ theFace=pango_ft2_font_get_face(pFont);
+ FT_Select_Charmap(theFace,ft_encoding_unicode) && FT_Select_Charmap(theFace,ft_encoding_symbol);
+#endif
+}
+
+void font_instance::FreeTheFace()
+{
+#ifdef USE_PANGO_WIN32
+ SelectObject(daddy->hScreenDC,GetStockObject(SYSTEM_FONT));
+ pango_win32_font_cache_unload(daddy->pangoFontCache,theFace);
+#endif
+ theFace=NULL;
+}
+
+void font_instance::InstallFace(PangoFont* iFace)
+{
+ if ( !iFace )
+ return;
+ pFont=iFace;
+
+ InitTheFace();
+
+ if ( pFont && IsOutlineFont() == false ) {
+ FreeTheFace();
+ if ( pFont ) g_object_unref(pFont);
+ pFont=NULL;
+ }
+}
+
+bool font_instance::IsOutlineFont(void)
+{
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+#ifdef USE_PANGO_WIN32
+ TEXTMETRIC tm;
+ return GetTextMetrics(daddy->hScreenDC,&tm) && tm.tmPitchAndFamily&TMPF_TRUETYPE;
+#else
+ return FT_IS_SCALABLE(theFace);
+#endif
+}
+
+int font_instance::MapUnicodeChar(gunichar c)
+{
+ if ( pFont == NULL ) return 0;
+#ifdef USE_PANGO_WIN32
+ return pango_win32_font_get_glyph_index(pFont,c);
+#else
+ int res=0;
+ theFace=pango_ft2_font_get_face(pFont);
+ if ( c > 0xf0000 ) {
+ res=CLAMP(c,0xf0000,0x1fffff)-0xf0000;
+ } else {
+ res=FT_Get_Char_Index(theFace, c);
+ }
+ return res;
+#endif
+}
+
+
+#ifdef USE_PANGO_WIN32
+static inline NR::Point pointfx_to_nrpoint(const POINTFX &p, double scale)
+{
+ return NR::Point(*(long*)&p.x / 65536.0 * scale,
+ *(long*)&p.y / 65536.0 * scale);
+}
+#endif
+
+void font_instance::LoadGlyph(int glyph_id)
+{
+ if ( pFont == NULL ) return;
+ InitTheFace();
+#ifndef USE_PANGO_WIN32
+ if ( theFace->units_per_EM == 0 ) return; // bitmap font
+#endif
+
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ if ( nbGlyph >= maxGlyph ) {
+ maxGlyph=2*nbGlyph+1;
+ glyphs=(font_glyph*)realloc(glyphs,maxGlyph*sizeof(font_glyph));
+ }
+ font_glyph n_g;
+ n_g.outline=NULL;
+ n_g.artbpath=NULL;
+ n_g.bbox[0]=n_g.bbox[1]=n_g.bbox[2]=n_g.bbox[3]=0;
+ bool doAdd=false;
+
+#ifdef USE_PANGO_WIN32
+ MAT2 identity = {{0,1},{0,0},{0,0},{0,1}};
+ OUTLINETEXTMETRIC otm;
+ GetOutlineTextMetrics(daddy->hScreenDC, sizeof(otm), &otm);
+ GLYPHMETRICS metrics;
+ DWORD bufferSize=GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+ double scale=1.0/daddy->fontSize;
+ n_g.h_advance=metrics.gmCellIncX*scale;
+ n_g.v_advance=otm.otmTextMetrics.tmHeight*scale;
+ n_g.h_width=metrics.gmBlackBoxX*scale;
+ n_g.v_width=metrics.gmBlackBoxY*scale;
+ n_g.outline=NULL;
+ if ( bufferSize == GDI_ERROR) {
+ // shit happened
+ } else if ( bufferSize == 0) {
+ // character has no visual representation, but is valid (eg whitespace)
+ doAdd=true;
+ } else {
+ std::auto_ptr<char> buffer(new char[bufferSize]);
+ if ( GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, bufferSize, buffer.get(), &identity) <= 0 ) {
+ // shit happened
+ } else {
+ // Platform SDK is rubbish, read KB87115 instead
+ n_g.outline=new Path;
+ DWORD polyOffset=0;
+ while ( polyOffset < bufferSize ) {
+ TTPOLYGONHEADER const *polyHeader=(TTPOLYGONHEADER const *)(buffer.get()+polyOffset);
+ if (polyOffset+polyHeader->cb > bufferSize) break;
+
+ if (polyHeader->dwType == TT_POLYGON_TYPE) {
+ n_g.outline->MoveTo(pointfx_to_nrpoint(polyHeader->pfxStart, scale));
+ DWORD curveOffset=polyOffset+sizeof(TTPOLYGONHEADER);
+
+ while ( curveOffset < polyOffset+polyHeader->cb ) {
+ TTPOLYCURVE const *polyCurve=(TTPOLYCURVE const *)(buffer.get()+curveOffset);
+ POINTFX const *p=polyCurve->apfx;
+ POINTFX const *endp=p+polyCurve->cpfx;
+
+ switch (polyCurve->wType) {
+ case TT_PRIM_LINE:
+ while ( p != endp )
+ n_g.outline->LineTo(pointfx_to_nrpoint(*p++, scale));
+ break;
+
+ case TT_PRIM_QSPLINE:
+ {
+ g_assert(polyCurve->cpfx >= 2);
+ endp -= 2;
+ NR::Point this_mid=pointfx_to_nrpoint(p[0], scale);
+ while ( p != endp ) {
+ NR::Point next_mid=pointfx_to_nrpoint(p[1], scale);
+ n_g.outline->BezierTo((next_mid+this_mid)/2);
+ n_g.outline->IntermBezierTo(this_mid);
+ n_g.outline->EndBezierTo();
+ ++p;
+ this_mid=next_mid;
+ }
+ n_g.outline->BezierTo(pointfx_to_nrpoint(p[1], scale));
+ n_g.outline->IntermBezierTo(this_mid);
+ n_g.outline->EndBezierTo();
+ break;
+ }
+
+ case 3: // TT_PRIM_CSPLINE
+ g_assert(polyCurve->cpfx % 3 == 0);
+ while ( p != endp ) {
+ n_g.outline->CubicTo(pointfx_to_nrpoint(p[2], scale), pointfx_to_nrpoint(p[0], scale), pointfx_to_nrpoint(p[1], scale));
+ p += 3;
+ }
+ break;
+ }
+ curveOffset += sizeof(TTPOLYCURVE)+sizeof(POINTFX)*(polyCurve->cpfx-1);
+ }
+ n_g.outline->Close();
+ }
+ polyOffset += polyHeader->cb;
+ }
+ doAdd=true;
+ }
+ }
+#else
+ if (FT_Load_Glyph (theFace, glyph_id, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) {
+ // shit happened
+ } else {
+ if ( FT_HAS_HORIZONTAL(theFace) ) {
+ n_g.h_advance=((double)theFace->glyph->metrics.horiAdvance)/((double)theFace->units_per_EM);
+ n_g.h_width=((double)theFace->glyph->metrics.width)/((double)theFace->units_per_EM);
+ } else {
+ n_g.h_width=n_g.h_advance=((double)(theFace->bbox.xMax-theFace->bbox.xMin))/((double)theFace->units_per_EM);
+ }
+ if ( FT_HAS_VERTICAL(theFace) ) {
+ n_g.v_advance=((double)theFace->glyph->metrics.vertAdvance)/((double)theFace->units_per_EM);
+ n_g.v_width=((double)theFace->glyph->metrics.height)/((double)theFace->units_per_EM);
+ } else {
+ n_g.v_width=n_g.v_advance=((double)theFace->height)/((double)theFace->units_per_EM);
+ }
+ if ( theFace->glyph->format == ft_glyph_format_outline ) {
+ FT_Outline_Funcs ft2_outline_funcs = {
+ ft2_move_to,
+ ft2_line_to,
+ ft2_conic_to,
+ ft2_cubic_to,
+ 0, 0
+ };
+ n_g.outline=new Path;
+ ft2_to_liv tData;
+ tData.theP=n_g.outline;
+ tData.scale=1.0/((double)theFace->units_per_EM);
+ tData.last=NR::Point(0,0);
+ FT_Outline_Decompose (&theFace->glyph->outline, &ft2_outline_funcs, &tData);
+ }
+ doAdd=true;
+ }
+#endif
+
+ if ( doAdd ) {
+ if ( n_g.outline ) {
+ n_g.outline->FastBBox(n_g.bbox[0],n_g.bbox[1],n_g.bbox[2],n_g.bbox[3]);
+ n_g.artbpath=n_g.outline->MakeArtBPath();
+ }
+ glyphs[nbGlyph]=n_g;
+ id_to_no[glyph_id]=nbGlyph;
+ nbGlyph++;
+ }
+ } else {
+ }
+}
+
+bool font_instance::FontMetrics(double &ascent,double &descent,double &leading)
+{
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+ if ( theFace == NULL ) return false;
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false;
+ double scale=1.0/daddy->fontSize;
+ ascent=fabs(otm.otmAscent*scale);
+ descent=fabs(otm.otmDescent*scale);
+ leading=fabs(otm.otmLineGap*scale);
+#else
+ if ( theFace->units_per_EM == 0 ) return false; // bitmap font
+ ascent=fabs(((double)theFace->ascender)/((double)theFace->units_per_EM));
+ descent=fabs(((double)theFace->descender)/((double)theFace->units_per_EM));
+ leading=fabs(((double)theFace->height)/((double)theFace->units_per_EM));
+ leading-=ascent+descent;
+#endif
+ return true;
+}
+
+bool font_instance::FontSlope(double &run, double &rise)
+{
+ run = 0.0;
+ rise = 1.0;
+
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+ if ( theFace == NULL ) return false;
+
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false;
+ run=otm.otmsCharSlopeRun;
+ rise=otm.otmsCharSlopeRise;
+#else
+ if ( theFace->units_per_EM == 0 ) return false; // bitmap font
+
+ TT_HoriHeader *hhea = (TT_HoriHeader*)FT_Get_Sfnt_Table(theFace, ft_sfnt_hhea);
+ if (hhea == NULL) return false;
+ run = hhea->caret_Slope_Run;
+ rise = hhea->caret_Slope_Rise;
+#endif
+ return true;
+}
+
+NR::Rect font_instance::BBox(int glyph_id)
+{
+ int no=-1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ if ( no < 0 ) return NR::Rect(NR::Point(0,0),NR::Point(0,0));
+ NR::Point rmin(glyphs[no].bbox[0],glyphs[no].bbox[1]);
+ NR::Point rmax(glyphs[no].bbox[2],glyphs[no].bbox[3]);
+ NR::Rect res(rmin,rmax);
+ return res;
+}
+
+Path* font_instance::Outline(int glyph_id,Path* copyInto)
+{
+ int no=-1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ if ( no < 0 ) return NULL;
+ Path* src_o=glyphs[no].outline;
+ if ( copyInto ) {
+ copyInto->Reset();
+ copyInto->Copy(src_o);
+ return copyInto;
+ }
+ return src_o;
+}
+
+void* font_instance::ArtBPath(int glyph_id)
+{
+ int no=-1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ if ( no < 0 ) return NULL;
+ return glyphs[no].artbpath;
+}
+
+double font_instance::Advance(int glyph_id,bool vertical)
+{
+ int no=-1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ if ( no >= 0 ) {
+ if ( vertical ) {
+ return glyphs[no].v_advance;
+ } else {
+ return glyphs[no].h_advance;
+ }
+ }
+ return 0;
+}
+
+
+raster_font* font_instance::RasterFont(const NR::Matrix &trs,double stroke_width,bool vertical,JoinType stroke_join,ButtType stroke_cap,float miter_limit)
+{
+ font_style nStyle;
+ nStyle.transform=trs;
+ nStyle.vertical=vertical;
+ nStyle.stroke_width=stroke_width;
+ nStyle.stroke_cap=stroke_cap;
+ nStyle.stroke_join=stroke_join;
+ nStyle.nbDash=0;
+ nStyle.dash_offset=0;
+ nStyle.dashes=NULL;
+ return RasterFont(nStyle);
+}
+
+raster_font* font_instance::RasterFont(const font_style &inStyle)
+{
+ raster_font *res=NULL;
+ double *savDashes=NULL;
+ font_style nStyle=inStyle;
+ // for some evil reason font_style doesn't have a copy ctor, so the
+ // stuff that should be done there is done here instead (because the
+ // raster_font ctor copies nStyle).
+ if ( nStyle.stroke_width > 0 && nStyle.nbDash > 0 && nStyle.dashes ) {
+ savDashes=nStyle.dashes;
+ nStyle.dashes=(double*)malloc(nStyle.nbDash*sizeof(double));
+ memcpy(nStyle.dashes,savDashes,nStyle.nbDash*sizeof(double));
+ }
+ if ( loadedStyles.find(nStyle) == loadedStyles.end() ) {
+ raster_font *nR = new raster_font(nStyle);
+ nR->Ref();
+ nR->daddy=this;
+ loadedStyles[nStyle]=nR;
+ res=nR;
+ if ( res ) Ref();
+ } else {
+ res=loadedStyles[nStyle];
+ res->Ref();
+ if ( nStyle.dashes ) free(nStyle.dashes); // since they're not taken by a new rasterfont
+ }
+ nStyle.dashes=savDashes;
+ return res;
+}
+
+void font_instance::RemoveRasterFont(raster_font* who)
+{
+ if ( who == NULL ) return;
+ if ( loadedStyles.find(who->style) == loadedStyles.end() ) {
+ //g_print("RemoveRasterFont failed \n");
+ // not found
+ } else {
+ loadedStyles.erase(loadedStyles.find(who->style));
+ //g_print("RemoveRasterFont\n");
+ Unref();
+ }
+}
+
+
+
+/*
+ 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/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp
new file mode 100755
index 000000000..0fbbb6f0c
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Compute.cpp
@@ -0,0 +1,1514 @@
+/*
+ * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "style.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "sp-object.h"
+#include "Layout-TNG-Scanline-Maker.h"
+
+namespace Inkscape {
+namespace Text {
+
+//#define IFTRACE(_code) _code
+//#define TRACE(_args) g_print _args
+#define IFTRACE(_code)
+#define TRACE(_args)
+
+// ******* enum conversion tables
+static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, PANGO_DIRECTION_LTR},
+ {SP_CSS_WRITING_MODE_RL_TB, PANGO_DIRECTION_RTL},
+ {SP_CSS_WRITING_MODE_TB_LR, PANGO_DIRECTION_LTR}}; // this is correct
+
+static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_my_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, Layout::LEFT_TO_RIGHT},
+ {SP_CSS_WRITING_MODE_RL_TB, Layout::RIGHT_TO_LEFT},
+ {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}}; // this is correct
+
+/** \brief private to Layout. Does the real work of text flowing.
+
+This class does a standard greedy paragraph wrapping algorithm.
+
+Very high-level overview:
+
+<pre>
+foreach(paragraph) {
+ call pango_itemize() (_buildPangoItemizationForPara())
+ break into spans, without dealing with wrapping (_buildSpansForPara())
+ foreach(line in flow shape) {
+ foreach(chunk in flow shape) { (in _buildChunksInScanRun())
+ // this inner loop in _measureUnbrokenSpan()
+ if the line height changed discard the line and start again
+ keep adding characters until we run out of space in the chunk, then back up to the last word boundary
+ (do sensible things if there is no previous word break)
+ }
+ push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
+ }
+ push the paragraph (in calculate())
+}
+</pre>
+
+...and all of that needs to work vertically too, and with all the little details that make life annoying
+*/
+class Layout::Calculator
+{
+ class SpanPosition;
+ friend class SpanPosition;
+
+ Layout &_flow;
+
+ ScanlineMaker *_scanline_maker;
+
+ unsigned _current_shape_index; /// index into Layout::_input_wrap_shapes
+
+ PangoContext *_pango_context;
+
+ Direction _block_progression;
+
+ /** for y= attributes in tspan elements et al, we do the adjustment by moving each
+ glyph individually by this number. The spec means that this is maintained across
+ paragraphs. */
+ double _y_offset;
+
+ /** to stop pango from hinting its output, the font factory creates all fonts very large.
+ All numbers returned from pango have to be divided by this number \em and divided by
+ PANGO_SCALE. See font_factory::font_factory(). */
+ double _font_factory_size_multiplier;
+
+ /** Temporary storage associated with each item in Layout::_input_stream. */
+ struct InputItemInfo {
+ bool in_sub_flow;
+ Layout *sub_flow; // this is only set for the first input item in a sub-flow
+
+ InputItemInfo() : in_sub_flow(false), sub_flow(NULL) {}
+ void free();
+ };
+
+ /** Temporary storage associated with each item returned by the call to
+ pango_itemize(). */
+ struct PangoItemInfo {
+ PangoItem *item;
+ font_instance *font;
+
+ PangoItemInfo() : item(NULL), font(NULL) {}
+ void free();
+ };
+
+ /** These spans have approximately the same definition as that used for
+ Layout::Span (constant font, direction, etc), except that they are from
+ before we have located the line breaks, so bear no relation to chunks.
+ They are guaranteed to be in at most one PangoItem (spans with no text in
+ them will not have an associated PangoItem), exactly one input source and
+ will only have one change of x, y, dx, dy or rotate attribute, which will
+ be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
+ BrokenSpan. */
+ struct UnbrokenSpan {
+ PangoGlyphString *glyph_string;
+ int pango_item_index; /// index into _para.pango_items, or -1 if this is style only
+ unsigned input_index; /// index into Layout::_input_stream
+ Glib::ustring::const_iterator input_stream_first_character;
+ double font_size;
+ LineHeight line_height;
+ double line_height_multiplier; /// calculated from the font-height css property
+ unsigned text_bytes;
+ unsigned char_index_in_para; /// the index of the first character in this span in the paragraph, for looking up char_attributes
+ SVGLength x, y, dx, dy, rotate; // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
+
+ UnbrokenSpan() : glyph_string(NULL) {}
+ void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;}
+ };
+
+ /** a useful little iterator for moving char-by-char across spans. */
+ struct UnbrokenSpanPosition {
+ std::vector<UnbrokenSpan>::iterator iter_span;
+ unsigned char_byte;
+ unsigned char_index;
+
+ void increment(); ///< Step forward by one character.
+
+ inline bool operator== (UnbrokenSpanPosition const &other) const
+ {return char_byte == other.char_byte && iter_span == other.iter_span;}
+ inline bool operator!= (UnbrokenSpanPosition const &other) const
+ {return char_byte != other.char_byte || iter_span != other.iter_span;}
+ };
+
+ /** The line breaking algorithm will convert each UnbrokenSpan into one
+ or more of these. A BrokenSpan will never cross a chunk boundary, c.f.
+ UnbrokenSpan. */
+ struct BrokenSpan {
+ UnbrokenSpanPosition start;
+ UnbrokenSpanPosition end; // the end of this will always be the same as the start of the next
+ unsigned start_glyph_index;
+ unsigned end_glyph_index;
+ double width;
+ unsigned whitespace_count;
+ bool ends_with_whitespace;
+ double each_whitespace_width;
+ void setZero();
+ };
+
+ /** The definition of a chunk used here is the same as that used in Layout. */
+ struct ChunkInfo {
+ std::vector<BrokenSpan> broken_spans;
+ double scanrun_width;
+ double text_width; ///< Total width used by the text (excluding justification).
+ double x;
+ int whitespace_count;
+ };
+
+ /** Used to provide storage for anything that applies to the current
+ paragraph only. Since we're only processing one paragraph at a time,
+ there's only one instantiation of this struct, on the stack of
+ calculate(). */
+ struct ParagraphInfo {
+ unsigned first_input_index; ///< Index into Layout::_input_stream.
+ Direction direction;
+ Alignment alignment;
+ std::vector<InputItemInfo> input_items;
+ std::vector<PangoItemInfo> pango_items;
+ std::vector<PangoLogAttr> char_attributes; ///< For every character in the paragraph.
+ std::vector<UnbrokenSpan> unbroken_spans;
+
+ template<typename T> static void free_sequence(T &seq);
+ void free();
+ };
+
+/* *********************************************************************************************************/
+// Initialisation of ParagraphInfo structure
+
+
+#if 0 /* unused */
+ void _initialiseInputItems(ParagraphInfo *para) const;
+#endif
+
+ void _buildPangoItemizationForPara(ParagraphInfo *para) const;
+
+ static void _computeFontLineHeight(font_instance *font, double font_size,
+ SPStyle const *style, LineHeight *line_height,
+ double *line_height_multiplier);
+
+ unsigned _buildSpansForPara(ParagraphInfo *para) const;
+
+/* *********************************************************************************************************/
+// Per-line functions
+
+
+ bool _goToNextWrapShape();
+
+ bool _findChunksForLine(ParagraphInfo const &para, UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
+
+ static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &span_pos)
+ {
+ return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
+ }
+
+ bool _buildChunksInScanRun(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &start_span_pos,
+ ScanlineMaker::ScanRun const &scan_run,
+ std::vector<ChunkInfo> *chunk_info,
+ LineHeight *line_height) const;
+
+ /** computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
+ and outputs its vital statistics into the other fields of \a span.
+ Measuring will stop if maximum_width is reached and in that case the
+ function will return false. In other cases where a line break must be
+ done immediately the function will also return false. On return
+ \a last_break_span will contain the vital statistics for the span only
+ up to the last line breaking change. If there are no line breaking
+ characters in the span then \a last_break_span will not be altered.
+ Similarly, \a last_emergency_break_span will contain the vital
+ statistics for the span up to the last inter-character boundary,
+ or will be unaltered if there is none. */
+ bool _measureUnbrokenSpan(ParagraphInfo const &para, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
+ {
+ span->setZero();
+
+ if (span->start.iter_span->dx._set && span->start.char_byte == 0)
+ span->width += span->start.iter_span->dx.computed;
+
+ if (span->start.iter_span->pango_item_index == -1) {
+ // if this is a style-only span there's no text in it
+ // so we don't need to do very much at all
+ span->end.iter_span++;
+ return true;
+ }
+
+ if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
+ InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
+ if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
+ *last_emergency_break_span = *last_break_span = *span;
+ return false;
+ }
+ if (control_code->code == ARBITRARY_GAP) {
+ if (span->width + control_code->width > maximum_width)
+ return false;
+ TRACE(("fitted control code, width = %f\n", control_code->width));
+ span->width += control_code->width;
+ span->end.increment();
+ }
+ return true;
+
+ }
+
+ if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
+ return true; // never happens
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
+
+ if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
+ // TODO: block-progression altered in the middle
+ // Measure the precomputed flow from para.input_items
+ span->end.iter_span++; // for now, skip to the next span
+ return true;
+ }
+
+ // a normal span going with a normal block-progression
+ double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
+ double soft_hyphen_glyph_width = 0.0;
+ bool soft_hyphen_in_word = false;
+ bool is_soft_hyphen = false;
+ IFTRACE(int char_count = 0);
+
+ // if we're not at the start of the span we need to pre-init glyph_index
+ span->start_glyph_index = 0;
+ while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
+ && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
+ span->start_glyph_index++;
+ span->end_glyph_index = span->start_glyph_index;
+
+ // go char-by-char summing the width, while keeping track of the previous break point
+ do {
+ PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
+
+ if (char_attributes.is_mandatory_break) {
+ *last_emergency_break_span = *last_break_span = *span;
+ TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ return false;
+ }
+
+ if (char_attributes.is_line_break) {
+ // a suitable position to break at, record where we are
+ *last_emergency_break_span = *last_break_span = *span;
+ if (soft_hyphen_in_word) {
+ // if there was a previous soft hyphen we're not going to need it any more so we can remove it
+ span->width -= soft_hyphen_glyph_width;
+ if (!is_soft_hyphen)
+ soft_hyphen_in_word = false;
+ }
+ } else if (char_attributes.is_char_break) {
+ *last_emergency_break_span = *span;
+ }
+ // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
+
+ // sum the glyph widths, letter spacing and word spacing to get the character width
+ double char_width = 0.0;
+ while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
+ && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
+ char_width += span->start.iter_span->font_size * para.pango_items[span->end.iter_span->pango_item_index].font->Advance(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].glyph, true);
+ else
+ char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
+ span->end_glyph_index++;
+ }
+ if (char_attributes.is_cursor_position)
+ char_width += text_source->style->letter_spacing.computed;
+ if (char_attributes.is_white)
+ char_width += text_source->style->word_spacing.computed;
+ span->width += char_width;
+ IFTRACE(char_count++);
+
+ if (char_attributes.is_white) {
+ span->whitespace_count++;
+ span->each_whitespace_width = char_width;
+ }
+ span->ends_with_whitespace = char_attributes.is_white;
+
+ is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
+ if (is_soft_hyphen)
+ soft_hyphen_glyph_width = char_width;
+
+ span->end.increment();
+
+ if (span->width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
+ TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ return false;
+ }
+
+ } while (span->end.char_byte != 0); // while we haven't wrapped to the next span
+ TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ return true;
+ }
+
+/* *********************************************************************************************************/
+// Per-line functions (output)
+
+ /** Uses the paragraph alignment and the chunk information to work out
+ where the actual left of the final chunk must be. Also sets
+ \a add_to_each_whitespace to be the amount of x to add at each
+ whitespace character to make full justification work. */
+ double _getChunkLeftWithAlignment(ParagraphInfo const &para, std::vector<ChunkInfo>::const_iterator it_chunk, double *add_to_each_whitespace) const
+ {
+ *add_to_each_whitespace = 0.0;
+ if (_flow._input_wrap_shapes.empty()) {
+ switch (para.alignment) {
+ case FULL:
+ case LEFT:
+ default:
+ return it_chunk->x;
+ case RIGHT:
+ return it_chunk->x - it_chunk->text_width;
+ case CENTER:
+ return it_chunk->x - it_chunk->text_width / 2;
+ }
+ }
+
+ switch (para.alignment) {
+ case FULL:
+ if (!it_chunk->broken_spans.empty()
+ && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) { // don't justify the last chunk in the para
+ if (it_chunk->whitespace_count)
+ *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
+ //else
+ //add_to_each_charspace = something
+ }
+ return it_chunk->x;
+ case LEFT:
+ default:
+ return it_chunk->x;
+ case RIGHT:
+ return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
+ case CENTER:
+ return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
+ }
+ }
+
+ /** Once we've got here we have finished making changes to the line and
+ are ready to output the final result to #_flow. This method takes its
+ input parameters and does that.
+ */
+ void _outputLine(ParagraphInfo const &para, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
+ {
+ if (chunk_info.empty()) {
+ TRACE(("line too short to fit anything on it, go to next\n"));
+ return;
+ }
+
+ // we've finished fiddling about with ascents and descents: create the output
+ TRACE(("found line fit; creating output\n"));
+ Layout::Line new_line;
+ new_line.in_paragraph = _flow._paragraphs.size() - 1;
+ new_line.baseline_y = _scanline_maker->yCoordinate() + line_height.ascent;
+ new_line.in_shape = _current_shape_index;
+ _flow._lines.push_back(new_line);
+
+ for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
+
+ double add_to_each_whitespace;
+ // add the chunk to the list
+ Layout::Chunk new_chunk;
+ new_chunk.in_line = _flow._lines.size() - 1;
+ new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
+ // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
+ if (!it_chunk->broken_spans.empty() // this one only happens for empty paragraphs
+ && it_chunk->broken_spans.front().start.char_byte == 0
+ && it_chunk->broken_spans.front().start.iter_span->y._set) {
+ // if this is the start of a line, we should change the baseline rather than each glyph individually
+ if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
+ new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
+ _flow._lines.back().baseline_y = new_line.baseline_y;
+ _y_offset = 0.0;
+ _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
+ } else
+ _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
+ }
+ _flow._chunks.push_back(new_chunk);
+
+ double x;
+ double direction_sign;
+ Direction previous_direction = para.direction;
+ double counter_directional_width_remaining = 0.0;
+ float glyph_rotate = 0.0;
+ if (para.direction == LEFT_TO_RIGHT) {
+ direction_sign = +1.0;
+ x = 0.0;
+ } else {
+ direction_sign = -1.0;
+ if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
+ x = it_chunk->scanrun_width;
+ else
+ x = it_chunk->text_width;
+ }
+
+ for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
+ // begin adding spans to the list
+ UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
+
+ if (it_span->start.char_byte == 0) {
+ // start of an unbroken span, we might have dx, dy or rotate still to process (x and y are done per chunk)
+ if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
+ if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
+ if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
+ }
+
+ if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
+ && unbroken_span.pango_item_index == -1) {
+ // style only, nothing to output
+ continue;
+ }
+
+ Layout::Span new_span;
+ double x_in_span = 0.0;
+
+ new_span.in_chunk = _flow._chunks.size() - 1;
+ new_span.line_height = unbroken_span.line_height;
+ new_span.in_input_stream_item = unbroken_span.input_index;
+ new_span.baseline_shift = _y_offset;
+ new_span.block_progression = _block_progression;
+ if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
+ new_span.font = para.pango_items[unbroken_span.pango_item_index].font;
+ new_span.font->Ref();
+ new_span.font_size = unbroken_span.font_size;
+ new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
+ new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
+ } else { // a control code
+ new_span.font = NULL;
+ new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
+ new_span.direction = para.direction;
+ }
+
+ if (new_span.direction == para.direction) {
+ x -= counter_directional_width_remaining;
+ counter_directional_width_remaining = 0.0;
+ } else if (new_span.direction != previous_direction) {
+ // measure width of spans we need to switch round
+ counter_directional_width_remaining = 0.0;
+ std::vector<BrokenSpan>::const_iterator it_following_span;
+ for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
+ Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
+ if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
+ if (it_following_span->start.iter_span->pango_item_index == -1) { // when the span came from a control code
+ if (new_span.direction != para.direction) break;
+ } else
+ if (new_span.direction != (para.pango_items[it_following_span->start.iter_span->pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT)) break;
+ }
+ counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
+ }
+ x += counter_directional_width_remaining;
+ counter_directional_width_remaining = 0.0; // we want to go increasingly negative
+ }
+ new_span.x_start = x;
+
+ if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
+ // the span is set up, push the glyphs and chars
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
+ Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
+ unsigned char_index_in_unbroken_span = it_span->start.char_index;
+ unsigned cluster_start_char_index = _flow._characters.size();
+ double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
+
+ for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
+ unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
+ if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
+ cluster_start_char_index = _flow._characters.size();
+
+ if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
+ && *iter_source_text == UNICODE_SOFT_HYPHEN
+ && it_span + 1 != it_chunk->broken_spans.end()
+ && glyph_index + 1 != it_span->end_glyph_index) {
+ // if we're looking at a soft hyphen and it's not the last glyph in the
+ // chunk we don't draw the glyph but we still need to add to _characters
+ Layout::Character new_character;
+ new_character.in_span = _flow._spans.size(); // the span hasn't been added yet, so no -1
+ new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
+ new_character.in_glyph = -1;
+ _flow._characters.push_back(new_character);
+ iter_source_text++;
+ char_index_in_unbroken_span++;
+ while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
+ && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
+ glyph_index++;
+ glyph_index--;
+ continue;
+ }
+
+ // create the Layout::Glyph
+ Layout::Glyph new_glyph;
+ new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
+ new_glyph.in_character = cluster_start_char_index;
+ new_glyph.rotation = glyph_rotate;
+
+ /* put something like this back in when we do glyph-rotation-horizontal/vertical
+ if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
+ new_glyph.x += new_span.line_height.ascent;
+ new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
+ new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
+ } else */
+
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
+ new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
+ new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
+ new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true);
+ } else {
+ new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
+ new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
+ new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
+ if (new_glyph.width == 0)
+ new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false);
+ // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
+ }
+ if (new_span.direction == RIGHT_TO_LEFT) {
+ // pango wanted to give us glyphs in visual order but we refused, so we need to work
+ // out where the cluster start is ourselves
+ double cluster_width = 0.0;
+ for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
+ if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
+ break;
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
+ cluster_width += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[rtl_index].glyph, true);
+ else
+ cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
+ }
+ new_glyph.x -= cluster_width;
+ }
+ _flow._glyphs.push_back(new_glyph);
+
+ // create the Layout::Character(s)
+ double advance_width = new_glyph.width;
+ unsigned end_byte;
+ if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
+ end_byte = it_span->start.iter_span->text_bytes;
+ else {
+ // output chars for the whole cluster that is commenced by this glyph
+ if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
+ int next_cluster_glyph_index = glyph_index + 1;
+ while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
+ && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
+ next_cluster_glyph_index++;
+ if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
+ end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
+ else
+ end_byte = it_span->start.iter_span->text_bytes;
+ } else
+ end_byte = char_byte; // don't output any chars if we're not at the start of a cluster
+ }
+ while (char_byte < end_byte) {
+ Layout::Character new_character;
+ new_character.in_span = _flow._spans.size();
+ new_character.x = x_in_span;
+ new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
+ new_character.in_glyph = _flow._glyphs.size() - 1;
+ _flow._characters.push_back(new_character);
+ if (new_character.char_attributes.is_white)
+ advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification
+ if (new_character.char_attributes.is_cursor_position)
+ advance_width += text_source->style->letter_spacing.computed;
+ iter_source_text++;
+ char_index_in_unbroken_span++;
+ char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
+ glyph_rotate = 0.0;
+ }
+
+ advance_width *= direction_sign;
+ if (new_span.direction != para.direction) {
+ counter_directional_width_remaining -= advance_width;
+ x -= advance_width;
+ x_in_span -= advance_width;
+ } else {
+ x += advance_width;
+ x_in_span += advance_width;
+ }
+ }
+ } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
+ x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
+ }
+
+ new_span.x_end = new_span.x_start + x_in_span;
+ _flow._spans.push_back(new_span);
+ previous_direction = new_span.direction;
+ }
+ // end adding spans to the list, on to the next chunk...
+ }
+ TRACE(("output done\n"));
+ }
+
+/* *********************************************************************************************************/
+// Setup and top-level functions
+
+ /** initialises the ScanlineMaker for the first shape in the flow, or
+ the infinite version if we're not doing wrapping. */
+ void _createFirstScanlineMaker()
+ {
+ _current_shape_index = 0;
+ if (_flow._input_wrap_shapes.empty()) {
+ // create the special no-wrapping infinite scanline maker
+ double initial_x = 0, initial_y = 0;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
+ if (!text_source->x.empty())
+ initial_x = text_source->x.front().computed;
+ if (!text_source->y.empty())
+ initial_y = text_source->y.front().computed;
+ _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
+ TRACE((" wrapping disabled\n"));
+ }
+ else {
+ _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
+ TRACE((" begin wrap shape 0\n"));
+ }
+ }
+
+public:
+ Calculator(Layout *text_flow)
+ : _flow(*text_flow) {}
+
+ bool calculate();
+};
+
+/* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
+ * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
+ * that does delete or Unref.
+ *
+ * I suggest using the garbage collector to manage deletion.
+ */
+void Layout::Calculator::InputItemInfo::free()
+{
+ if (sub_flow) {
+ delete sub_flow;
+ sub_flow = NULL;
+ }
+}
+
+void Layout::Calculator::PangoItemInfo::free()
+{
+ if (item) {
+ pango_item_free(item);
+ item = NULL;
+ }
+ if (font) {
+ font->Unref();
+ font = NULL;
+ }
+}
+
+void Layout::Calculator::UnbrokenSpanPosition::increment()
+{
+ gchar const *text_base = &*iter_span->input_stream_first_character.base();
+ char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
+ char_index++;
+ if (char_byte == iter_span->text_bytes) {
+ iter_span++;
+ char_index = char_byte = 0;
+ }
+}
+
+void Layout::Calculator::BrokenSpan::setZero()
+{
+ end = start;
+ width = 0.0;
+ whitespace_count = 0;
+ end_glyph_index = start_glyph_index = 0;
+ ends_with_whitespace = false;
+ each_whitespace_width = 0.0;
+}
+
+template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
+{
+ for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
+ it->free();
+ }
+ seq.clear();
+}
+
+void Layout::Calculator::ParagraphInfo::free()
+{
+ free_sequence(input_items);
+ free_sequence(pango_items);
+ free_sequence(unbroken_spans);
+}
+
+///**
+// * For sections of text with a block-progression different to the rest
+// * of the flow, the best thing to do is to detect them in advance and
+// * create child TextFlow objects with just the rotated text. In the
+// * parent we then effectively use ARBITRARY_GAP fields during the
+// * flowing (because we don't allow wrapping when the block-progression
+// * changes) and copy the actual text in during the output phase.
+// *
+// * NB: this code not enabled yet.
+// */
+//void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
+//{
+// Direction prev_block_progression = _block_progression;
+// int run_start_input_index = para->first_input_index;
+//
+// para->free_sequence(para->input_items);
+// for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
+// InputItemInfo input_item;
+//
+// input_item.in_sub_flow = false;
+// input_item.sub_flow = NULL;
+// if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+// if ( control_code->code == SHAPE_BREAK
+// || control_code->code == PARAGRAPH_BREAK)
+// break; // stop at the end of the paragraph
+// // all other control codes we'll pick up later
+//
+// } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
+// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
+// Direction this_block_progression = text_source->styleGetBlockProgression();
+// if (this_block_progression != prev_block_progression) {
+// if (prev_block_progression != _block_progression) {
+// // need to back up so that control codes belong outside the block-progression change
+// int run_end_input_index = input_index - 1;
+// while (run_end_input_index > run_start_input_index
+// && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
+// run_end_input_index--;
+// // now create the sub-flow
+// input_item.sub_flow = new Layout;
+// for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
+// input_item.in_sub_flow = true;
+// if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
+// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
+// input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
+// } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
+// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
+// input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source_cookie, NULL, 0, text_source->text_begin, text_source->text_end);
+// Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
+// sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call
+// sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect
+// sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway)
+// sub_flow_text_source->dy = text_source->dy;
+// sub_flow_text_source->rotate = text_source->rotate;
+// }
+// }
+// input_item.sub_flow->calculateFlow();
+// }
+// run_start_input_index = input_index;
+// }
+// prev_block_progression = this_block_progression;
+// }
+// para->input_items.push_back(input_item);
+// }
+//}
+
+/**
+ * Take all the text from \a _para.first_input_index to the end of the
+ * paragraph and stitch it together so that pango_itemize() can be called on
+ * the whole thing.
+ *
+ * Input: para.first_input_index.
+ * Output: para.direction, para.pango_items, para.char_attributes.
+ */
+void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
+{
+ Glib::ustring para_text;
+ PangoAttrList *attributes_list;
+ unsigned input_index;
+
+ para->free_sequence(para->pango_items);
+ para->char_attributes.clear();
+
+ TRACE(("itemizing para, first input %d\n", para->first_input_index));
+
+ attributes_list = pango_attr_list_new();
+ for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
+ if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+ Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+ if ( control_code->code == SHAPE_BREAK
+ || control_code->code == PARAGRAPH_BREAK)
+ break; // stop at the end of the paragraph
+ // all other control codes we'll pick up later
+
+ } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
+ Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
+
+ // create the font_instance
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font == NULL)
+ continue; // bad news: we'll have to ignore all this text because we know of no font to render it
+
+ PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
+ attribute_font_description->start_index = para_text.bytes();
+ para_text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text
+ attribute_font_description->end_index = para_text.bytes();
+ pango_attr_list_insert(attributes_list, attribute_font_description);
+ // ownership of attribute is assumed by the list
+ }
+ }
+
+ TRACE(("whole para: \"%s\"\n", para_text.data()));
+ TRACE(("%d input sources used\n", input_index - para->first_input_index));
+
+ // do the pango_itemize()
+ GList *pango_items_glist = NULL;
+ if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
+ Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
+ if (text_source->style->direction.set) {
+ PangoDirection pango_direction = (PangoDirection)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_pango_direction, sizeof(enum_convert_spstyle_direction_to_pango_direction)/sizeof(enum_convert_spstyle_direction_to_pango_direction[0]));
+ pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
+ para->direction = (Layout::Direction)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_my_direction, sizeof(enum_convert_spstyle_direction_to_my_direction)/sizeof(enum_convert_spstyle_direction_to_my_direction[0]));
+ }
+ }
+ if (pango_items_glist == NULL) { // no direction specified, guess it
+ pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
+
+ // I think according to the css spec this is wrong and we're never allowed to guess the directionality
+ // of a paragraph. Need to talk to an rtl speaker.
+ if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
+ else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
+ }
+ pango_attr_list_unref(attributes_list);
+
+ // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
+ para->pango_items.reserve(g_list_length(pango_items_glist));
+ TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
+ for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
+ PangoItemInfo new_item;
+ new_item.item = (PangoItem*)current_pango_item->data;
+ PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
+ new_item.font = (font_factory::Default())->Face(font_description);
+ pango_font_description_free(font_description); // Face() makes a copy
+ para->pango_items.push_back(new_item);
+ }
+ g_list_free(pango_items_glist);
+
+ // and get the character attributes on everything
+ para->char_attributes.resize(para_text.length() + 1);
+ pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
+
+ TRACE(("end para itemize, direction = %d\n", para->direction));
+}
+
+/**
+ * Gets the ascent, descent and leading for a font and the alteration that has to be performed
+ * according to the value specified by the line-height css property. The result of multiplying
+ * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
+ * section 10.8.
+ */
+void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
+ SPStyle const *style, LineHeight *line_height,
+ double *line_height_multiplier)
+{
+ if (font == NULL) {
+ line_height->setZero();
+ *line_height_multiplier = 1.0;
+ }
+ font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
+ *line_height *= font_size;
+
+ // yet another borked SPStyle member that we're going to have to fix ourselves
+ for ( ; ; ) {
+ if (style->line_height.set && !style->line_height.inherit) {
+ if (style->line_height.normal)
+ break;
+ switch (style->line_height.unit) {
+ case SP_CSS_UNIT_NONE:
+ *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
+ return;
+ case SP_CSS_UNIT_EX:
+ *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
+ // 0.5 is an approximation of the x-height. Fixme.
+ return;
+ case SP_CSS_UNIT_EM:
+ case SP_CSS_UNIT_PERCENT:
+ *line_height_multiplier = style->line_height.value * font_size / line_height->total();
+ return;
+ default: // absolute values
+ *line_height_multiplier = style->line_height.computed / line_height->total();
+ return;
+ }
+ break;
+ }
+ if (style->object->parent == NULL) break;
+ style = style->object->parent->style;
+ if (style == NULL) break;
+ }
+ *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
+}
+
+/**
+ * Split the paragraph into spans. Also call pango_shape() on them.
+ *
+ * Input: para->first_input_index, para->pango_items
+ * Output: para->spans
+ * Returns: the index of the beginning of the following paragraph in _flow._input_stream
+ */
+unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
+{
+ unsigned pango_item_index = 0;
+ unsigned char_index_in_para = 0;
+ unsigned byte_index_in_para = 0;
+ unsigned input_index;
+
+ TRACE(("build spans\n"));
+ para->free_sequence(para->unbroken_spans);
+
+ for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
+ if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+ Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+ if ( control_code->code == SHAPE_BREAK
+ || control_code->code == PARAGRAPH_BREAK)
+ break; // stop at the end of the paragraph
+ else if (control_code->code == ARBITRARY_GAP) {
+ UnbrokenSpan new_span;
+ new_span.pango_item_index = -1;
+ new_span.input_index = input_index;
+ new_span.line_height.ascent = control_code->ascent;
+ new_span.line_height.descent = control_code->descent;
+ new_span.line_height.leading = 0.0;
+ new_span.text_bytes = 0;
+ new_span.char_index_in_para = char_index_in_para;
+ para->unbroken_spans.push_back(new_span);
+ TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
+ }
+ } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
+ Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
+ unsigned char_index_in_source = 0;
+
+ unsigned span_start_byte_in_source = 0;
+ // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
+ for ( ; ; ) {
+ /* we need to change spans at every change of PangoItem, source stream change,
+ or change in one of the attributes altering position/rotation. */
+
+ unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
+ ? 0
+ : ( para->pango_items[pango_item_index].item->offset
+ + para->pango_items[pango_item_index].item->length
+ - byte_index_in_para ) );
+ unsigned const text_source_bytes = ( text_source->text_end.base()
+ - text_source->text_begin.base()
+ - span_start_byte_in_source );
+ UnbrokenSpan new_span;
+ new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
+ new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
+ new_span.char_index_in_para = char_index_in_para + char_index_in_source;
+ new_span.input_index = input_index;
+
+ // cut at <tspan> attribute changes as well
+ new_span.x._set = false;
+ new_span.y._set = false;
+ new_span.dx._set = false;
+ new_span.dy._set = false;
+ new_span.rotate._set = false;
+ if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
+ if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source];
+ if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source];
+ if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
+ if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
+ } else {
+ if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source];
+ if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source];
+ if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
+ if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
+ }
+ if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
+ if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
+ // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
+ // so that the top of the letters is at zero, not the baseline
+ new_span.y = 0.0;
+ }
+ Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
+ iter_text++;
+ for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
+ if (iter_text >= text_source->text_end) break;
+ if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
+ if ( i >= text_source->x.size() && i >= text_source->y.size()
+ && i >= text_source->dx.size() && i >= text_source->dy.size()
+ && i >= text_source->rotate.size()) break;
+ if ( (text_source->x.size() > i && text_source->x[i]._set)
+ || (text_source->y.size() > i && text_source->y[i]._set)
+ || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
+ || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
+ || (text_source->rotate.size() > i && text_source->rotate[i]._set
+ && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
+ new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
+ break;
+ }
+ }
+
+ // now we know the length, do some final calculations and add the UnbrokenSpan to the list
+ new_span.font_size = text_source->styleComputeFontSize();
+ if (new_span.text_bytes) {
+ int const original_bidi_level = para->pango_items[pango_item_index].item->analysis.level;
+ para->pango_items[pango_item_index].item->analysis.level = 0;
+ // pango_shape() will reorder glyphs in rtl sections which messes us up because
+ // the svg spec requires us to draw glyphs in character order
+ new_span.glyph_string = pango_glyph_string_new();
+ /* Some assertions intended to help diagnose bug #1277746. */
+ g_assert( 0 < new_span.text_bytes );
+ g_assert( span_start_byte_in_source < text_source->text->bytes() );
+ g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
+ g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
+ == NULL );
+ pango_shape(text_source->text->data() + span_start_byte_in_source,
+ new_span.text_bytes,
+ &para->pango_items[pango_item_index].item->analysis,
+ new_span.glyph_string);
+ para->pango_items[pango_item_index].item->analysis.level = original_bidi_level;
+ new_span.pango_item_index = pango_item_index;
+ _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
+ // TODO: metrics for vertical text
+ TRACE(("add text span %d \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
+ TRACE((" %d glyphs\n", new_span.glyph_string->num_glyphs));
+ } else {
+ // if there's no text we still need to initialise the styles
+ new_span.pango_item_index = -1;
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font) {
+ _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
+ font->Unref();
+ } else {
+ new_span.line_height.setZero();
+ new_span.line_height_multiplier = 1.0;
+ }
+ TRACE(("add style init span %d\n", para->unbroken_spans.size()));
+ }
+ para->unbroken_spans.push_back(new_span);
+
+ // calculations for moving to the next UnbrokenSpan
+ byte_index_in_para += new_span.text_bytes;
+ char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
+
+ if (new_span.text_bytes >= pango_item_bytes) { // end of pango item
+ pango_item_index++;
+ if (pango_item_index == para->pango_items.size()) break; // end of paragraph
+ }
+ if (new_span.text_bytes == text_source_bytes)
+ break; // end of source
+ // else <tspan> attribute changed
+ span_start_byte_in_source += new_span.text_bytes;
+ }
+ char_index_in_para += char_index_in_source;
+ }
+ }
+ TRACE(("end build spans\n"));
+ return input_index;
+}
+
+/**
+ * Reinitialises the variables required on completion of one shape and
+ * moving on to the next. Returns false if there are no more shapes to wrap
+ * in to.
+ */
+bool Layout::Calculator::_goToNextWrapShape()
+{
+ delete _scanline_maker;
+ _scanline_maker = NULL;
+ _current_shape_index++;
+ if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
+ _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
+ TRACE(("begin wrap shape %d\n", _current_shape_index));
+ return true;
+}
+
+/**
+ * Given \a para filled in and \a start_span_pos set, keeps trying to
+ * find somewhere it can fit the next line of text. The process of finding
+ * the text that fits will involve creating one or more entries in
+ * \a chunk_info describing the bounds of the fitted text and several
+ * bits of information that will prove useful when we come to output the
+ * line to #_flow. Returns with \a start_span_pos set to the end of the
+ * text that was fitted, \a chunk_info completely filled out and
+ * \a line_height set to the largest line box on the line. The return
+ * value is false only if we've run out of shapes to wrap inside (and
+ * hence couldn't create any chunks).
+ */
+bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
+ UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info,
+ LineHeight *line_height)
+{
+ // init the initial line_height
+ if (start_span_pos->iter_span == para.unbroken_spans.end()) {
+ if (_flow._spans.empty()) {
+ // empty first para: create a font for the sole purpose of measuring it
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font) {
+ double font_size = text_source->styleComputeFontSize();
+ double multiplier;
+ _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
+ font->Unref();
+ *line_height *= multiplier;
+ _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
+ }
+ }
+ // else empty subsequent para: keep the old line height
+ } else {
+ if (_flow._input_wrap_shapes.empty()) {
+ // if we're not wrapping set the line_height big and negative so we can use negative line height
+ line_height->ascent = -1.0e10;
+ line_height->descent = -1.0e10;
+ line_height->leading = -1.0e10;
+ }
+ else
+ line_height->setZero();
+ }
+
+ UnbrokenSpanPosition span_pos;
+ for( ; ; ) {
+ std::vector<ScanlineMaker::ScanRun> scan_runs;
+ scan_runs = _scanline_maker->makeScanline(*line_height);
+ while (scan_runs.empty()) {
+ if (!_goToNextWrapShape()) return false; // no more shapes to wrap in to
+ scan_runs = _scanline_maker->makeScanline(*line_height);
+ }
+
+ TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
+ chunk_info->clear();
+ chunk_info->reserve(scan_runs.size());
+ if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
+ unsigned scan_run_index;
+ span_pos = *start_span_pos;
+ for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
+ if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
+ break;
+ if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
+ span_pos = chunk_info->back().broken_spans.back().end;
+ }
+ if (scan_run_index == scan_runs.size()) break; // ie when buildChunksInScanRun() succeeded
+ }
+ *start_span_pos = span_pos;
+ return true;
+}
+
+/**
+ * Given a scan run and a first character, append one or more chunks to
+ * the \a chunk_info vector that describe all the spans and other detail
+ * necessary to output the greatest amount of text that will fit on this scan
+ * line (greedy line breaking algorithm). Each chunk contains one or more
+ * BrokenSpan structures that link back to UnbrokenSpan structures that link
+ * to the text itself. Normally there will be either one or zero (if the
+ * scanrun is too short to fit any text) chunk added to \a chunk_info by
+ * each call to this method, but we will add more than one if an x or y
+ * attribute has been set on a tspan. \a line_height must be set on input,
+ * and if it needs to be made larger and the #_scanline_maker can't do
+ * an in-situ resize then it will be set to the required value and the
+ * method will return false.
+ */
+bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &start_span_pos,
+ ScanlineMaker::ScanRun const &scan_run,
+ std::vector<ChunkInfo> *chunk_info,
+ LineHeight *line_height) const
+{
+ ChunkInfo new_chunk;
+ new_chunk.text_width = 0.0;
+ new_chunk.whitespace_count = 0;
+ new_chunk.scanrun_width = scan_run.width();
+ new_chunk.x = scan_run.x_start;
+
+ // we haven't done anything yet so the last valid break position is the beginning
+ BrokenSpan last_span_at_break, last_span_at_emergency_break;
+ last_span_at_break.start = start_span_pos;
+ last_span_at_break.setZero();
+ last_span_at_emergency_break.start = start_span_pos;
+ last_span_at_emergency_break.setZero();
+
+ TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
+ BrokenSpan new_span;
+ new_span.end = start_span_pos;
+ while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan
+
+ new_span.start = new_span.end;
+
+ // force a chunk change at x or y attribute change
+ if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
+
+ if (new_span.start.iter_span != start_span_pos.iter_span)
+ chunk_info->push_back(new_chunk);
+
+ new_chunk.x += new_chunk.text_width;
+ new_chunk.text_width = 0.0;
+ new_chunk.whitespace_count = 0;
+ new_chunk.broken_spans.clear();
+ if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
+ // y doesn't need to be done until output time
+ }
+
+ // see if this span is too tall to fit on the current line
+ LineHeight total_height = new_span.start.iter_span->line_height;
+ total_height *= new_span.start.iter_span->line_height_multiplier;
+ /* floating point 80-bit/64-bit rounding problems require epsilon. See
+ discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
+ if ( total_height.ascent > line_height->ascent + FLT_EPSILON
+ || total_height.descent > line_height->descent + FLT_EPSILON
+ || total_height.leading > line_height->leading + FLT_EPSILON) {
+ line_height->max(total_height);
+ if (!_scanline_maker->canExtendCurrentScanline(*line_height))
+ return false;
+ }
+
+ bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
+
+ new_chunk.text_width += new_span.width;
+ new_chunk.whitespace_count += new_span.whitespace_count;
+ new_chunk.broken_spans.push_back(new_span); // if !span_fitted we'll correct ourselves below
+
+ if (!span_fitted) break;
+
+ if (new_span.end.iter_span == para.unbroken_spans.end()) {
+ last_span_at_break = new_span;
+ break;
+ }
+ }
+
+ TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
+ chunk_info->push_back(new_chunk);
+
+ if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
+ /* **non-SVG spec bit**: See bug #1191102
+ If the user types a very long line with no spaces, the way the spec
+ is written at the moment means that when the length of the text
+ exceeds the available width of all remaining areas, the text is
+ completely hidden. This condition alters that behaviour so that if
+ the length of the line is greater than four times the line-height
+ and there are no spaces, it'll be emergency-wrapped at the last
+ character. One could read the SVG Tiny 1.2 draft as permitting this
+ sort of behaviour, but it's still a bit dodgy. The hard-coding of
+ 4x is not nice, either. */
+ last_span_at_break = last_span_at_emergency_break;
+ }
+
+ if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
+ // need to back out spans until we come to the one with the last break in it
+ while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
+ chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
+ chunk_info->back().broken_spans.pop_back();
+ if (chunk_info->back().broken_spans.empty())
+ chunk_info->pop_back();
+ }
+ if (!chunk_info->empty()) {
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
+ chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
+ if (last_span_at_break.start == last_span_at_break.end) {
+ chunk_info->back().broken_spans.pop_back(); // last break was at an existing boundary
+ if (chunk_info->back().broken_spans.empty())
+ chunk_info->pop_back();
+ } else {
+ chunk_info->back().broken_spans.back() = last_span_at_break;
+ chunk_info->back().text_width += last_span_at_break.width;
+ chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
+ }
+ TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
+ }
+ }
+
+ if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
+ // for justification we need to discard space occupied by the single whitespace at the end of the chunk
+ chunk_info->back().broken_spans.back().ends_with_whitespace = false;
+ chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
+ chunk_info->back().broken_spans.back().whitespace_count--;
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
+ chunk_info->back().whitespace_count--;
+ }
+
+ return true;
+}
+
+/** The management function to start the whole thing off. */
+bool Layout::Calculator::calculate()
+{
+ if (_flow._input_stream.empty())
+ return false;
+ g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
+ if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
+ return false;
+
+ TRACE(("begin calculateFlow()\n"));
+
+ _flow._clearOutputObjects();
+
+ _pango_context = (font_factory::Default())->fontContext;
+ _font_factory_size_multiplier = (font_factory::Default())->fontSize;
+
+ _block_progression = _flow._blockProgression();
+ _y_offset = 0.0;
+ _createFirstScanlineMaker();
+
+ ParagraphInfo para;
+ LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
+ for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
+ // jump to the next wrap shape if this is a SHAPE_BREAK control code
+ if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
+ InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
+ if (control_code->code == SHAPE_BREAK) {
+ TRACE(("shape break control code\n"));
+ if (!_goToNextWrapShape()) break;
+ continue;
+ }
+ }
+ if (_scanline_maker == NULL)
+ break; // we're trying to flow past the last wrap shape
+
+ _buildPangoItemizationForPara(&para);
+ unsigned para_end_input_index = _buildSpansForPara(&para);
+
+ if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
+ para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
+ else
+ para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+
+ TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
+ Layout::Paragraph new_paragraph;
+ new_paragraph.base_direction = para.direction;
+ new_paragraph.alignment = para.alignment;
+ _flow._paragraphs.push_back(new_paragraph);
+
+ // start scanning lines
+ UnbrokenSpanPosition span_pos;
+ span_pos.iter_span = para.unbroken_spans.begin();
+ span_pos.char_byte = 0;
+ span_pos.char_index = 0;
+
+ do { // for each line in the paragraph
+ TRACE(("begin line\n"));
+ std::vector<ChunkInfo> line_chunk_info;
+ if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
+ break; // out of shapes to wrap in to
+
+ _outputLine(para, line_height, line_chunk_info);
+ _scanline_maker->completeLine();
+ } while (span_pos.iter_span != para.unbroken_spans.end());
+
+ TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
+ if (_scanline_maker != NULL) {
+ bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
+ if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
+ || para_end_input_index + 1 < _flow._input_stream.size()) {
+ // we need a span just for the para if it's either an empty last para or a break in the middle
+ Layout::Span new_span;
+ if (_flow._spans.empty()) {
+ new_span.font = NULL;
+ new_span.font_size = line_height.ascent + line_height.descent;
+ new_span.line_height = line_height;
+ new_span.x_end = 0.0;
+ } else {
+ new_span = _flow._spans.back();
+ if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
+ new_span.x_end = 0.0;
+ }
+ new_span.in_chunk = _flow._chunks.size() - 1;
+ if (new_span.font)
+ new_span.font->Ref();
+ new_span.x_start = new_span.x_end;
+ new_span.baseline_shift = 0.0;
+ new_span.direction = para.direction;
+ new_span.block_progression = _block_progression;
+ if (para_end_input_index == _flow._input_stream.size())
+ new_span.in_input_stream_item = _flow._input_stream.size() - 1;
+ else
+ new_span.in_input_stream_item = para_end_input_index;
+ _flow._spans.push_back(new_span);
+ }
+ if (para_end_input_index + 1 < _flow._input_stream.size()) {
+ // we've got to add an invisible character between paragraphs so that we can position iterators
+ // (and hence cursors) both before and after the paragraph break
+ Layout::Character new_character;
+ new_character.in_span = _flow._spans.size() - 1;
+ new_character.char_attributes.is_line_break = 1;
+ new_character.char_attributes.is_mandatory_break = 1;
+ new_character.char_attributes.is_char_break = 1;
+ new_character.char_attributes.is_white = 1;
+ new_character.char_attributes.is_cursor_position = 1;
+ new_character.char_attributes.is_word_start = 0;
+ new_character.char_attributes.is_word_end = 1;
+ new_character.char_attributes.is_sentence_start = 0;
+ new_character.char_attributes.is_sentence_end = 1;
+ new_character.char_attributes.is_sentence_boundary = 1;
+ new_character.char_attributes.backspace_deletes_character = 1;
+ new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
+ new_character.in_glyph = -1;
+ _flow._characters.push_back(new_character);
+ }
+ }
+ para.free();
+ para.first_input_index = para_end_input_index + 1;
+ }
+
+ para.free();
+ if (_scanline_maker)
+ delete _scanline_maker;
+
+ return true;
+}
+
+void Layout::_calculateCursorShapeForEmpty()
+{
+ _empty_cursor_shape.position = NR::Point(0, 0);
+ _empty_cursor_shape.height = 0.0;
+ _empty_cursor_shape.rotation = 0.0;
+ if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
+ return;
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
+
+ font_instance *font = text_source->styleGetFontInstance();
+ double font_size = text_source->styleComputeFontSize();
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ LineHeight line_height;
+ if (font) {
+ const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
+ font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
+ line_height *= font_size;
+ font->Unref();
+ } else {
+ line_height.ascent = font_size * 0.85; // random guesses
+ line_height.descent = font_size * 0.15;
+ line_height.leading = 0.0;
+ }
+ double caret_slope = atan2(caret_slope_run, caret_slope_rise);
+ _empty_cursor_shape.height = font_size / cos(caret_slope);
+ _empty_cursor_shape.rotation = caret_slope;
+
+ if (_input_wrap_shapes.empty()) {
+ _empty_cursor_shape.position = NR::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
+ text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
+ } else {
+ Direction block_progression = text_source->styleGetBlockProgression();
+ ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
+ std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
+ if (!scan_runs.empty()) {
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
+ _empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
+ else
+ _empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
+ }
+ }
+}
+
+bool Layout::calculateFlow()
+{
+ bool result = Calculator(this).calculate();
+ if (_characters.empty())
+ _calculateCursorShapeForEmpty();
+ return result;
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp
new file mode 100755
index 000000000..8b695af04
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Input.cpp
@@ -0,0 +1,270 @@
+/*
+ * Inkscape::Text::Layout - text layout engine input functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "style.h"
+#include "svg/svg-length.h"
+#include "sp-object.h"
+#include "FontFactory.h"
+
+namespace Inkscape {
+namespace Text {
+
+void Layout::_clearInputObjects()
+{
+ for(std::vector<InputStreamItem*>::iterator it = _input_stream.begin() ; it != _input_stream.end() ; it++)
+ delete *it;
+ _input_stream.clear();
+ _input_wrap_shapes.clear();
+}
+
+// this function does nothing more than store all its parameters for future reference
+void Layout::appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end)
+{
+ if (style == NULL) return;
+
+ InputStreamTextSource *new_source = new InputStreamTextSource;
+
+ new_source->source_cookie = source_cookie;
+ new_source->text = &text;
+ new_source->text_begin = text_begin;
+ new_source->text_end = text_end;
+ new_source->style = style;
+ sp_style_ref(style);
+
+ new_source->text_length = 0;
+ for ( ; text_begin != text_end && text_begin != text.end() ; text_begin++)
+ new_source->text_length++; // save this because calculating the length of a UTF-8 string is expensive
+
+ if (optional_attributes) {
+ // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly
+ _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length));
+ _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length));
+ _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length);
+ _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length);
+ _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length);
+ }
+
+ _input_stream.push_back(new_source);
+}
+
+void Layout::_copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length)
+{
+ output_vector->clear();
+ if (input_offset >= input_vector.size()) return;
+ output_vector->reserve(std::min(max_length, input_vector.size() - input_offset));
+ while (input_offset < input_vector.size() && max_length != 0) {
+ if (!input_vector[input_offset]._set)
+ break;
+ output_vector->push_back(input_vector[input_offset]);
+ input_offset++;
+ max_length--;
+ }
+}
+
+// just save what we've been given, really
+void Layout::appendControlCode(TextControlCode code, void *source_cookie, double width, double ascent, double descent)
+{
+ InputStreamControlCode *new_code = new InputStreamControlCode;
+
+ new_code->source_cookie = source_cookie;
+ new_code->code = code;
+ new_code->width = width;
+ new_code->ascent = ascent;
+ new_code->descent = descent;
+
+ _input_stream.push_back(new_code);
+}
+
+// more saving of the parameters
+void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align)
+{
+ _input_wrap_shapes.push_back(InputWrapShape());
+ _input_wrap_shapes.back().shape = shape;
+ _input_wrap_shapes.back().display_align = display_align;
+}
+
+int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size)
+{
+ for (unsigned i = 0 ; i < conversion_table_size ; i++)
+ if (conversion_table[i].input == input)
+ return conversion_table[i].output;
+ return conversion_table[0].output;
+}
+
+// ***** the style format interface
+// this doesn't include all accesses to SPStyle, only the ones that are non-trivial
+
+static const float medium_font_size = 12.0; // more of a default if all else fails than anything else
+float Layout::InputStreamTextSource::styleComputeFontSize() const
+{
+ return style->font_size.computed;
+
+ // in case the computed value's not good enough, here's some manual code held in reserve:
+ SPStyle const *this_style = style;
+ float inherit_multiplier = 1.0;
+
+ for ( ; ; ) {
+ if (this_style->font_size.set && !this_style->font_size.inherit) {
+ switch (this_style->font_size.type) {
+ case SP_FONT_SIZE_LITERAL: {
+ switch(this_style->font_size.value) { // these multipliers are straight out of the CSS spec
+ case SP_CSS_FONT_SIZE_XX_SMALL: return medium_font_size * inherit_multiplier * (3.0/5.0);
+ case SP_CSS_FONT_SIZE_X_SMALL: return medium_font_size * inherit_multiplier * (3.0/4.0);
+ case SP_CSS_FONT_SIZE_SMALL: return medium_font_size * inherit_multiplier * (8.0/9.0);
+ default:
+ case SP_CSS_FONT_SIZE_MEDIUM: return medium_font_size * inherit_multiplier;
+ case SP_CSS_FONT_SIZE_LARGE: return medium_font_size * inherit_multiplier * (6.0/5.0);
+ case SP_CSS_FONT_SIZE_X_LARGE: return medium_font_size * inherit_multiplier * (3.0/2.0);
+ case SP_CSS_FONT_SIZE_XX_LARGE: return medium_font_size * inherit_multiplier * 2.0;
+ case SP_CSS_FONT_SIZE_SMALLER: inherit_multiplier *= 0.84; break; //not exactly according to spec
+ case SP_CSS_FONT_SIZE_LARGER: inherit_multiplier *= 1.26; break; //not exactly according to spec
+ }
+ break;
+ }
+ case SP_FONT_SIZE_PERCENTAGE: { // 'em' units should be in here, but aren't. Fix in style.cpp.
+ inherit_multiplier *= this_style->font_size.value;
+ break;
+ }
+ case SP_FONT_SIZE_LENGTH: {
+ return this_style->font_size.value * inherit_multiplier;
+ }
+ }
+ }
+ if (this_style->object->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return medium_font_size * inherit_multiplier;
+}
+
+static const Layout::EnumConversionItem enum_convert_spstyle_block_progression_to_direction[] = {
+ {SP_CSS_BLOCK_PROGRESSION_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_BLOCK_PROGRESSION_LR, Layout::LEFT_TO_RIGHT},
+ {SP_CSS_BLOCK_PROGRESSION_RL, Layout::RIGHT_TO_LEFT}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_writing_mode_to_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_WRITING_MODE_RL_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_WRITING_MODE_TB_RL, Layout::RIGHT_TO_LEFT},
+ {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};
+
+Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
+{
+ // this function shouldn't be necessary, but since style.cpp doesn't support
+ // shorthand properties yet, it is.
+ SPStyle const *this_style = style;
+
+ for ( ; ; ) {
+ if (this_style->block_progression.set)
+ return (Layout::Direction)_enum_converter(this_style->block_progression.computed, enum_convert_spstyle_block_progression_to_direction, sizeof(enum_convert_spstyle_block_progression_to_direction)/sizeof(enum_convert_spstyle_block_progression_to_direction[0]));
+ if (this_style->writing_mode.set)
+ return (Layout::Direction)_enum_converter(this_style->writing_mode.computed, enum_convert_spstyle_writing_mode_to_direction, sizeof(enum_convert_spstyle_writing_mode_to_direction)/sizeof(enum_convert_spstyle_writing_mode_to_direction[0]));
+ if (this_style->object->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return TOP_TO_BOTTOM;
+
+}
+
+static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction para_direction)
+{
+ switch (anchor) {
+ default:
+ case SP_CSS_TEXT_ANCHOR_START: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::LEFT : Layout::RIGHT;
+ case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER;
+ case SP_CSS_TEXT_ANCHOR_END: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::RIGHT : Layout::LEFT;
+ }
+}
+
+Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const
+{
+ if (!try_text_align)
+ return text_anchor_to_alignment(style->text_anchor.computed, para_direction);
+
+ // there's no way to tell the difference between text-anchor set higher up the cascade to the default and
+ // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align
+ // to use we'll have to run up the style tree ourselves.
+ SPStyle const *this_style = style;
+
+ for ( ; ; ) {
+ // If both text-align and text-anchor are set at the same level, text-align takes
+ // precedence because it is the most expressive.
+ if (this_style->text_align.set) {
+ switch (style->text_align.computed) {
+ default:
+ case SP_CSS_TEXT_ALIGN_START: return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+ case SP_CSS_TEXT_ALIGN_END: return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT;
+ case SP_CSS_TEXT_ALIGN_LEFT: return LEFT;
+ case SP_CSS_TEXT_ALIGN_RIGHT: return RIGHT;
+ case SP_CSS_TEXT_ALIGN_CENTER: return CENTER;
+ case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL;
+ }
+ }
+ if (this_style->text_anchor.set)
+ return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction);
+ if (this_style->object->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+}
+
+static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = {
+ {SP_CSS_FONT_STYLE_NORMAL, PANGO_STYLE_NORMAL},
+ {SP_CSS_FONT_STYLE_ITALIC, PANGO_STYLE_ITALIC},
+ {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = {
+ {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL},
+ {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_ULTRALIGHT},
+ {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT},
+ {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT},
+ {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL},
+ {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_SEMIBOLD},
+ {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD},
+ {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = {
+ {SP_CSS_FONT_STRETCH_NORMAL, PANGO_STRETCH_NORMAL},
+ {SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_CONDENSED},
+ {SP_CSS_FONT_STRETCH_EXTRA_CONDENSED, PANGO_STRETCH_EXTRA_CONDENSED},
+ {SP_CSS_FONT_STRETCH_CONDENSED, PANGO_STRETCH_CONDENSED},
+ {SP_CSS_FONT_STRETCH_SEMI_CONDENSED, PANGO_STRETCH_SEMI_CONDENSED},
+ {SP_CSS_FONT_STRETCH_SEMI_EXPANDED, PANGO_STRETCH_SEMI_EXPANDED},
+ {SP_CSS_FONT_STRETCH_EXPANDED, PANGO_STRETCH_EXPANDED},
+ {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED, PANGO_STRETCH_EXTRA_EXPANDED},
+ {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = {
+ {SP_CSS_FONT_VARIANT_NORMAL, PANGO_VARIANT_NORMAL},
+ {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}};
+
+font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
+{
+ if (style->text == NULL) return NULL;
+ return (font_factory::Default())->Face(style->text->font_family.value,
+ _enum_converter(style->font_variant.computed, enum_convert_spstyle_variant_to_pango_variant, sizeof(enum_convert_spstyle_variant_to_pango_variant)/sizeof(enum_convert_spstyle_variant_to_pango_variant[0])),
+ _enum_converter(style->font_style.computed, enum_convert_spstyle_style_to_pango_style, sizeof(enum_convert_spstyle_style_to_pango_style)/sizeof(enum_convert_spstyle_style_to_pango_style[0])),
+ _enum_converter(style->font_weight.computed, enum_convert_spstyle_weight_to_pango_weight, sizeof(enum_convert_spstyle_weight_to_pango_weight)/sizeof(enum_convert_spstyle_weight_to_pango_weight[0])),
+ _enum_converter(style->font_stretch.computed, enum_convert_spstyle_stretch_to_pango_stretch, sizeof(enum_convert_spstyle_stretch_to_pango_stretch)/sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0])));
+}
+
+Layout::InputStreamTextSource::~InputStreamTextSource()
+{
+ sp_style_unref(style);
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp
new file mode 100755
index 000000000..3dd043a68
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-OutIter.cpp
@@ -0,0 +1,994 @@
+/*
+ * Inkscape::Text::Layout - text layout engine output functions using iterators
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "livarot/Path.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "libnr/nr-matrix-translate-ops.h"
+#include "libnr/nr-translate-rotate-ops.h"
+#include "style.h"
+
+namespace Inkscape {
+namespace Text {
+
+Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const
+{
+ unsigned char_index = _lineToCharacter(line_index);
+ int best_char_index = -1;
+ double best_x_difference = DBL_MAX;
+
+ if (char_index == _characters.size()) return end();
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (_characters[char_index].chunk(this).in_line != line_index) break;
+ if (_characters[char_index].char_attributes.is_mandatory_break) break;
+ if (!_characters[char_index].char_attributes.is_cursor_position) continue;
+ double this_x_difference = fabs(_characters[char_index].x + _characters[char_index].span(this).x_start + _characters[char_index].chunk(this).left_x - local_x);
+ if (this_x_difference < best_x_difference) {
+ best_char_index = char_index;
+ best_x_difference = this_x_difference;
+ }
+ }
+ // also try the very end of a para (not lines though because the space wraps)
+ if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
+ double this_x_difference;
+ if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x);
+ else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x);
+ if (this_x_difference < best_x_difference) {
+ best_char_index = char_index;
+ best_x_difference = this_x_difference;
+ }
+ }
+ if (best_char_index == -1) return iterator(this, char_index);
+ return iterator(this, best_char_index);
+}
+
+double Layout::_getChunkWidth(unsigned chunk_index) const
+{
+ double chunk_width = 0.0;
+ unsigned span_index;
+ if (chunk_index) {
+ span_index = _lineToSpan(_chunks[chunk_index].in_line);
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++);
+ } else
+ span_index = 0;
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++)
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ return chunk_width;
+}
+
+/* getting the cursor position for a mouse click is not as simple as it might
+seem. The two major problems are flows set up in multiple columns and large
+dy adjustments such that text does not belong to the line it appears to. In
+the worst case it's possible to have two characters on top of each other, in
+which case the one we pick is arbitrary.
+
+This is a 3-stage (2 pass) algorithm:
+1) search all the spans to see if the point is contained in one, if so take
+ that. Note that this will collect all clicks from the current UI because
+ of how the hit detection of nrarena objects works.
+2) if that fails, run through all the chunks finding a best guess of the one
+ the user wanted. This is the one whose y coordinate is nearest, or if
+ there's a tie, the x.
+3) search in that chunk using x-coordinate only to find the position.
+*/
+Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
+{
+ if (_lines.empty()) return begin();
+ double local_x = x;
+ double local_y = y;
+
+ if (_path_fitted) {
+ Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(NR::Point(x, y));
+ local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
+ return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ local_x = y;
+ local_y = x;
+ }
+ // stage 1:
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ double span_left, span_right;
+ if (_spans[span_index].x_start < _spans[span_index].x_end) {
+ span_left = _spans[span_index].x_start;
+ span_right = _spans[span_index].x_end;
+ } else {
+ span_left = _spans[span_index].x_end;
+ span_right = _spans[span_index].x_start;
+ }
+ if ( local_x >= _chunks[_spans[span_index].in_chunk].left_x + span_left
+ && local_x <= _chunks[_spans[span_index].in_chunk].left_x + span_right
+ && local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent
+ && local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) {
+ return _cursorXOnLineToIterator(_chunks[_spans[span_index].in_chunk].in_line, local_x);
+ }
+ }
+
+ // stage 2:
+ unsigned span_index = 0;
+ unsigned chunk_index;
+ int best_chunk_index = -1;
+ double best_y_range = DBL_MAX;
+ double best_x_range = DBL_MAX;
+ for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
+ LineHeight line_height = {0.0, 0.0, 0.0};
+ double chunk_width = 0.0;
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
+ line_height.max(_spans[span_index].line_height);
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ }
+ double this_y_range;
+ if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
+ this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
+ else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
+ this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
+ else
+ this_y_range = 0.0;
+ if (this_y_range <= best_y_range) {
+ if (this_y_range < best_y_range) best_x_range = DBL_MAX;
+ double this_x_range;
+ if (local_x < _chunks[chunk_index].left_x)
+ this_x_range = _chunks[chunk_index].left_x - local_y;
+ else if (local_x > _chunks[chunk_index].left_x + chunk_width)
+ this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
+ else
+ this_x_range = 0.0;
+ if (this_x_range < best_x_range) {
+ best_y_range = this_y_range;
+ best_x_range = this_x_range;
+ best_chunk_index = chunk_index;
+ }
+ }
+ }
+
+ // stage 3:
+ if (best_chunk_index == -1) return begin(); // never happens
+ return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x);
+}
+
+Layout::iterator Layout::getLetterAt(double x, double y) const
+{
+ NR::Point point(x, y);
+
+ double rotation;
+ for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
+ NR::Rect box = characterBoundingBox(it, &rotation);
+ // todo: rotation
+ if (box.contains(point)) return it;
+ }
+ return end();
+}
+
+Layout::iterator Layout::sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const
+{
+ unsigned source_index;
+ if (_characters.empty()) return end();
+ for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
+ if (_input_stream[source_index]->source_cookie == source_cookie) break;
+ if (source_index == _input_stream.size()) return end();
+
+ unsigned char_index = _sourceToCharacter(source_index);
+
+ if (_input_stream[source_index]->Type() != TEXT_SOURCE)
+ return iterator(this, char_index);
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
+ if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
+ if (text_iterator >= text_source->text_end) {
+ if (source_index == _input_stream.size() - 1) return end();
+ return iterator(this, _sourceToCharacter(source_index + 1));
+ }
+ Glib::ustring::const_iterator iter_text = text_source->text_begin;
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (iter_text == text_iterator)
+ return iterator(this, char_index);
+ iter_text++;
+ }
+ return end(); // never happens
+}
+
+Layout::iterator Layout::sourceToIterator(void *source_cookie) const
+{
+ return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
+}
+
+NR::Rect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
+{
+ if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
+ return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
+}
+
+NR::Point Layout::characterAnchorPoint(iterator const &it) const
+{
+ if (_characters.empty())
+ return _empty_cursor_shape.position;
+ if (it._char_index == _characters.size()) {
+ return NR::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
+ } else {
+ return NR::Point(_characters[it._char_index].chunk(this).left_x
+ + _spans[_characters[it._char_index].in_span].x_start
+ + _characters[it._char_index].x,
+ _characters[it._char_index].line(this).baseline_y
+ + _characters[it._char_index].span(this).baseline_shift);
+ }
+}
+
+NR::Point Layout::chunkAnchorPoint(iterator const &it) const
+{
+ unsigned chunk_index;
+
+ if (_chunks.empty())
+ return NR::Point(0.0, 0.0);
+
+ if (_characters.empty())
+ chunk_index = 0;
+ else if (it._char_index == _characters.size())
+ chunk_index = _chunks.size() - 1;
+ else chunk_index = _characters[it._char_index].span(this).in_chunk;
+
+ Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
+ if (alignment == LEFT || alignment == FULL)
+ return NR::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y);
+
+ double chunk_width = _getChunkWidth(chunk_index);
+ if (alignment == RIGHT)
+ return NR::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y);
+ //centre
+ return NR::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
+}
+
+NR::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
+{
+ NR::Point top_left, bottom_right;
+ unsigned char_index = it._char_index;
+
+ if (_path_fitted) {
+ double cluster_half_width = 0.0;
+ for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++)
+ cluster_half_width += _glyphs[glyph_index].width;
+ cluster_half_width *= 0.5;
+
+ double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
+ int unused = 0;
+ Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) {
+ NR::Point midpoint;
+ NR::Point tangent;
+ Span const &span = _characters[char_index].span(this);
+ double top = span.baseline_shift - span.line_height.ascent;
+ double bottom = span.baseline_shift + span.line_height.descent;
+
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+ top_left[NR::X] = midpoint[NR::X] - cluster_half_width;
+ top_left[NR::Y] = midpoint[NR::Y] + top;
+ bottom_right[NR::X] = midpoint[NR::X] + cluster_half_width;
+ bottom_right[NR::Y] = midpoint[NR::Y] + bottom;
+ if (rotation)
+ *rotation = atan2(tangent[1], tangent[0]);
+ }
+ g_free(midpoint_otp);
+ } else {
+ if (it._char_index == _characters.size()) {
+ top_left[NR::X] = bottom_right[NR::X] = _chunks.back().left_x + _spans.back().x_end;
+ char_index--;
+ } else {
+ double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
+ top_left[NR::X] = span_x + _characters[it._char_index].x;
+ if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
+ bottom_right[NR::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
+ else
+ bottom_right[NR::X] = span_x + _characters[it._char_index + 1].x;
+ }
+
+ double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent;
+ top_left[NR::Y] = top_left[NR::X];
+ top_left[NR::X] = baseline_y - span_height * 0.5;
+ bottom_right[NR::Y] = bottom_right[NR::X];
+ bottom_right[NR::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[NR::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
+ bottom_right[NR::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
+ }
+
+ if (rotation) {
+ if (it._glyph_index == -1)
+ *rotation = 0.0;
+ else if (it._glyph_index == (int)_glyphs.size())
+ *rotation = _glyphs.back().rotation;
+ else
+ *rotation = _glyphs[it._glyph_index].rotation;
+ }
+ }
+
+ return NR::Rect(top_left, bottom_right);
+}
+
+std::vector<NR::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const
+{
+ std::vector<NR::Point> quads;
+ unsigned char_index;
+ unsigned end_char_index;
+
+ if (it_start._char_index < it_end._char_index) {
+ char_index = it_start._char_index;
+ end_char_index = it_end._char_index;
+ } else {
+ char_index = it_end._char_index;
+ end_char_index = it_start._char_index;
+ }
+ for ( ; char_index < end_char_index ; ) {
+ if (_characters[char_index].in_glyph == -1) {
+ char_index++;
+ continue;
+ }
+ double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
+ unsigned span_index = _characters[char_index].in_span;
+
+ NR::Point top_left, bottom_right;
+ if (_path_fitted || char_rotation != 0.0) {
+ NR::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
+ top_left = box.min();
+ bottom_right = box.max();
+ char_index++;
+ } else { // for straight text we can be faster by combining all the character boxes in a span into one box
+ double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
+ top_left[NR::X] = span_x + _characters[char_index].x;
+ while (char_index < end_char_index && _characters[char_index].in_span == span_index)
+ char_index++;
+ if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
+ bottom_right[NR::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
+ else
+ bottom_right[NR::X] = span_x + _characters[char_index].x;
+
+ double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
+ top_left[NR::Y] = top_left[NR::X];
+ top_left[NR::X] = baseline_y - span_height * 0.5;
+ bottom_right[NR::Y] = bottom_right[NR::X];
+ bottom_right[NR::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[NR::Y] = baseline_y - _spans[span_index].line_height.ascent;
+ bottom_right[NR::Y] = baseline_y + _spans[span_index].line_height.descent;
+ }
+ }
+
+ NR::Rect char_box(top_left, bottom_right);
+ if (char_box.extent(NR::X) == 0.0 || char_box.extent(NR::Y) == 0.0)
+ continue;
+ NR::Point center_of_rotation((top_left[NR::X] + bottom_right[NR::X]) * 0.5,
+ top_left[NR::Y] + _spans[span_index].line_height.ascent);
+ NR::Matrix total_transform = NR::translate(-center_of_rotation) * NR::rotate(char_rotation) * NR::translate(center_of_rotation) * transform;
+ for(int i = 0; i < 4; i ++)
+ quads.push_back(char_box.corner(i) * total_transform);
+ }
+ return quads;
+}
+
+void Layout::queryCursorShape(iterator const &it, NR::Point *position, double *height, double *rotation) const
+{
+ if (_characters.empty()) {
+ *position = _empty_cursor_shape.position;
+ *height = _empty_cursor_shape.height;
+ *rotation = _empty_cursor_shape.rotation;
+ } else {
+ // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
+ // this means x & rotation are the current values but y & height belong to the previous character.
+ // this isn't quite right because dx attributes will be moved along, but it's good enough
+ Span const *span;
+ if (_path_fitted) {
+ // text on a path
+ double x;
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
+ if (it._char_index != 0)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ double path_length = const_cast<Path*>(_path_fitted)->Length();
+ double x_on_path = x;
+ if (x_on_path < 0.0) x_on_path = 0.0;
+
+ int unused = 0;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
+ Path::cut_position path_parameter;
+ if (path_parameter_list != NULL && path_parameter_list[0].piece >= 0)
+ path_parameter = path_parameter_list[0];
+ else {
+ path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
+ path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
+ }
+ g_free(path_parameter_list);
+
+ NR::Point point;
+ NR::Point tangent;
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
+ if (x < 0.0)
+ point += x * tangent;
+ if (x > path_length )
+ point += (x - path_length) * tangent;
+ *rotation = atan2(tangent);
+ (*position)[NR::X] = point[NR::X] - tangent[NR::Y] * span->baseline_shift;
+ (*position)[NR::Y] = point[NR::Y] + tangent[NR::X] * span->baseline_shift;
+ } else {
+ // text is not on a path
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_end;
+ *rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
+ if (it._glyph_index == -1) *rotation = 0.0;
+ else if(it._glyph_index == 0) *rotation = _glyphs[0].rotation;
+ else *rotation = _glyphs[it._glyph_index - 1].rotation;
+ // the first char in a line wants to have the y of the new line, so in that case we don't switch to the previous span
+ if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ (*position)[NR::Y] = span->line(this).baseline_y + span->baseline_shift;
+ }
+ // up to now *position is the baseline point, not the final point which will be the bottom of the descent
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ *height = span->line_height.ascent + span->line_height.descent;
+ *rotation += M_PI / 2;
+ std::swap((*position)[NR::X], (*position)[NR::Y]);
+ (*position)[NR::X] -= sin(*rotation) * *height * 0.5;
+ (*position)[NR::Y] += cos(*rotation) * *height * 0.5;
+ } else {
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ if (span->font)
+ const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
+ double caret_slope = atan2(caret_slope_run, caret_slope_rise);
+ *height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope);
+ *rotation += caret_slope;
+ (*position)[NR::X] -= sin(*rotation) * span->line_height.descent;
+ (*position)[NR::Y] += cos(*rotation) * span->line_height.descent;
+ }
+ }
+}
+
+void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
+{
+ if (it._char_index == _characters.size()) {
+ *source_cookie = NULL;
+ return;
+ }
+ InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
+ *source_cookie = stream_item->source_cookie;
+ if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(stream_item);
+ Glib::ustring::const_iterator text_iter_const = text_source->text_begin;
+ unsigned char_index = it._char_index;
+ unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
+ // confusing algorithm because the iterator goes forwards while the index goes backwards.
+ // It's just that it's faster doing it that way
+ while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
+ ++text_iter_const;
+ char_index--;
+ }
+ text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base());
+ *text_iterator = Glib::ustring::iterator(std::string::iterator(const_cast<char*>(&*text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base()))));
+ // the caller owns the string, so they're going to want a non-const iterator
+ }
+}
+
+void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
+{
+ SVGLength zero_length;
+ zero_length = 0.0;
+
+ result->x.clear();
+ result->y.clear();
+ result->dx.clear();
+ result->dy.clear();
+ result->rotate.clear();
+ if (to._char_index <= from._char_index)
+ return;
+ result->dx.reserve(to._char_index - from._char_index);
+ result->dy.reserve(to._char_index - from._char_index);
+ result->rotate.reserve(to._char_index - from._char_index);
+ for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
+ if (!_characters[char_index].char_attributes.is_char_break)
+ continue;
+ if (char_index == 0)
+ continue;
+ if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
+ continue;
+
+ unsigned prev_cluster_char_index;
+ for (prev_cluster_char_index = char_index - 1 ;
+ prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
+ prev_cluster_char_index--);
+ if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
+ // dx is zero for the first char in a chunk
+ // this algorithm works by comparing the summed widths of the glyphs with the observed
+ // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
+ double glyphs_width = 0.0;
+ if (_characters[prev_cluster_char_index].in_glyph != -1)
+ for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
+ glyphs_width += _glyphs[glyph_index].width;
+ if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
+ glyphs_width = -glyphs_width;
+
+ double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
+ - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
+ - glyphs_width;
+
+
+ InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
+ if (input_item->Type() == TEXT_SOURCE) {
+ SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
+ if (_characters[char_index].char_attributes.is_white)
+ dx -= style->word_spacing.computed;
+ if (_characters[char_index].char_attributes.is_cursor_position)
+ dx -= style->letter_spacing.computed;
+ }
+
+ if (fabs(dx) > 0.0001) {
+ result->dx.resize(char_index - from._char_index + 1, zero_length);
+ result->dx.back() = dx;
+ }
+ }
+ double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
+ if (fabs(dy) > 0.0001) {
+ result->dy.resize(char_index - from._char_index + 1, zero_length);
+ result->dy.back() = dy;
+ }
+ if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
+ result->rotate.resize(char_index - from._char_index + 1, zero_length);
+ result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
+ }
+ }
+}
+
+#define PREV_START_OF_ITEM(this_func) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ _char_index--; \
+ return this_func(); \
+ }
+// end of macro
+
+#define THIS_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ unsigned original_item; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _char_index--; \
+ original_item = item_getter; \
+ } else { \
+ original_item = item_getter; \
+ _char_index--; \
+ } \
+ while (item_getter == original_item) { \
+ if (_char_index == 0) { \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ } \
+ _char_index--; \
+ } \
+ _char_index++; \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define NEXT_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == _parent_layout->_characters.size()) return false; \
+ unsigned original_item = item_getter; \
+ for( ; ; ) { \
+ _char_index++; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ if (item_getter != original_item) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::prevStartOfSpan()
+ PREV_START_OF_ITEM(thisStartOfSpan);
+
+bool Layout::iterator::thisStartOfSpan()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+bool Layout::iterator::nextStartOfSpan()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+
+bool Layout::iterator::prevStartOfChunk()
+ PREV_START_OF_ITEM(thisStartOfChunk);
+
+bool Layout::iterator::thisStartOfChunk()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+bool Layout::iterator::nextStartOfChunk()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+
+bool Layout::iterator::prevStartOfLine()
+ PREV_START_OF_ITEM(thisStartOfLine);
+
+bool Layout::iterator::thisStartOfLine()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+bool Layout::iterator::nextStartOfLine()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+
+bool Layout::iterator::prevStartOfShape()
+ PREV_START_OF_ITEM(thisStartOfShape);
+
+bool Layout::iterator::thisStartOfShape()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+bool Layout::iterator::nextStartOfShape()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+
+bool Layout::iterator::prevStartOfParagraph()
+ PREV_START_OF_ITEM(thisStartOfParagraph);
+
+bool Layout::iterator::thisStartOfParagraph()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+bool Layout::iterator::nextStartOfParagraph()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+
+bool Layout::iterator::prevStartOfSource()
+ PREV_START_OF_ITEM(thisStartOfSource);
+
+bool Layout::iterator::thisStartOfSource()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+bool Layout::iterator::nextStartOfSource()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+
+bool Layout::iterator::thisEndOfLine()
+{
+ if (_char_index == _parent_layout->_characters.size()) return false;
+ if (nextStartOfLine())
+ {
+ if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
+ return prevCursorPosition();
+ return true;
+ }
+ if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
+ return prevCursorPosition(); // for when the last paragraph is empty
+ return false;
+}
+
+void Layout::iterator::beginCursorUpDown()
+{
+ if (_char_index == _parent_layout->_characters.size())
+ _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
+ else
+ _x_coordinate = _parent_layout->_characters[_char_index].x + _parent_layout->_characters[_char_index].span(_parent_layout).x_start + _parent_layout->_characters[_char_index].chunk(_parent_layout).left_x;
+ _cursor_moving_vertically = true;
+}
+
+bool Layout::iterator::nextLineCursor()
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ if (_char_index == _parent_layout->_characters.size())
+ return false;
+ unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index == _parent_layout->_lines.size() - 1) return false;
+ if (_parent_layout->_lines[line_index + 1].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + 1)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + 1, _x_coordinate)._char_index;
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+bool Layout::iterator::prevLineCursor()
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ unsigned line_index;
+ if (_char_index == _parent_layout->_characters.size())
+ line_index = _parent_layout->_lines.size() - 1;
+ else
+ line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index == 0) return false;
+ if (_parent_layout->_lines[line_index - 1].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - 1)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - 1, _x_coordinate)._char_index;
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+#define NEXT_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index + 1 >= _parent_layout->_characters.size()) { \
+ _char_index = _parent_layout->_characters.size(); \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ _char_index++; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define PREV_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index == 0) { \
+ _glyph_index = 0; \
+ return true; \
+ } \
+ _char_index--; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::nextCursorPosition()
+ NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::prevCursorPosition()
+ PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::nextStartOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::prevStartOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::nextEndOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::prevEndOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::nextStartOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::prevStartOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::nextEndOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::prevEndOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
+{
+ // the only reason this function is so complicated is to enable visual cursor
+ // movement moving in to or out of counterdirectional runs
+ if (_parent_layout->_characters.empty()) return false;
+ unsigned old_span_index;
+ Direction old_span_direction;
+ if (_char_index == _parent_layout->_characters.size())
+ old_span_index = _parent_layout->_spans.size() - 1;
+ else
+ old_span_index = _parent_layout->_characters[_char_index].in_span;
+ old_span_direction = _parent_layout->_spans[old_span_index].direction;
+ Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
+
+ int scan_direction;
+ unsigned old_char_index = _char_index;
+ if (old_span_direction != para_direction
+ && ((_char_index == 0 && direction == para_direction)
+ || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
+ // the end of the text is actually in the middle because of reordering. Do cleverness
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else {
+ if (direction == old_span_direction) {
+ if (!nextCursorPosition()) return false;
+ } else {
+ if (!prevCursorPosition()) return false;
+ }
+
+ unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
+ if (new_span_index == old_span_index) return true;
+ if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
+ // we must jump to the other end of a counterdirectional run
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
+ // we might have to do a weird jump when we would have crossed a chunk/line break
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
+ return true;
+ if (old_span_direction == para_direction)
+ return true;
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else
+ return true; // same direction, same chunk: no cleverness required
+ }
+
+ unsigned new_span_index = old_span_index;
+ for ( ; ; ) {
+ if (scan_direction > 0) {
+ if (new_span_index == _parent_layout->_spans.size() - 1) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index++;
+ } else {
+ if (new_span_index == 0) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index--;
+ }
+ if (_parent_layout->_spans[new_span_index].direction == para_direction) {
+ if (para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
+ && para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ }
+
+ // found the correct span, now find the correct character
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
+ if (new_span_index > old_span_index)
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else {
+ if (_parent_layout->_spans[new_span_index].direction != direction) {
+ if (new_span_index >= _parent_layout->_spans.size() - 1)
+ _char_index = _parent_layout->_characters.size();
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ }
+ if (_char_index == _parent_layout->_characters.size()) {
+ _glyph_index = _parent_layout->_glyphs.size();
+ return false;
+ }
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return _char_index != 0;
+}
+
+bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
+{
+ bool r;
+ while ((r = _cursorLeftOrRightLocalX(direction))
+ && !_parent_layout->_characters[_char_index].char_attributes.is_word_start);
+ return r;
+}
+
+bool Layout::iterator::cursorUp()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevLineCursor();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDown()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextLineCursor();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeft()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRight()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorUpWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDownWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeftWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRightWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp
new file mode 100755
index 000000000..08eb403db
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Output.cpp
@@ -0,0 +1,469 @@
+/*
+ * Inkscape::Text::Layout - text layout engine output functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "display/nr-arena-glyphs.h"
+#include "style.h"
+#include "print.h"
+#include "extension/print.h"
+#include "livarot/Path.h"
+#include "libnr/nr-scale-matrix-ops.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+
+namespace Inkscape {
+namespace Text {
+
+void Layout::_clearOutputObjects()
+{
+ _paragraphs.clear();
+ _lines.clear();
+ _chunks.clear();
+ for (std::vector<Span>::iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++)
+ if (it_span->font) it_span->font->Unref();
+ _spans.clear();
+ _characters.clear();
+ _glyphs.clear();
+ _path_fitted = NULL;
+}
+
+void Layout::LineHeight::max(LineHeight const &other)
+{
+ if (other.ascent > ascent) ascent = other.ascent;
+ if (other.descent > descent) descent = other.descent;
+ if (other.leading > leading) leading = other.leading;
+}
+
+void Layout::_getGlyphTransformMatrix(int glyph_index, NRMatrix *matrix) const
+{
+ Span const &span = _glyphs[glyph_index].span(this);
+ double sin_rotation = sin(_glyphs[glyph_index].rotation);
+ double cos_rotation = cos(_glyphs[glyph_index].rotation);
+ (*matrix)[0] = span.font_size * cos_rotation;
+ (*matrix)[1] = span.font_size * sin_rotation;
+ (*matrix)[2] = span.font_size * sin_rotation;
+ (*matrix)[3] = -span.font_size * cos_rotation;
+ if (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) {
+ (*matrix)[4] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y;
+ (*matrix)[5] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x;
+ } else {
+ (*matrix)[4] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x;
+ (*matrix)[5] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y;
+ }
+}
+
+void Layout::show(NRArenaGroup *in_arena, NRRect const *paintbox) const
+{
+ int glyph_index = 0;
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) continue;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[span_index].in_input_stream_item]);
+ NRArenaGlyphsGroup *nr_group = NRArenaGlyphsGroup::create(in_arena->arena);
+ nr_arena_item_add_child(in_arena, nr_group, NULL);
+ nr_arena_item_unref(nr_group);
+
+ nr_arena_glyphs_group_set_style(nr_group, text_source->style);
+ while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) {
+ NRMatrix glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ nr_arena_glyphs_group_add_component(nr_group, _spans[span_index].font, _glyphs[glyph_index].glyph, &glyph_matrix);
+ }
+ glyph_index++;
+ }
+ nr_arena_glyphs_group_set_paintbox(NR_ARENA_GLYPHS_GROUP(nr_group), paintbox);
+ }
+ nr_arena_item_request_update(NR_ARENA_ITEM(in_arena), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void Layout::getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) const
+{
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue;
+ // this could be faster
+ NRMatrix glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ NR::Matrix total_transform = glyph_matrix;
+ total_transform *= transform;
+ NR::Rect glyph_rect = _glyphs[glyph_index].span(this).font->BBox(_glyphs[glyph_index].glyph);
+ NR::Point bmi = glyph_rect.min(), bma = glyph_rect.max();
+ NR::Point tlp(bmi[0],bmi[1]), trp(bma[0],bmi[1]), blp(bmi[0],bma[1]), brp(bma[0],bma[1]);
+ tlp *= total_transform;
+ trp *= total_transform;
+ blp *= total_transform;
+ brp *= total_transform;
+ glyph_rect = NR::Rect(tlp,trp);
+ glyph_rect.expandTo(blp);
+ glyph_rect.expandTo(brp);
+ if ( (glyph_rect.min())[0] < bounding_box->x0 ) bounding_box->x0=(glyph_rect.min())[0];
+ if ( (glyph_rect.max())[0] > bounding_box->x1 ) bounding_box->x1=(glyph_rect.max())[0];
+ if ( (glyph_rect.min())[1] < bounding_box->y0 ) bounding_box->y0=(glyph_rect.min())[1];
+ if ( (glyph_rect.max())[1] > bounding_box->y1 ) bounding_box->y1=(glyph_rect.max())[1];
+ }
+}
+
+void Layout::print(SPPrintContext *ctx,
+ NRRect const *pbox, NRRect const *dbox, NRRect const *bbox,
+ NRMatrix const &ctm) const
+{
+ if (_input_stream.empty()) return;
+
+ Direction block_progression = _blockProgression();
+ bool text_to_path = ctx->module->textToPath();
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) {
+ // invisible glyphs
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (_glyphs[glyph_index].in_character == same_character)
+ glyph_index++;
+ continue;
+ }
+ NRMatrix glyph_matrix;
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ if (text_to_path || _path_fitted) {
+ NRBPath bpath;
+ bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph);
+ if (bpath.path) {
+ NRBPath abp;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ abp.path = nr_artpath_affine(bpath.path, glyph_matrix);
+ if (text_source->style->fill.type != SP_PAINT_TYPE_NONE)
+ sp_print_fill(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox);
+ if (text_source->style->stroke.type != SP_PAINT_TYPE_NONE)
+ sp_print_stroke(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox);
+ nr_free(abp.path);
+ }
+ glyph_index++;
+ } else {
+ NR::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix
+ glyph_matrix = NR::Matrix(NR::scale(1.0, -1.0) * NR::Matrix(NR::rotate(_glyphs[glyph_index].rotation)));
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
+ glyph_matrix.c[4] = span.line(this).baseline_y + span.baseline_shift;
+ // since we're outputting character codes, not glyphs, we want the character x
+ glyph_matrix.c[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ } else {
+ glyph_matrix.c[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ glyph_matrix.c[5] = span.line(this).baseline_y + span.baseline_shift;
+ }
+ Glib::ustring::const_iterator span_iter = span.input_stream_first_character;
+ unsigned char_index = _glyphs[glyph_index].in_character;
+ unsigned original_span = _characters[char_index].in_span;
+ while (char_index && _characters[char_index - 1].in_span == original_span) {
+ char_index--;
+ span_iter++;
+ }
+
+ // try to output as many characters as possible in one go by detecting kerning and stopping when we encounter it
+ Glib::ustring span_string;
+ double char_x = _characters[_glyphs[glyph_index].in_character].x;
+ unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span;
+ do {
+ span_string += *span_iter;
+ span_iter++;
+
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) {
+ char_x += _glyphs[glyph_index].width;
+ glyph_index++;
+ }
+ } while (glyph_index < _glyphs.size()
+ && _path_fitted == NULL
+ && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index
+ && fabs(char_x - _characters[_glyphs[glyph_index].in_character].x) < 1e-5);
+ sp_print_bind(ctx, glyph_matrix, 1.0);
+ sp_print_text(ctx, span_string.c_str(), g_pos, text_source->style);
+ sp_print_release(ctx);
+ }
+ }
+}
+
+// these functions are for dumpAsText() only. No need to translate
+static char const *direction_to_text(Layout::Direction d)
+{
+ switch (d) {
+ case Layout::LEFT_TO_RIGHT: return "ltr";
+ case Layout::RIGHT_TO_LEFT: return "rtl";
+ case Layout::TOP_TO_BOTTOM: return "ttb";
+ case Layout::BOTTOM_TO_TOP: return "btt";
+ }
+ return "???";
+}
+
+static char const *style_to_text(PangoStyle s)
+{
+ switch (s) {
+ case PANGO_STYLE_NORMAL: return "upright";
+ case PANGO_STYLE_ITALIC: return "italic";
+ case PANGO_STYLE_OBLIQUE: return "oblique";
+ }
+ return "???";
+}
+
+static char const *weight_to_text(PangoWeight w)
+{
+ switch (w) {
+ case PANGO_WEIGHT_ULTRALIGHT: return "ultralight";
+ case PANGO_WEIGHT_LIGHT : return "light";
+ case PANGO_WEIGHT_SEMIBOLD : return "semibold";
+ case PANGO_WEIGHT_NORMAL : return "normalweight";
+ case PANGO_WEIGHT_BOLD : return "bold";
+ case PANGO_WEIGHT_ULTRABOLD : return "ultrabold";
+ case PANGO_WEIGHT_HEAVY : return "heavy";
+ }
+ return "???";
+}
+
+Glib::ustring Layout::dumpAsText() const
+{
+ Glib::ustring result;
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ char line[256];
+ snprintf(line, sizeof(line), "==== span %d\n", span_index);
+ result += line;
+ snprintf(line, sizeof(line), " in para %d (direction=%s)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph,
+ direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction));
+ result += line;
+ snprintf(line, sizeof(line), " in source %d (type=%d, cookie=%p)\n", _spans[span_index].in_input_stream_item,
+ _input_stream[_spans[span_index].in_input_stream_item]->Type(),
+ _input_stream[_spans[span_index].in_input_stream_item]->source_cookie);
+ result += line;
+ snprintf(line, sizeof(line), " in line %d (baseline=%f, shape=%d)\n", _chunks[_spans[span_index].in_chunk].in_line,
+ _lines[_chunks[_spans[span_index].in_chunk].in_line].baseline_y,
+ _lines[_chunks[_spans[span_index].in_chunk].in_line].in_shape);
+ result += line;
+ snprintf(line, sizeof(line), " in chunk %d (x=%f, baselineshift=%f)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift);
+ result += line;
+ if (_spans[span_index].font) {
+ snprintf(line, sizeof(line), " font '%s' %f %s %s\n", pango_font_description_get_family(_spans[span_index].font->descr), _spans[span_index].font_size, style_to_text(pango_font_description_get_style(_spans[span_index].font->descr)), weight_to_text(pango_font_description_get_weight(_spans[span_index].font->descr)));
+ result += line;
+ }
+ snprintf(line, sizeof(line), " x_start = %f, x_end = %f\n", _spans[span_index].x_start, _spans[span_index].x_end);
+ result += line;
+ snprintf(line, sizeof(line), " line height: ascent %f, descent %f leading %f\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent, _spans[span_index].line_height.leading);
+ result += line;
+ snprintf(line, sizeof(line), " direction %s, block-progression %s\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression));
+ result += line;
+ result += " ** characters:\n";
+ Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
+ // very inefficent code. what the hell, it's only debug stuff.
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) {
+ if (_characters[char_index].in_span != span_index) continue;
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) {
+ snprintf(line, sizeof(line), " %d: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _characters[char_index].in_glyph);
+ } else {
+ snprintf(line, sizeof(line), " %d: '%c' x=%f flags=%03x glyph=%d\n", char_index, *iter_char, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _characters[char_index].in_glyph);
+ iter_char++;
+ }
+ result += line;
+ }
+ result += " ** glyphs:\n";
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_span != span_index) continue;
+ snprintf(line, sizeof(line), " %d: %d (%f,%f) rot=%f cx=%f char=%d\n", glyph_index, _glyphs[glyph_index].glyph, _glyphs[glyph_index].x, _glyphs[glyph_index].y, _glyphs[glyph_index].rotation, _glyphs[glyph_index].width, _glyphs[glyph_index].in_character);
+ result += line;
+ }
+ result += "\n";
+ }
+ result += "EOT\n";
+ return result;
+}
+
+void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path)
+{
+ double offset = 0.0;
+
+ if (startOffset._set) {
+ if (startOffset.unit == SVGLength::PERCENT)
+ offset = startOffset.computed * const_cast<Path&>(path).Length();
+ else
+ offset = startOffset.computed;
+ }
+
+ switch (_paragraphs.front().alignment) {
+ case CENTER:
+ offset -= _getChunkWidth(0) * 0.5;
+ break;
+ case RIGHT:
+ offset -= _getChunkWidth(0);
+ break;
+ default:
+ break;
+ }
+
+ if (_characters.empty()) {
+ int unused = 0;
+ Path::cut_position *point_otp = const_cast<Path&>(path).CurvilignToPosition(1, &offset, unused);
+ if (offset >= 0.0 && point_otp != NULL && point_otp[0].piece >= 0) {
+ NR::Point point;
+ NR::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent);
+ _empty_cursor_shape.position = point;
+ _empty_cursor_shape.rotation = atan2(tangent[NR::Y], tangent[NR::X]);
+ }
+ }
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ int next_cluster_glyph_index;
+ unsigned next_cluster_char_index;
+ double character_advance;
+ Span const &span = _characters[char_index].span(this);
+
+ for (next_cluster_char_index = char_index + 1 ;
+ next_cluster_char_index < _characters.size() && !_characters[next_cluster_char_index].char_attributes.is_cursor_position;
+ next_cluster_char_index++);
+
+ if (next_cluster_char_index == _characters.size()) {
+ next_cluster_glyph_index = _glyphs.size();
+ character_advance = 0.0; // arbitrary because we're not going to advance
+ } else {
+ next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph;
+ character_advance = (_glyphs[next_cluster_glyph_index].x + _glyphs[next_cluster_glyph_index].chunk(this).left_x)
+ - (_glyphs[_characters[char_index].in_glyph].x + span.chunk(this).left_x);
+ }
+
+ double start_offset = offset + span.x_start + _characters[char_index].x;
+ double cluster_width = 0.0;
+ for (int glyph_index = _characters[char_index].in_glyph ; glyph_index < next_cluster_glyph_index ; glyph_index++)
+ cluster_width += _glyphs[glyph_index].width;
+ if (span.direction == RIGHT_TO_LEFT)
+ start_offset -= cluster_width;
+ double end_offset = start_offset + cluster_width;
+
+ int unused = 0;
+ double midpoint_offset = (start_offset + end_offset) * 0.5;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *midpoint_otp = const_cast<Path&>(path).CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) {
+ NR::Point midpoint;
+ NR::Point tangent;
+
+ const_cast<Path&>(path).PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+
+ if (start_offset >= 0.0 && end_offset >= 0.0) {
+ Path::cut_position *start_otp = const_cast<Path&>(path).CurvilignToPosition(1, &start_offset, unused);
+ if (start_otp != NULL && start_otp[0].piece >= 0) {
+ Path::cut_position *end_otp = const_cast<Path&>(path).CurvilignToPosition(1, &end_offset, unused);
+ if (end_otp != NULL && end_otp[0].piece >= 0) {
+ bool on_same_subpath = true;
+ for (size_t i = 0 ; i < path.pts.size() ; i++) {
+ if (path.pts[i].piece <= start_otp[0].piece) continue;
+ if (path.pts[i].piece >= end_otp[0].piece) break;
+ if (path.pts[i].isMoveTo == polyline_moveto) {
+ on_same_subpath = false;
+ break;
+ }
+ }
+ if (on_same_subpath) {
+ // both points were on the same subpath (without this test the angle is very weird)
+ NR::Point startpoint, endpoint;
+ const_cast<Path&>(path).PointAt(start_otp[0].piece, start_otp[0].t, startpoint);
+ const_cast<Path&>(path).PointAt(end_otp[0].piece, end_otp[0].t, endpoint);
+ if (endpoint != startpoint) {
+ tangent = endpoint - startpoint;
+ tangent.normalize();
+ } else {
+ tangent = NR::Point (0,0);
+ }
+ }
+ g_free(end_otp);
+ }
+ g_free(start_otp);
+ }
+ }
+
+ double rotation = atan2(tangent[1], tangent[0]);
+ for (int glyph_index = _characters[char_index].in_glyph ; glyph_index < next_cluster_glyph_index ; glyph_index++) {
+ double tangent_shift = -cluster_width * 0.5 + _glyphs[glyph_index].x - (_characters[char_index].x + span.x_start);
+ double normal_shift = _glyphs[glyph_index].y;
+ if (span.direction == RIGHT_TO_LEFT)
+ tangent_shift += cluster_width;
+ _glyphs[glyph_index].x = midpoint[0] - span.chunk(this).left_x + tangent[0] * tangent_shift - tangent[1] * normal_shift;
+ _glyphs[glyph_index].y = midpoint[1] - _lines.front().baseline_y + tangent[1] * tangent_shift + tangent[0] * normal_shift;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ } else { // outside the bounds of the path: hide the glyphs
+ _characters[char_index].in_glyph = -1;
+ }
+ g_free(midpoint_otp);
+
+ char_index = next_cluster_char_index;
+ }
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ _spans[span_index].x_start += offset;
+ _spans[span_index].x_end += offset;
+ }
+
+ _path_fitted = &path;
+}
+
+SPCurve *Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
+{
+ GSList *cc = NULL;
+
+ for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) {
+ NRMatrix glyph_matrix;
+ Span const &span = _glyphs[glyph_index].span(this);
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ NRBPath bpath;
+ bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph);
+ if (bpath.path) {
+ NArtBpath *abp = nr_artpath_affine(bpath.path, glyph_matrix);
+ SPCurve *c = sp_curve_new_from_bpath(abp);
+ if (c) cc = g_slist_prepend(cc, c);
+ }
+ }
+ cc = g_slist_reverse(cc);
+
+ SPCurve *curve;
+ if ( cc ) {
+ curve = sp_curve_concat(cc);
+ } else {
+ curve = sp_curve_new();
+ }
+
+ while (cc) {
+ /* fixme: This is dangerous, as we are mixing art_alloc and g_new */
+ sp_curve_unref((SPCurve *) cc->data);
+ cc = g_slist_remove(cc, cc->data);
+ }
+
+ return curve;
+}
+
+void Layout::transform(NR::Matrix const &transform)
+{
+ // this is all massively oversimplified
+ // I can't actually think of anybody who'll want to use it at the moment, so it'll stay simple
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ NR::Point point(_glyphs[glyph_index].x, _glyphs[glyph_index].y);
+ point *= transform;
+ _glyphs[glyph_index].x = point[0];
+ _glyphs[glyph_index].y = point[1];
+ }
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Scanline-Maker.h b/src/libnrtype/Layout-TNG-Scanline-Maker.h
new file mode 100755
index 000000000..3865e2c12
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Maker.h
@@ -0,0 +1,169 @@
+/*
+ * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#ifndef __LAYOUT_TNG_SCANLINE_MAKER_H__
+#define __LAYOUT_TNG_SCANLINE_MAKER_H__
+
+#include <vector>
+#include <cmath>
+#include "libnrtype/Layout-TNG.h"
+
+class Shape;
+
+namespace Inkscape {
+namespace Text {
+
+/** \brief private to Layout. Generates lists of chunks within a shape.
+
+This is the abstract base class for taking a given shape and scanning through
+it line-by-line to get the horizontal extents of each chunk for a line of a
+given height. There are two specialisations: One for real shapes and one that
+turns off wrapping by simulating an infinite shape. In due course there will
+be a further specialisation to optimise for the common case where the shape
+is a rectangle.
+*/
+class Layout::ScanlineMaker
+{
+public:
+ virtual ~ScanlineMaker() {}
+
+ struct ScanRun {
+ double y; /// that's the top of the scan run, not the baseline
+ double x_start; // these are not flipped according to the text direction
+ double x_end;
+ inline double width() const {return std::abs(x_start - x_end);}
+ };
+
+ /** Returns a list of chunks on the current line which can fit text with
+ the given properties. It is up to the caller to discard any chunks which
+ are too narrow for its needs. This function may change the y coordinate
+ between calls if the new height too big to fit in the space remaining in
+ this shape. Returns an empty vector if there is no space left in the
+ current shape. */
+ virtual std::vector<ScanRun> makeScanline(Layout::LineHeight const &line_height) =0;
+
+ /** Indicates that the caller has successfully filled the current line
+ and hence that the next call to makeScanline() should return lines on
+ the next lower line. There is no error return, the next call to
+ makeScanline() will give an error if there is no more space. */
+ virtual void completeLine() =0;
+
+ /** Returns the y coordinate of the top of the scanline that will be
+ returned by the next call to makeScanline(). */
+ virtual double yCoordinate() = 0;
+
+ /** Forces an arbitrary change in the stored y coordinate of the object.
+ The next call to makeScanline() will return runs whose top is at
+ the new coordinate. */
+ virtual void setNewYCoordinate(double new_y) =0;
+
+ /** Tests whether the caller can fit a new line with the given metrics
+ into exactly the space returned by the previous call to makeScanline().
+ This saves the caller from having to discard its wrapping solution and
+ starting at the beginning of the line again when a larger font is seen.
+ The metrics given here are considered to be the ones that are being
+ used now, and hence is the line advance height used by completeLine().
+ */
+ virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height) =0;
+};
+
+/** \brief private to Layout. Generates infinite scanlines for when you don't want wrapping
+
+This is a 'fake' scanline maker which will always return infinite results,
+effectively turning off wrapping. It's a very simple implementation.
+
+It does have the curious property, however, that the input coordinates are
+'real' x and y, but the outputs are rotated according to the
+\a block_progression.
+*/
+class Layout::InfiniteScanlineMaker : public Layout::ScanlineMaker
+{
+public:
+ InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression);
+ ~InfiniteScanlineMaker();
+
+ /** Returns a single infinite run at the current location */
+ virtual std::vector<ScanRun> makeScanline(Layout::LineHeight const &line_height);
+
+ /** Increments the current y by the current line height */
+ virtual void completeLine();
+
+ virtual double yCoordinate()
+ {return _y;}
+
+ /** Just changes y */
+ virtual void setNewYCoordinate(double new_y);
+
+ /** Always true, but has to save the new height */
+ virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height);
+
+private:
+ double _x, _y;
+ Layout::LineHeight _current_line_height;
+ bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt
+};
+
+/** \brief private to Layout. Generates scanlines inside an arbitrary shape
+
+This is the 'perfect', and hence slowest, implementation of a
+Layout::ScanlineMaker, which will return exact bounds for any given
+input shape.
+*/
+class Layout::ShapeScanlineMaker : public Layout::ScanlineMaker
+{
+public:
+ ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression);
+ ~ShapeScanlineMaker();
+
+ virtual std::vector<ScanRun> makeScanline(Layout::LineHeight const &line_height);
+
+ virtual void completeLine();
+
+ virtual double yCoordinate();
+
+ virtual void setNewYCoordinate(double new_y);
+
+ /** never true */
+ virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height);
+private:
+ /** To generate scanlines for top-to-bottom text it is easiest if we
+ simply rotate the given shape by a multiple of 90 degrees. This stores
+ that. If no rotation was needed we can simply store the pointer we were
+ given and set shape_needs_freeing appropriately. */
+ Shape *_rotated_shape;
+
+ /// see #rotated_shape;
+ bool _shape_needs_freeing;
+
+ // Shape::BeginRaster() needs floats rather than doubles
+ float _bounding_box_top, _bounding_box_bottom;
+ float _y;
+ float _rasterizer_y;
+ int _current_rasterization_point;
+ float _current_line_height;
+
+ bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt
+};
+
+}//namespace Text
+}//namespace Inkscape
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Scanline-Makers.cpp b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp
new file mode 100755
index 000000000..73b4dd6fa
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp
@@ -0,0 +1,189 @@
+/*
+ * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG-Scanline-Maker.h"
+#include "livarot/Shape.h"
+#include "livarot/float-line.h"
+
+namespace Inkscape {
+namespace Text {
+
+// *********************** infinite version
+
+Layout::InfiniteScanlineMaker::InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression)
+{
+ _current_line_height.ascent = 0.0;
+ _current_line_height.descent = 0.0;
+ _current_line_height.leading = 0.0;
+ switch (block_progression) {
+ case LEFT_TO_RIGHT:
+ case RIGHT_TO_LEFT:
+ _x = initial_y;
+ _y = initial_x;
+ break;
+ default:
+ _x = initial_x;
+ _y = initial_y;
+ break;
+ }
+ _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP;
+
+}
+
+Layout::InfiniteScanlineMaker::~InfiniteScanlineMaker()
+{
+}
+
+std::vector<Layout::ScanlineMaker::ScanRun> Layout::InfiniteScanlineMaker::makeScanline(Layout::LineHeight const &line_height)
+{
+ std::vector<ScanRun> runs(1);
+ runs[0].x_start = _x;
+ runs[0].x_end = FLT_MAX; // we could use DBL_MAX, but this just seems safer
+ runs[0].y = _y;
+ _current_line_height = line_height;
+ return runs;
+}
+
+void Layout::InfiniteScanlineMaker::completeLine()
+{
+ if (_negative_block_progression)
+ _y -= _current_line_height.total();
+ else
+ _y += _current_line_height.total();
+ _current_line_height.ascent = 0.0;
+ _current_line_height.descent = 0.0;
+ _current_line_height.leading = 0.0;
+}
+
+void Layout::InfiniteScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = new_y;
+}
+
+bool Layout::InfiniteScanlineMaker::canExtendCurrentScanline(Layout::LineHeight const &line_height)
+{
+ _current_line_height = line_height;
+ return true;
+}
+
+// *********************** real shapes version
+
+Layout::ShapeScanlineMaker::ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression)
+{
+ if (block_progression == TOP_TO_BOTTOM) {
+ _rotated_shape = const_cast<Shape*>(shape);
+ _shape_needs_freeing = false;
+ } else {
+ Shape *temp_rotated_shape = new Shape;
+ _shape_needs_freeing = true;
+ temp_rotated_shape->Copy(const_cast<Shape*>(shape));
+ switch (block_progression) {
+ case BOTTOM_TO_TOP: temp_rotated_shape->Transform(NR::Matrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)); break; // reflect about x axis
+ case LEFT_TO_RIGHT: temp_rotated_shape->Transform(NR::Matrix(0.0, 1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=x
+ case RIGHT_TO_LEFT: temp_rotated_shape->Transform(NR::Matrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=-x
+ default: break;
+ }
+ _rotated_shape = new Shape;
+ _rotated_shape->ConvertToShape(temp_rotated_shape);
+ delete temp_rotated_shape;
+ }
+ _rotated_shape->CalcBBox(true);
+ _bounding_box_top = _rotated_shape->topY;
+ _bounding_box_bottom = _rotated_shape->bottomY;
+ _y = _rasterizer_y = _bounding_box_top;
+ _current_rasterization_point = 0;
+ _rotated_shape->BeginRaster(_y, _current_rasterization_point);
+ _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP;
+}
+
+
+Layout::ShapeScanlineMaker::~ShapeScanlineMaker()
+{
+ _rotated_shape->EndRaster();
+ if (_shape_needs_freeing)
+ delete _rotated_shape;
+}
+
+std::vector<Layout::ScanlineMaker::ScanRun> Layout::ShapeScanlineMaker::makeScanline(Layout::LineHeight const &line_height)
+{
+ FloatLigne line_rasterization;
+ FloatLigne line_decent_length_runs;
+ float line_text_height = (float)(line_height.ascent + line_height.descent);
+
+ if (_y > _bounding_box_bottom)
+ return std::vector<ScanRun>();
+
+ if (_y < _bounding_box_top)
+ _y = _bounding_box_top;
+
+ if (line_text_height == 0.0)
+ line_text_height = 0.001; // Scan() doesn't work for zero height so this will have to do
+
+ _current_line_height = (float)line_height.total();
+
+ // I think what's going on here is that we're moving the top of the scanline to the given position...
+ _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y, line_text_height);
+ // ...then actually retreiving the scanline (which alters the first two parameters)
+ _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y + line_text_height , &line_rasterization, true, line_text_height);
+ // sanitise the raw rasterisation, which could have weird overlaps
+ line_rasterization.Flatten();
+ // cut out runs that cover less than 90% of the line
+ line_decent_length_runs.Over(&line_rasterization, 0.9 * line_text_height);
+
+ if (line_decent_length_runs.runs.empty())
+ {
+ if (line_rasterization.runs.empty())
+ return std::vector<ScanRun>(); // stop the flow
+ // make up a pointless run: anything that's not an empty vector
+ std::vector<ScanRun> result(1);
+ result[0].x_start = line_rasterization.runs[0].st;
+ result[0].x_end = line_rasterization.runs[0].st;
+ result[0].y = _negative_block_progression ? -_current_line_height - _y : _y;
+ return result;
+ }
+
+ // convert the FloatLigne to what we use: vector<ScanRun>
+ std::vector<ScanRun> result(line_decent_length_runs.runs.size());
+ for (unsigned i = 0 ; i < result.size() ; i++) {
+ result[i].x_start = line_decent_length_runs.runs[i].st;
+ result[i].x_end = line_decent_length_runs.runs[i].en;
+ result[i].y = _negative_block_progression ? -_current_line_height - _y : _y;
+ }
+
+ return result;
+}
+
+void Layout::ShapeScanlineMaker::completeLine()
+{
+ _y += _current_line_height;
+}
+
+double Layout::ShapeScanlineMaker::yCoordinate()
+{
+ if (_negative_block_progression) return -_current_line_height - _y;
+ return _y;
+}
+
+void Layout::ShapeScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = (float)new_y;
+ if (_negative_block_progression) _y = -_current_line_height - _y;
+ // what will happen with the rasteriser if we move off the shape?
+ // it's not an important question because <flowSpan> doesn't have a y attribute
+}
+
+bool Layout::ShapeScanlineMaker::canExtendCurrentScanline(Layout::LineHeight const &line_height)
+{
+ //we actually could return true if only the leading changed, but that's too much effort for something that rarely happens
+ return false;
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG.cpp b/src/libnrtype/Layout-TNG.cpp
new file mode 100755
index 000000000..bda0d1697
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.cpp
@@ -0,0 +1,45 @@
+/*
+ * Inkscape::Text::Layout - text layout engine misc
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+
+namespace Inkscape {
+namespace Text {
+
+const gunichar Layout::UNICODE_SOFT_HYPHEN = 0x00AD;
+const double Layout::LINE_HEIGHT_NORMAL = 1.25;
+
+Layout::Layout()
+{
+ _path_fitted = NULL;
+}
+
+Layout::~Layout()
+{
+ clear();
+}
+
+void Layout::clear()
+{
+ _clearInputObjects();
+ _clearOutputObjects();
+}
+
+bool Layout::_directions_are_orthogonal(Direction d1, Direction d2)
+{
+ if (d1 == BOTTOM_TO_TOP) d1 = TOP_TO_BOTTOM;
+ if (d2 == BOTTOM_TO_TOP) d2 = TOP_TO_BOTTOM;
+ if (d1 == RIGHT_TO_LEFT) d1 = LEFT_TO_RIGHT;
+ if (d2 == RIGHT_TO_LEFT) d2 = LEFT_TO_RIGHT;
+ return d1 != d2;
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h
new file mode 100755
index 000000000..6400ee77b
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.h
@@ -0,0 +1,1029 @@
+/*
+ * Inkscape::Text::Layout - text layout engine
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#ifndef __LAYOUT_TNG_H__
+#define __LAYOUT_TNG_H__
+
+#include "libnr/nr-rect.h"
+#include "libnr/nr-matrix.h"
+#include "libnr/nr-matrix-ops.h"
+#include "libnr/nr-rotate-ops.h"
+#include <glibmm/ustring.h>
+#include <pango/pango-break.h>
+#include <vector>
+
+class SPStyle;
+class Shape;
+class NRArenaGroup;
+class SPPrintContext;
+class SVGLength;
+class Path;
+class SPCurve;
+class font_instance;
+
+namespace Inkscape {
+namespace Text {
+
+/** \brief Generates the layout for either wrapped or non-wrapped text and stores the result
+
+Use this class for all your text output needs. It takes text with formatting
+markup as input and turns that into the glyphs and their necessary positions.
+It stores the glyphs internally, but maintains enough information to both
+retrieve your own rendering information if you wish and to perform visual
+text editing where the output refers back to where it came from.
+
+Usage:
+-# Construct
+-# Set the text using appendText() and appendControlCode()
+-# If you want text wrapping, call appendWrapShape() a few times
+-# Call calculateFlow()
+-# You can go several directions from here, but the most interesting
+ things start with creating a Layout::iterator with begin() or end().
+
+Terminology, in descending order of size:
+- Flow: Not often used, but when it is it means all the text
+- Shape: A Shape object which is used to represent one of the regions inside
+ which to flow the text. Can overlap with...
+- Paragraph: Err...A paragraph. Contains one or more...
+- Line: An entire horizontal line with a common baseline. Contains one or
+ more...
+- Chunk: You only get more than one of these when a shape is sufficiently
+ complex that the text has to flow either side of some obstruction in
+ the middle. A chunk is the base unit for wrapping. Contains one or more...
+- Span: A convenient subset of a chunk with the same font, style,
+ directionality, block progression and input stream. Fill and outline
+ need not be constant because that's a later rendering stage.
+- This is where it gets weird because a span will contain one or more
+ elements of both of the following, which can overlap with each other in
+ any way:
+ - Character: a single Unicode codepoint from an input stream. Many arabic
+ characters contain multiple glyphs
+ - Glyph: a rendering primitive for font engines. A ligature glyph will
+ represent multiple characters.
+
+Other terminology:
+- Input stream: An object representing a single call to appendText() or
+ appendControlCode().
+- Control code: Metadata in the text stream to signify items that occupy
+ real space (unlike style changes) but don't belong in the text string.
+ Paragraph breaks are in this category. See Layout::TextControlCode.
+- SVG1.1: The W3C Recommendation "Scalable Vector Graphics (SVG) 1.1"
+ http://www.w3.org/TR/SVG11/
+- 'left', 'down', etc: These terms are generally used to mean what they
+ mean in left-to-right, top-to-bottom text but rotated or reflected for
+ the current directionality. Thus, the 'width' of a ttb line is actually
+ its height, and the (internally stored) y coordinate of a glyph is
+ actually its x coordinate. Confusing to the reader but much simpler in
+ the code. All public methods use real x and y.
+
+Comments:
+- There's a strong emphasis on international support in this class, but
+ that's primarily because once you can display all the insane things
+ required by various languages, simple things like styling text are
+ almost trivial.
+- There are a few places (appendText() is one) where pointers are held to
+ caller-owned objects and used for quite a long time. This is messy but
+ is safe for our usage scenario and in many cases the cost of copying the
+ objects is quite high.
+- "Why isn't foo here?": Ask yourself if it's possible to implement foo
+ externally using iterators. However this may not mean that it doesn't
+ belong as a member, though.
+- I've used floats rather than doubles to store relative distances in some
+ places (internal only) where it would save significant amounts of memory.
+ The SVG spec allows you to do this as long as intermediate calculations
+ are done double. Very very long lines might not finish precisely where
+ you want, but that's to be expected with any typesetting. Also,
+ SVGLength only uses floats.
+- If you look at the six arrays for holding the output data you'll realise
+ that there's no O(1) way to drill down from a paragraph to find its
+ starting glyph. This was a conscious decision to reduce complexity and
+ to save memory. Drilling down isn't actually that slow because a binary
+ chop will work nicely. Add this to the realisation that most of the
+ times you do this will be in response to user actions and hence you only
+ need to be faster than the user and I think the design makes sense.
+- There are a massive number of functions acting on Layout::iterator. A
+ large number are trivial and will be inline, but is it really necessary
+ to have all these, especially when some can be implemented by the caller
+ using the others?
+- The separation of methods between Layout and Layout::iterator is a
+ bit arbitrary, because many methods could go in either. I've used the STL
+ model where the iterator itself can only move around; the base class is
+ required to do anything interesting.
+- I use Pango internally, not Pangomm. The reason for this is lots of
+ Pangomm methods take Glib::ustrings as input and then output byte offsets
+ within the strings. There's simply no way to use byte offsets with
+ ustrings without some very entertaining reinterpret_cast<>s. The Pangomm
+ docs seem to be lacking quite a lot of things mentioned in the Pango
+ docs, too.
+*/
+class Layout {
+public:
+ class iterator;
+ friend class iterator;
+ class Calculator;
+ friend class Calculator;
+ class ScanlineMaker;
+ class InfiniteScanlineMaker;
+ class ShapeScanlineMaker;
+
+ Layout();
+ virtual ~Layout();
+
+ /** Used to specify any particular text direction required. Used for
+ both the 'direction' and 'block-progression' CSS attributes. */
+ enum Direction {LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP};
+
+ /** Display alignment for shapes. See appendWrapShape(). */
+ enum DisplayAlign {DISPLAY_ALIGN_BEFORE, DISPLAY_ALIGN_CENTER, DISPLAY_ALIGN_AFTER};
+
+ /** The optional attributes which can be applied to a SVG text or
+ related tag. See appendText(). See SVG1.1 section 10.4 for the
+ definitions of all these members. See sp_svg_length_list_read() for
+ the standard way to make these vectors. It is the responsibility of
+ the caller to deal with the inheritance of these values using its
+ knowledge of the parse tree. */
+ struct OptionalTextTagAttrs {
+ std::vector<SVGLength> x;
+ std::vector<SVGLength> y;
+ std::vector<SVGLength> dx;
+ std::vector<SVGLength> dy;
+ std::vector<SVGLength> rotate;
+ };
+
+ /** Control codes which can be embedded in the text to be flowed. See
+ appendControlCode(). */
+ enum TextControlCode {
+ PARAGRAPH_BREAK, /// forces the flow to move on to the next line
+ SHAPE_BREAK, /// forces the flow to ignore the remainder of the current shape (from #flow_inside_shapes) and continue at the top of the one after.
+ ARBITRARY_GAP /// inserts an arbitrarily-sized hole in the flow in line with the current text.
+ };
+
+ /** For expressing paragraph alignment. These values are rotated in the
+ case of vertical text, but are not dependent on whether the paragraph is
+ rtl or ltr, thus LEFT is always either left or top. */
+ enum Alignment {LEFT, CENTER, RIGHT, FULL};
+
+ /** The CSS spec allows line-height:normal to be whatever the user agent
+ thinks will look good. This is our value, as a multiple of font-size. */
+ static const double LINE_HEIGHT_NORMAL;
+
+ // ************************** describing the stuff to flow *************************
+
+ /** \name Input
+ Methods for describing the text you want to flow, its style, and the
+ shapes to flow in to.
+ */
+ //@{
+
+ /** Empties everything stored in this class and resets it to its
+ original state, like when it was created. All iterators on this
+ object will be invalidated (but can be revalidated using
+ validateIterator(). */
+ void clear();
+
+ /** Queries whether any calls have been made to appendText() or
+ appendControlCode() since the object was last cleared. */
+ bool inputExists() const
+ {return !_input_stream.empty();}
+
+ /** adds a new piece of text to the end of the current list of text to
+ be processed. This method can only add text of a consistent style.
+ To add lots of different styles, call it lots of times.
+ \param text The text. \b Note: only a \em pointer is stored. Do not
+ mess with the text until after you have called
+ calculateFlow().
+ \param style The font style. Layout will hold a reference to this
+ object for the duration of its ownership, ie until you
+ call clear() or the class is destroyed. Must not be NULL.
+ \param source_cookie This pointer is treated as opaque by Layout
+ but will be passed through the flowing process intact so
+ that callers can use it to refer to the original object
+ that generated a particular glyph. See Layout::iterator.
+ Implementation detail: currently all callers put an
+ SPString in here.
+ \param optional_attributes A structure containing additional options
+ for this text. See OptionalTextTagAttrs. The values are
+ copied to internal storage before this method returns.
+ \param optional_attributes_offset It is convenient for callers to be
+ able to use the same \a optional_attributes structure for
+ several sequential text fields, in which case the vectors
+ will need to be offset. This parameter causes the <i>n</i>th
+ element of all the vectors to be read as if it were the
+ first.
+ \param text_begin Used for selecting only a substring of \a text
+ to process.
+ \param text_end Used for selecting only a substring of \a text
+ to process.
+ */
+ void appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end);
+ inline void appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes = NULL, unsigned optional_attributes_offset = 0)
+ {appendText(text, style, source_cookie, optional_attributes, optional_attributes_offset, text.begin(), text.end());}
+
+ /** Control codes are metadata in the text stream to signify items
+ that occupy real space (unlike style changes) but don't belong in the
+ text string. See TextControlCode for the types available.
+
+ A control code \em cannot be the first item in the input stream. Use
+ appendText() with an empty string to set up the paragraph properties.
+ \param code A member of the TextFlowControlCode enumeration.
+ \param width The width in pixels that this item occupies.
+ \param ascent The number of pixels above the text baseline that this
+ control code occupies.
+ \param descent The number of pixels below the text baseline that this
+ control code occupies.
+ \param source_cookie This pointer is treated as opaque by Layout
+ but will be passed through the flowing process intact so
+ that callers can use it to refer to the original object
+ that generated a particular area. See Layout::iterator.
+ Implementation detail: currently all callers put an
+ SPObject in here.
+ Note that for some control codes (eg tab) the values of the \a width,
+ \a ascender and \a descender are implied by the surrounding text (and
+ in the case of tabs, the values set in tab_stops) so the values you pass
+ here are ignored.
+ */
+ void appendControlCode(TextControlCode code, void *source_cookie, double width = 0.0, double ascent = 0.0, double descent = 0.0);
+
+ /** Stores another shape inside which to flow the text. If this method
+ is never called then no automatic wrapping is done and lines will
+ continue to infinity if necessary. Text can be flowed inside multiple
+ shapes in sequence, like with frames in a DTP package. If the text flows
+ past the end of the last shape all remaining text is ignored.
+
+ \param shape The Shape to use next in the flow. The storage for this
+ is managed by the caller, and need only be valid for
+ the duration of the call to calculateFlow().
+ \param display_align The vertical alignment of the text within this
+ shape. See XSL1.0 section 7.13.4. The behaviour of
+ settings other than DISPLAY_ALIGN_BEFORE when using
+ non-rectangular shapes is undefined.
+ */
+ void appendWrapShape(Shape const *shape, DisplayAlign display_align = DISPLAY_ALIGN_BEFORE);
+
+ //@}
+
+ // ************************** doing the actual flowing *************************
+
+ /** \name Processing
+ The method to do the actual work of converting text into glyphs.
+ */
+ //@{
+
+ /** Takes all the stuff you set with the members above here and creates
+ a load of glyphs for use with the members below here. All iterators on
+ this object will be invalidated (but can be fixed with validateIterator().
+ The implementation just creates a new Layout::Calculator and calls its
+ Calculator::Calculate() method, so if you want more details on the
+ internals, go there.
+ \return false on failure.
+ */
+ bool calculateFlow();
+
+ //@}
+
+ // ************************** operating on the output glyphs *************************
+
+ /** \name Output
+ Methods for reading and interpreting the output glyphs. See also
+ Layout::iterator.
+ */
+ //@{
+
+ /** Returns true if there are some glyphs in this object, ie whether
+ computeFlow() has been called on a non-empty input since the object was
+ created or the last call to clear(). */
+ inline bool outputExists() const
+ {return !_characters.empty();}
+
+ /** Adds all the output glyphs to \a in_arena using the given \a paintbox.
+ \param in_arena The arena to add the glyphs group to
+ \param paintbox The current rendering tile
+ */
+ void show(NRArenaGroup *in_arena, NRRect const *paintbox) const;
+
+ /** Calculates the smallest rectangle completely enclosing all the
+ glyphs.
+ \param bounding_box Where to store the box
+ \param transform The transform to be applied to the entire object
+ prior to calculating its bounds.
+ */
+ void getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) const;
+
+ /** Sends all the glyphs to the given print context.
+ \param ctx I have
+ \param pbox no idea
+ \param dbox what these
+ \param bbox parameters
+ \param ctm do yet
+ */
+ void print(SPPrintContext *ctx, NRRect const *pbox, NRRect const *dbox, NRRect const *bbox, NRMatrix const &ctm) const;
+
+ /** debug and unit test method. Creates a textual representation of the
+ contents of this object. The output is designed to be both human-readable
+ and comprehensible when diffed with a known-good dump. */
+ Glib::ustring dumpAsText() const;
+
+ /** Moves all the glyphs in the structure so that the baseline of all
+ the characters sits neatly along the path specified. If the text has
+ more than one line the results are undefined. The 'align' means to
+ use the SVG align method as documented in SVG1.1 section 10.13.2.
+ NB: njh has suggested that it would be cool if we could flow from
+ shape to path and back again. This is possible, so this method will be
+ removed at some point.
+ A pointer to \a path is retained by the class for use by the cursor
+ positioning functions. */
+ void fitToPathAlign(SVGLength const &startOffset, Path const &path);
+
+ /** Convert the specified range of characters into their bezier
+ outlines.
+ */
+ SPCurve* convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const;
+ inline SPCurve* convertToCurves() const;
+
+ /** Apply the given transform to all the output presently stored in
+ this object. This only transforms the glyph positions, The glyphs
+ themselves will not be transformed. */
+ void transform(NR::Matrix const &transform);
+
+ //@}
+
+ // **********
+
+ /** \name Output (Iterators)
+ Methods for operating with the Layout::iterator class. The method
+ names ending with 'Index' return 0-based offsets of the number of
+ items since the beginning of the flow.
+ */
+ //@{
+
+ /** Returns an iterator pointing at the first glyph of the flowed output.
+ The first glyph is also the first character, line, paragraph, etc. */
+ inline iterator begin() const;
+
+ /** Returns an iterator pointing just past the end of the last glyph,
+ which is also just past the end of the last chunk, span, etc, etc. */
+ inline iterator end() const;
+
+ /** Returns an iterator pointing at the given character index. This
+ index should be related to the result from a prior call to
+ iteratorToCharIndex(). */
+ inline iterator charIndexToIterator(int char_index) const;
+
+ /** Returns the character index from the start of the flow represented
+ by the given iterator. This number isn't very useful, except for when
+ editing text it will stay valid across calls to computeFlow() and will
+ change in predictable ways when characters are added and removed. It's
+ also useful when transitioning old code. */
+ inline int iteratorToCharIndex(iterator const &it) const;
+
+ /** Checks the validity of the given iterator over the current layout.
+ If it points to a position out of the bounds for this layout it will
+ be corrected to the nearest valid position. If you pass an iterator
+ belonging to a different layout it will be converted to one for this
+ layout. */
+ inline void validateIterator(iterator *it) const;
+
+ /** Returns an iterator pointing to the cursor position for a mouse
+ click at the given coordinates. */
+ iterator getNearestCursorPositionTo(double x, double y) const;
+ inline iterator getNearestCursorPositionTo(NR::Point &point) const;
+
+ /** Returns an iterator pointing to the letter whose bounding box contains
+ the given coordinates. end() if the point is not over any letter. The
+ iterator will \em not point at the specific glyph within the character. */
+ iterator getLetterAt(double x, double y) const;
+ inline iterator getLetterAt(NR::Point &point) const;
+
+ /** Returns an iterator pointing to the character in the output which
+ was created from the given input. If the character at the given byte
+ offset was removed (soft hyphens, for example) the next character after
+ it is returned. If no input was added with the given cookie, end() is
+ returned. If more than one input has the same cookie, the first will
+ be used regardless of the value of \a text_iterator. If
+ \a text_iterator is out of bounds, the first or last character belonging
+ to the given input will be returned accordingly. */
+ iterator sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const;
+
+ /** Returns an iterator pointing to the first character in the output
+ which was created from the given source. If \a source_cookie is invalid,
+ end() is returned. If more than one input has the same cookie, the
+ first one will be used. */
+ iterator sourceToIterator(void *source_cookie) const;
+
+ // many functions acting on iterators, most of which are obvious
+ // also most of them don't check that \a it != end(). Be careful.
+
+ /** Returns the bounding box of the given glyph, and its rotation.
+ The centre of rotation is the horizontal centre of the box at the
+ text baseline. */
+ NR::Rect glyphBoundingBox(iterator const &it, double *rotation) const;
+
+ /** Returns the zero-based line number of the character pointed to by
+ \a it. */
+ inline unsigned lineIndex(iterator const &it) const;
+
+ /** Returns the zero-based number of the shape which contains the
+ character pointed to by \a it. */
+ inline unsigned shapeIndex(iterator const &it) const;
+
+ /** Returns true if the character at \a it is a whitespace, as defined
+ by Pango. This is not meant to be used for picking out words from the
+ output, use iterator::nextStartOfWord() and friends instead. */
+ inline bool isWhitespace(iterator const &it) const;
+
+ /** Returns the unicode character code of the character pointed to by
+ \a it. If \a it == end() the result is undefined. */
+ inline int characterAt(iterator const &it) const;
+
+ /** Discovers where the character pointed to by \a it came from, by
+ retrieving the cookie that was passed to the call to appendText() or
+ appendControlCode() which generated that output. If \a it == end()
+ then NULL is returned as the cookie. If the character was generated
+ from a call to appendText() then the optional \a text_iterator
+ parameter is set to point to the actual character, otherwise
+ \a text_iterator is unaltered. */
+ void getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator = NULL) const;
+
+ /** For latin text, the left side of the character, on the baseline */
+ NR::Point characterAnchorPoint(iterator const &it) const;
+
+ /** This is that value to apply to the x,y attributes of tspan role=line
+ elements, and hence it takes alignment into account. */
+ NR::Point chunkAnchorPoint(iterator const &it) const;
+
+ /** Returns the box extents (not ink extents) of the given character.
+ The centre of rotation is at the horizontal centre of the box on the
+ text baseline. */
+ NR::Rect characterBoundingBox(iterator const &it, double *rotation = NULL) const;
+
+ /** Basically uses characterBoundingBox() on all the characters from
+ \a start to \a end and returns the union of these boxes. The return value
+ is a list of zero or more quadrilaterals specified by a group of four
+ points for each, thus size() is always a multiple of four. */
+ std::vector<NR::Point> createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const;
+
+ /** Returns true if \a it points to a character which is a valid cursor
+ position, as defined by Pango. */
+ inline bool isCursorPosition(iterator const &it) const;
+
+ /** Gets the ideal cursor shape for a given iterator. The result is
+ undefined if \a it is not at a valid cursor position.
+ \param it The location in the output
+ \param position The pixel location of the centre of the 'bottom' of
+ the cursor.
+ \param height The height in pixels of the surrounding text
+ \param rotation The angle to draw from \a position. Radians, zero up,
+ increasing clockwise.
+ */
+ void queryCursorShape(iterator const &it, NR::Point *position, double *height, double *rotation) const;
+
+ /** Returns true if \a it points to a character which is a the start of
+ a word, as defined by Pango. */
+ inline bool isStartOfWord(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the end of
+ a word, as defined by Pango. */
+ inline bool isEndOfWord(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the start of
+ a sentence, as defined by Pango. */
+ inline bool isStartOfSentence(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the end of
+ a sentence, as defined by Pango. */
+ inline bool isEndOfSentence(iterator const &it) const;
+
+ /** Returns the zero-based number of the paragraph containing the
+ character pointed to by \a it. */
+ inline unsigned paragraphIndex(iterator const &it) const;
+
+ /** Returns the actual alignment used for the paragraph containing
+ the character pointed to by \a it. This means that the CSS 'start'
+ and 'end' are correctly translated into LEFT or RIGHT according to
+ the paragraph's directionality. For vertical text, LEFT is top
+ alignment and RIGHT is bottom. */
+ inline Alignment paragraphAlignment(iterator const &it) const;
+
+ /** Returns kerning information which could cause the current output
+ to be exactly reproduced if the letter and word spacings were zero and
+ full justification was not used. The x and y arrays are not used, but
+ they are cleared. The dx applied to the first character in a chunk
+ will always be zero. If the region between \a from and \a to crosses
+ a line break then the results may be surprising, and are undefined.
+ Trailing zeros on the returned arrays will be trimmed. */
+ void simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const;
+
+ //@}
+
+ /// it's useful for this to be public so that ScanlineMaker can use it
+ struct LineHeight {
+ double ascent;
+ double descent;
+ double leading;
+ inline double total() const {return ascent + descent + leading;}
+ inline void setZero() {ascent = descent = leading = 0.0;}
+ inline LineHeight& operator*=(double x) {ascent *= x; descent *= x; leading *= x; return *this;}
+ void max(LineHeight const &other); /// makes this object contain the largest of all three members between this object and other
+ };
+
+ /// see _enum_converter()
+ struct EnumConversionItem {
+ int input, output;
+ };
+
+private:
+ /** Erases all the stuff set by the owner as input, ie #_input_stream
+ and #_input_wrap_shapes. */
+ void _clearInputObjects();
+
+ /** Erases all the stuff output by computeFlow(). Glyphs and things. */
+ void _clearOutputObjects();
+
+ static const gunichar UNICODE_SOFT_HYPHEN;
+
+ // ******************* input flow
+
+ enum InputStreamItemType {TEXT_SOURCE, CONTROL_CODE};
+
+ class InputStreamItem {
+ public:
+ virtual ~InputStreamItem() {}
+ virtual InputStreamItemType Type() =0;
+ void *source_cookie;
+ };
+
+ /** Represents a text item in the input stream. See #_input_stream.
+ Most of the members are copies of the values passed to appendText(). */
+ class InputStreamTextSource : public InputStreamItem {
+ public:
+ virtual InputStreamItemType Type() {return TEXT_SOURCE;}
+ virtual ~InputStreamTextSource();
+ Glib::ustring const *text; /// owned by the caller
+ Glib::ustring::const_iterator text_begin, text_end;
+ int text_length; /// in characters, from text_start to text_end only
+ SPStyle *style;
+ /** These vectors can (often will) be shorter than the text
+ in this source, but never longer. */
+ std::vector<SVGLength> x;
+ std::vector<SVGLength> y;
+ std::vector<SVGLength> dx;
+ std::vector<SVGLength> dy;
+ std::vector<SVGLength> rotate;
+
+ // a few functions for some of the more complicated style accesses
+ float styleComputeFontSize() const;
+ font_instance *styleGetFontInstance() const;
+ Direction styleGetBlockProgression() const;
+ Alignment styleGetAlignment(Direction para_direction, bool try_text_align) const;
+ };
+
+ /** Represents a control code item in the input stream. See
+ #_input_streams. All the members are copies of the values passed to
+ appendControlCode(). */
+ class InputStreamControlCode : public InputStreamItem {
+ public:
+ virtual InputStreamItemType Type() {return CONTROL_CODE;}
+ TextControlCode code;
+ double ascent;
+ double descent;
+ double width;
+ };
+
+ /** This is our internal storage for all the stuff passed to the
+ appendText() and appendControlCode() functions. */
+ std::vector<InputStreamItem*> _input_stream;
+
+ /** The parameters to appendText() are allowed to be a little bit
+ complex. This copies them to be the right length and starting at zero.
+ We also don't want to write five bits of identical code just with
+ different variable names. */
+ static void _copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length);
+
+ /** There are a few cases where we have different sets of enums meaning
+ the same thing, eg Pango font styles vs. SPStyle font styles. These need
+ converting. */
+ static int _enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size);
+
+ /** The overall block-progression of the whole flow. */
+ inline Direction _blockProgression() const
+ {return static_cast<InputStreamTextSource*>(_input_stream.front())->styleGetBlockProgression();}
+
+ /** so that LEFT_TO_RIGHT == RIGHT_TO_LEFT but != TOP_TO_BOTTOM */
+ static bool _directions_are_orthogonal(Direction d1, Direction d2);
+
+ /** If the output is empty callers still want to be able to call
+ queryCursorShape() and get a valid answer so, while #_input_wrap_shapes
+ can still be considered valid, we need to precompute the cursor shape
+ for this case. */
+ void _calculateCursorShapeForEmpty();
+
+ struct CursorShape {
+ NR::Point position;
+ double height;
+ double rotation;
+ } _empty_cursor_shape;
+
+ // ******************* input shapes
+
+ struct InputWrapShape {
+ Shape const *shape; /// as passed to Layout::appendWrapShape()
+ DisplayAlign display_align; /// as passed to Layout::appendWrapShape()
+ };
+ std::vector<InputWrapShape> _input_wrap_shapes;
+
+ // ******************* output
+
+ /** as passed to fitToPathAlign() */
+ Path const *_path_fitted;
+
+ struct Glyph;
+ struct Character;
+ struct Span;
+ struct Chunk;
+ struct Line;
+ struct Paragraph;
+
+ struct Glyph {
+ int glyph;
+ unsigned in_character;
+ float x; /// relative to the start of the chunk
+ float y; /// relative to the current line's baseline
+ float rotation; /// absolute, modulo any object transforms, which we don't know about
+ float width;
+ inline Span const & span(Layout const *l) const {return l->_spans[l->_characters[in_character].in_span];}
+ inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk];}
+ inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk].in_line];}
+ };
+ struct Character {
+ unsigned in_span;
+ float x; /// relative to the start of the *span* (so we can do block-progression)
+ PangoLogAttr char_attributes;
+ int in_glyph; /// will be -1 if this character has no visual representation
+ inline Span const & span(Layout const *l) const {return l->_spans[in_span];}
+ inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[in_span].in_chunk];}
+ inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line];}
+ inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line].in_paragraph];}
+ // to get the advance width of a character, subtract the x values if it's in the middle of a span, or use span.x_end if it's at the end
+ };
+ struct Span {
+ unsigned in_chunk;
+ font_instance *font;
+ float font_size;
+ float x_start; /// relative to the start of the chunk
+ float x_end; /// relative to the start of the chunk
+ LineHeight line_height;
+ double baseline_shift; /// relative to the line's baseline
+ Direction direction; /// See CSS3 section 3.2. Either rtl or ltr
+ Direction block_progression; /// See CSS3 section 3.2. The direction in which lines go.
+ unsigned in_input_stream_item;
+ Glib::ustring::const_iterator input_stream_first_character;
+ inline Chunk const & chunk(Layout const *l) const {return l->_chunks[in_chunk];}
+ inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[in_chunk].in_line];}
+ inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[in_chunk].in_line].in_paragraph];}
+ };
+ struct Chunk {
+ unsigned in_line;
+ double left_x;
+ };
+ struct Line {
+ unsigned in_paragraph;
+ double baseline_y;
+ unsigned in_shape;
+ };
+ struct Paragraph {
+ Direction base_direction; /// can be overridden by child Span objects
+ Alignment alignment;
+ };
+ std::vector<Paragraph> _paragraphs;
+ std::vector<Line> _lines;
+ std::vector<Chunk> _chunks;
+ std::vector<Span> _spans;
+ std::vector<Character> _characters;
+ std::vector<Glyph> _glyphs;
+
+ /** gets the overall matrix that transforms the given glyph from local
+ space to world space. */
+ void _getGlyphTransformMatrix(int glyph_index, NRMatrix *matrix) const;
+
+ // loads of functions to drill down the object tree, all of them
+ // annoyingly similar and all of them requiring predicate functors.
+ // I'll be buggered if I can find a way to make it work with
+ // functions or with a templated functor, so macros it is.
+#define EMIT_PREDICATE(name, object_type, index_generator) \
+ class name { \
+ Layout const * const _flow; \
+ public: \
+ inline name(Layout const *flow) : _flow(flow) {} \
+ inline bool operator()(object_type const &object, unsigned index) \
+ {return index_generator < index;} \
+ }
+// end of macro
+ EMIT_PREDICATE(PredicateLineToSpan, Span, _flow->_chunks[object.in_chunk].in_line);
+ EMIT_PREDICATE(PredicateLineToCharacter, Character, _flow->_chunks[_flow->_spans[object.in_span].in_chunk].in_line);
+ EMIT_PREDICATE(PredicateSpanToCharacter, Character, object.in_span);
+ EMIT_PREDICATE(PredicateSourceToCharacter, Character, _flow->_spans[object.in_span].in_input_stream_item);
+
+ inline unsigned _lineToSpan(unsigned line_index) const
+ {return std::lower_bound(_spans.begin(), _spans.end(), line_index, PredicateLineToSpan(this)) - _spans.begin();}
+ inline unsigned _lineToCharacter(unsigned line_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), line_index, PredicateLineToCharacter(this)) - _characters.begin();}
+ inline unsigned _spanToCharacter(unsigned span_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), span_index, PredicateSpanToCharacter(this)) - _characters.begin();}
+ inline unsigned _sourceToCharacter(unsigned source_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), source_index, PredicateSourceToCharacter(this)) - _characters.begin();}
+
+ /** given an x coordinate and a line number, returns an iterator
+ pointing to the closest cursor position on that line to the
+ coordinate. */
+ iterator _cursorXOnLineToIterator(unsigned line_index, double local_x) const;
+
+ /** calculates the width of a chunk, which is the largest x
+ coordinate (start or end) of the spans contained within it. */
+ double _getChunkWidth(unsigned chunk_index) const;
+};
+
+/** \brief Holds a position within the glyph output of Layout.
+
+Used to access the output of a Layout, query information and generally
+move around in it. See Layout for a glossary of the names of functions.
+
+I'm not going to document all the methods because most of their names make
+their function self-evident.
+
+A lot of the functions would do the same thing in a naive implementation
+for latin-only text, for example nextCharacter(), nextCursorPosition() and
+cursorRight(). Generally it's fairly obvious which one you should use in a
+given situation, but sometimes you might need to put some thought in to it.
+
+All the methods return false if the requested action would have caused the
+current position to move out of bounds. In this case the position is moved
+to either begin() or end(), depending on which direction you were going.
+
+Note that some characters do not have a glyph representation (eg line
+breaks), so if you try using prev/nextGlyph() from one of these you're
+heading for a crash.
+*/
+class Layout::iterator {
+public:
+ friend class Layout;
+ // this is just so you can create uninitialised iterators - don't actually try to use one
+ iterator() : _parent_layout(NULL) {}
+ // no copy constructor required, the default does what we want
+ bool operator== (iterator const &other) const
+ {return _glyph_index == other._glyph_index && _char_index == other._char_index;}
+ bool operator!= (iterator const &other) const
+ {return _glyph_index != other._glyph_index || _char_index != other._char_index;}
+
+ /* mustn't compare _glyph_index in these operators because for characters
+ that don't have glyphs (line breaks, elided soft hyphens, etc), the glyph
+ index is -1 which makes them not well-ordered. To be honest, interating by
+ glyphs is not very useful and should be avoided. */
+ bool operator< (iterator const &other) const
+ {return _char_index < other._char_index;}
+ bool operator<= (iterator const &other) const
+ {return _char_index <= other._char_index;}
+ bool operator> (iterator const &other) const
+ {return _char_index > other._char_index;}
+ bool operator>= (iterator const &other) const
+ {return _char_index >= other._char_index;}
+
+ /* **** visual-oriented methods **** */
+
+ //glyphs
+ inline bool prevGlyph();
+ inline bool nextGlyph();
+
+ //span
+ bool prevStartOfSpan();
+ bool thisStartOfSpan();
+ bool nextStartOfSpan();
+
+ //chunk
+ bool prevStartOfChunk();
+ bool thisStartOfChunk();
+ bool nextStartOfChunk();
+
+ //line
+ bool prevStartOfLine();
+ bool thisStartOfLine();
+ bool nextStartOfLine();
+ bool thisEndOfLine();
+
+ //shape
+ bool prevStartOfShape();
+ bool thisStartOfShape();
+ bool nextStartOfShape();
+
+ /* **** text-oriented methods **** */
+
+ //characters
+ inline bool nextCharacter();
+ inline bool prevCharacter();
+
+ bool nextCursorPosition();
+ bool prevCursorPosition();
+ bool nextLineCursor();
+ bool prevLineCursor();
+
+ //words
+ bool nextStartOfWord();
+ bool prevStartOfWord();
+ bool nextEndOfWord();
+ bool prevEndOfWord();
+
+ //sentences
+ bool nextStartOfSentence();
+ bool prevStartOfSentence();
+ bool nextEndOfSentence();
+ bool prevEndOfSentence();
+
+ //paragraphs
+ bool prevStartOfParagraph();
+ bool thisStartOfParagraph();
+ bool nextStartOfParagraph();
+ //no endOfPara methods because that's just the previous char
+
+ //sources
+ bool prevStartOfSource();
+ bool thisStartOfSource();
+ bool nextStartOfSource();
+
+ //logical cursor movement
+ bool cursorUp();
+ bool cursorDown();
+ bool cursorLeft();
+ bool cursorRight();
+
+ //logical cursor movement (by word or paragraph)
+ bool cursorUpWithControl();
+ bool cursorDownWithControl();
+ bool cursorLeftWithControl();
+ bool cursorRightWithControl();
+
+private:
+ Layout const *_parent_layout;
+ int _glyph_index; /// index into Layout::glyphs, or -1
+ unsigned _char_index; /// index into Layout::character
+ bool _cursor_moving_vertically;
+ /** for cursor up/down movement we must maintain the x position where
+ we started so the cursor doesn't 'drift' left or right with the repeated
+ quantization to character boundaries. */
+ double _x_coordinate;
+
+ inline iterator(Layout const *p, unsigned c, int g)
+ : _parent_layout(p), _glyph_index(g), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {}
+ inline iterator(Layout const *p, unsigned c)
+ : _parent_layout(p), _glyph_index(p->_characters[c].in_glyph), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {}
+ // no dtor required
+ void beginCursorUpDown(); /// stores the current x coordinate so that the cursor won't drift. See #_x_coordinate
+
+ /** moves forward or backwards one cursor position according to the
+ directionality of the current paragraph, but ignoring block progression.
+ Helper for the cursor*() functions. */
+ bool _cursorLeftOrRightLocalX(Direction direction);
+
+ /** moves forward or backwards by until the next character with
+ is_word_start according to the directionality of the current paragraph,
+ but ignoring block progression. Helper for the cursor*WithControl()
+ functions. */
+ bool _cursorLeftOrRightLocalXByWord(Direction direction);
+};
+
+// ************************** inline methods
+
+inline SPCurve* Layout::convertToCurves() const
+ {return convertToCurves(begin(), end());}
+
+inline Layout::iterator Layout::begin() const
+ {return iterator(this, 0, 0);}
+
+inline Layout::iterator Layout::end() const
+ {return iterator(this, _characters.size(), _glyphs.size());}
+
+inline Layout::iterator Layout::charIndexToIterator(int char_index) const
+{
+ if (char_index < 0) return begin();
+ if (char_index >= (int)_characters.size()) return end();
+ return iterator(this, char_index);
+}
+
+inline int Layout::iteratorToCharIndex(Layout::iterator const &it) const
+ {return it._char_index;}
+
+inline void Layout::validateIterator(Layout::iterator *it) const
+{
+ it->_parent_layout = this;
+ if (it->_char_index >= _characters.size()) {
+ it->_char_index = _characters.size();
+ it->_glyph_index = _glyphs.size();
+ } else
+ it->_glyph_index = _characters[it->_char_index].in_glyph;
+}
+
+inline Layout::iterator Layout::getNearestCursorPositionTo(NR::Point &point) const
+ {return getNearestCursorPositionTo(point[0], point[1]);}
+
+inline Layout::iterator Layout::getLetterAt(NR::Point &point) const
+ {return getLetterAt(point[0], point[1]);}
+
+inline unsigned Layout::lineIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _lines.size() - 1 : _characters[it._char_index].chunk(this).in_line;}
+
+inline unsigned Layout::shapeIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _input_wrap_shapes.size() - 1 : _characters[it._char_index].line(this).in_shape;}
+
+inline bool Layout::isWhitespace(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_white;}
+
+inline int Layout::characterAt(iterator const &it) const
+{
+ void *unused;
+ Glib::ustring::iterator text_iter;
+ getSourceOfCharacter(it, &unused, &text_iter);
+ return *text_iter;
+}
+
+inline bool Layout::isCursorPosition(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_cursor_position;}
+
+inline bool Layout::isStartOfWord(iterator const &it) const
+ {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_word_start;}
+
+inline bool Layout::isEndOfWord(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_word_end;}
+
+inline bool Layout::isStartOfSentence(iterator const &it) const
+ {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_sentence_start;}
+
+inline bool Layout::isEndOfSentence(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_sentence_end;}
+
+inline unsigned Layout::paragraphIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _paragraphs.size() - 1 : _characters[it._char_index].line(this).in_paragraph;}
+
+inline Layout::Alignment Layout::paragraphAlignment(iterator const &it) const
+ {return _paragraphs[paragraphIndex(it)].alignment;}
+
+inline bool Layout::iterator::nextGlyph()
+{
+ _cursor_moving_vertically = false;
+ if (_glyph_index >= (int)_parent_layout->_glyphs.size() - 1) {
+ if (_glyph_index == (int)_parent_layout->_glyphs.size()) return false;
+ _char_index = _parent_layout->_characters.size();
+ _glyph_index = _parent_layout->_glyphs.size();
+ }
+ else _char_index = _parent_layout->_glyphs[++_glyph_index].in_character;
+ return true;
+}
+
+inline bool Layout::iterator::prevGlyph()
+{
+ _cursor_moving_vertically = false;
+ if (_glyph_index == 0) return false;
+ _char_index = _parent_layout->_glyphs[--_glyph_index].in_character;
+ return true;
+}
+
+inline bool Layout::iterator::nextCharacter()
+{
+ _cursor_moving_vertically = false;
+ if (_char_index + 1 >= _parent_layout->_characters.size()) {
+ if (_char_index == _parent_layout->_characters.size()) return false;
+ _char_index = _parent_layout->_characters.size();
+ _glyph_index = _parent_layout->_glyphs.size();
+ }
+ else _glyph_index = _parent_layout->_characters[++_char_index].in_glyph;
+ return true;
+}
+
+inline bool Layout::iterator::prevCharacter()
+{
+ _cursor_moving_vertically = false;
+ if (_char_index == 0) return false;
+ _glyph_index = _parent_layout->_characters[--_char_index].in_glyph;
+ return true;
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Makefile_insert b/src/libnrtype/Makefile_insert
new file mode 100644
index 000000000..218cbad6b
--- /dev/null
+++ b/src/libnrtype/Makefile_insert
@@ -0,0 +1,42 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+libnrtype/all: libnrtype/libnrtype.a
+
+libnrtype/clean:
+ rm -f libnrtype/libnrtype.a $(libnrtype_libnrtype_a_OBJECTS)
+
+libnrtype_libnrtype_a_SOURCES = \
+ libnrtype/boundary-type.h \
+ libnrtype/font-style-to-pos.cpp \
+ libnrtype/font-style-to-pos.h \
+ libnrtype/font-glyph.h \
+ libnrtype/font-instance.h \
+ libnrtype/font-style.h \
+ libnrtype/nr-type-pos-def.cpp \
+ libnrtype/nr-type-pos-def.h \
+ libnrtype/nr-type-primitives.cpp \
+ libnrtype/nr-type-primitives.h \
+ libnrtype/nrtype-forward.h \
+ libnrtype/FontFactory.cpp \
+ libnrtype/FontFactory.h \
+ libnrtype/FontInstance.cpp \
+ libnrtype/one-box.h \
+ libnrtype/one-glyph.h \
+ libnrtype/one-para.h \
+ libnrtype/RasterFont.cpp \
+ libnrtype/RasterFont.h \
+ libnrtype/raster-glyph.h \
+ libnrtype/raster-position.h \
+ libnrtype/text-boundary.h \
+ libnrtype/TextWrapper.cpp \
+ libnrtype/TextWrapper.h \
+ libnrtype/Layout-TNG-Compute.cpp \
+ libnrtype/Layout-TNG-Input.cpp \
+ libnrtype/Layout-TNG-OutIter.cpp \
+ libnrtype/Layout-TNG-Output.cpp \
+ libnrtype/Layout-TNG-Scanline-Maker.h \
+ libnrtype/Layout-TNG-Scanline-Makers.cpp \
+ libnrtype/Layout-TNG.cpp \
+ libnrtype/Layout-TNG.h
+
+
diff --git a/src/libnrtype/RasterFont.cpp b/src/libnrtype/RasterFont.cpp
new file mode 100644
index 000000000..68ecb2e4d
--- /dev/null
+++ b/src/libnrtype/RasterFont.cpp
@@ -0,0 +1,431 @@
+/*
+ * RasterFont.cpp
+ * testICU
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "RasterFont.h"
+
+#include <livarot/float-line.h>
+#include <livarot/int-line.h>
+#include <livarot/Path.h>
+#include <livarot/Shape.h>
+#include <libnr/nr-pixblock.h>
+#include <libnrtype/font-instance.h>
+#include <libnrtype/raster-glyph.h>
+#include <libnrtype/raster-position.h>
+
+
+static void glyph_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven);
+
+void font_style::Apply(Path* src,Shape* dest) {
+ src->Convert(1);
+ if ( stroke_width > 0 ) {
+ if ( nbDash > 0 ) {
+ double dlen = 0.0;
+ const float scale = 1/*NR_MATRIX_DF_EXPANSION (&transform)*/;
+ for (int i = 0; i < nbDash; i++) dlen += dashes[i] * scale;
+ if (dlen >= 0.01) {
+ float sc_offset = dash_offset * scale;
+ float *tdashs=(float*)malloc((nbDash+1)*sizeof(float));
+ while ( sc_offset >= dlen ) sc_offset-=dlen;
+ tdashs[0]=dashes[0] * scale;
+ for (int i=1;i<nbDash;i++) tdashs[i] = tdashs[i - 1] + dashes[i] * scale;
+ src->DashPolyline(0.0,0.0,dlen,nbDash,tdashs,true,sc_offset);
+ free(tdashs);
+ }
+ }
+ src->Stroke(dest, false, 0.5*stroke_width, stroke_join, stroke_cap, 0.5*stroke_width*stroke_miter_limit);
+ } else {
+ src->Fill(dest,0);
+ }
+}
+
+raster_font::raster_font(font_style const &fstyle) :
+ daddy(NULL),
+ refCount(0),
+ style(fstyle),
+ glyph_id_to_raster_glyph_no(),
+ nbBase(0),
+ maxBase(0),
+ bases(NULL)
+{
+ // printf("raster font born\n");
+}
+
+raster_font::~raster_font(void)
+{
+// printf("raster font death\n");
+ if ( daddy ) daddy->RemoveRasterFont(this);
+ daddy=NULL;
+ if ( style.dashes ) free(style.dashes);
+ style.dashes=NULL;
+ for (int i=0;i<nbBase;i++) delete bases[i];
+ if ( bases ) free(bases);
+}
+
+void raster_font::Unref(void)
+{
+ refCount--;
+// printf("raster %x unref'd %i\n",this,refCount);
+ if ( refCount <= 0 ) {
+ if ( daddy ) daddy->RemoveRasterFont(this);
+ daddy=NULL;
+ delete this;
+ }
+}
+void raster_font::Ref(void)
+{
+ refCount++;
+// printf("raster %x ref'd %i\n",this,refCount);
+}
+raster_glyph* raster_font::GetGlyph(int glyph_id)
+{
+ raster_glyph *res=NULL;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ LoadRasterGlyph(glyph_id);
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { // recheck
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ return res;
+}
+NR::Point raster_font::Advance(int glyph_id)
+{
+ if ( daddy == NULL ) return NR::Point(0,0);
+ double a=daddy->Advance(glyph_id,style.vertical);
+ NR::Point f_a=(style.vertical)?NR::Point(0,a):NR::Point(a,0);
+ return f_a*style.transform;
+}
+void raster_font::BBox(int glyph_id,NRRect *area)
+{
+ area->x0=area->y0=area->x1=area->y1=0;
+ if ( daddy == NULL ) return;
+ NR::Rect res=daddy->BBox(glyph_id);
+ NR::Point bmi=res.min(),bma=res.max();
+ NR::Point tlp(bmi[0],bmi[1]),trp(bma[0],bmi[1]),blp(bmi[0],bma[1]),brp(bma[0],bma[1]);
+ tlp=tlp*style.transform;
+ trp=trp*style.transform;
+ blp=blp*style.transform;
+ brp=brp*style.transform;
+ res=NR::Rect(tlp,trp);
+ res.expandTo(blp);
+ res.expandTo(brp);
+ area->x0=(res.min())[0];
+ area->y0=(res.min())[1];
+ area->x1=(res.max())[0];
+ area->y1=(res.max())[1];
+}
+
+void raster_font::LoadRasterGlyph(int glyph_id)
+{
+ raster_glyph *res=NULL;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ res=new raster_glyph();
+ res->daddy=this;
+ res->glyph_id=glyph_id;
+ if ( nbBase >= maxBase ) {
+ maxBase=2*nbBase+1;
+ bases=(raster_glyph**)realloc(bases,maxBase*sizeof(raster_glyph*));
+ }
+ bases[nbBase]=res;
+ glyph_id_to_raster_glyph_no[glyph_id]=nbBase;
+ nbBase++;
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ if ( res == NULL ) return;
+ if ( res->polygon ) return;
+ if ( res->outline == NULL ) {
+ if ( daddy == NULL ) return;
+ Path* outline=daddy->Outline(glyph_id,NULL);
+ res->outline=new Path;
+ if ( outline ) {
+ res->outline->Copy(outline);
+ }
+ res->outline->Transform(style.transform);
+ }
+ Shape* temp=new Shape;
+ res->polygon=new Shape;
+ style.Apply(res->outline,temp);
+ if ( style.stroke_width > 0 ) {
+ res->polygon->ConvertToShape(temp,fill_nonZero);
+ } else {
+ res->polygon->ConvertToShape(temp,fill_oddEven);
+ }
+ delete temp;
+
+ res->SetSubPixelPositionning(4);
+}
+void raster_font::RemoveRasterGlyph(raster_glyph* who)
+{
+ if ( who == NULL ) return;
+ int glyph_id=who->glyph_id;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ int no=glyph_id_to_raster_glyph_no[glyph_id];
+ if ( no >= nbBase-1 ) {
+ } else {
+ bases[no]=bases[--nbBase];
+ glyph_id_to_raster_glyph_no[bases[no]->glyph_id]=no;
+ }
+ glyph_id_to_raster_glyph_no.erase(glyph_id_to_raster_glyph_no.find(glyph_id));
+ } else {
+ // not here
+ }
+}
+
+/*int top,bottom; // baseline is y=0
+ int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i
+ int nbRun;
+ float_ligne_run* runs;*/
+
+raster_position::raster_position(void)
+{
+ top=0;
+ bottom=-1;
+ run_on_line=NULL;
+ nbRun=0;
+ runs=NULL;
+}
+raster_position::~raster_position(void)
+{
+ if ( run_on_line ) free(run_on_line);
+ if ( runs ) free(runs);
+}
+
+void raster_position::AppendRuns(std::vector<float_ligne_run> const &r,int y)
+{
+ if ( top > bottom ) {
+ top=bottom=y;
+ if ( run_on_line ) free(run_on_line);
+ run_on_line=(int*)malloc(sizeof(int));
+ run_on_line[0]=0;
+ } else {
+ if ( y < top ) {
+ // printf("wtf?\n");
+ return;
+ } else if ( y > bottom ) {
+ int ob=bottom;
+ bottom=y;
+ run_on_line=(int*)realloc(run_on_line,(bottom-top+1)*sizeof(int));
+ for (int i=ob+1;i<=bottom;i++) run_on_line[i-top]=0;
+ }
+ }
+
+ if ( r.empty() == false) {
+ run_on_line[y - top] = r.size();
+ runs = (float_ligne_run *) realloc(runs, (nbRun + r.size()) * sizeof(float_ligne_run));
+
+ for (int i = 0; i < int(r.size()); i++) {
+ runs[nbRun + i] = r[i];
+ }
+
+ nbRun += r.size();
+ }
+}
+void raster_position::Blit(float ph,int pv,NRPixBlock &over)
+{
+ int base_y=top+pv;
+ int first_y=top+pv,last_y=bottom+pv;
+ if ( first_y < over.area.y0 ) first_y=over.area.y0;
+ if ( last_y >= over.area.y1 ) last_y=over.area.y1-1;
+ if ( first_y > last_y ) return;
+ IntLigne *theIL=new IntLigne();
+ FloatLigne *theI=new FloatLigne();
+
+ char* mdata=(char*)over.data.px;
+ if ( over.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)over.data.p;
+
+ for (int y=first_y;y<=last_y;y++) {
+ int first_r=0,last_r=0;
+ for (int i=base_y;i<y;i++) first_r+=run_on_line[i-base_y];
+ last_r=first_r+run_on_line[y-base_y]-1;
+ if ( first_r <= last_r ) {
+ theI->Reset();
+ for (int i=first_r;i<=last_r;i++) theI->AddRun(runs[i].st+ph,runs[i].en+ph,runs[i].vst,runs[i].ven,runs[i].pente);
+// for (int i=first_r;i<=last_r;i++) {runs[i].st+=ph;runs[i].en+=ph;}
+// theI->nbRun=theI->maxRun=last_r-first_r+1;
+// theI->runs=runs+first_r;
+
+ theIL->Copy(theI);
+ raster_info dest;
+ dest.startPix=over.area.x0;
+ dest.endPix=over.area.x1;
+ dest.sth=over.area.x0;
+ dest.stv=y;
+ dest.buffer=((uint32_t*)(mdata+(over.rs*(y-over.area.y0))));
+ theIL->Raster(dest,NULL,glyph_run_A8_OR);
+
+// theI->nbRun=theI->maxRun=0;
+// theI->runs=NULL;
+// for (int i=first_r;i<=last_r;i++) {runs[i].st-=ph;runs[i].en-=ph;}
+ }
+ }
+ delete theIL;
+ delete theI;
+}
+
+
+/* raster_font* daddy;
+ int glyph_id;
+
+ Path* outline;
+ Shape* polygon;
+
+ int nb_sub_pixel;
+ raster_position* sub_pixel;*/
+
+raster_glyph::raster_glyph(void)
+{
+ daddy=NULL;
+ glyph_id=0;
+ outline=NULL;
+ polygon=NULL;
+ nb_sub_pixel=0;
+ sub_pixel=NULL;
+}
+raster_glyph::~raster_glyph(void)
+{
+ if ( outline ) delete outline;
+ if ( polygon ) delete polygon;
+ if ( sub_pixel ) delete [] sub_pixel;
+}
+
+
+void raster_glyph::SetSubPixelPositionning(int nb_pos)
+{
+ nb_sub_pixel=nb_pos;
+ if ( nb_sub_pixel <= 0 ) nb_sub_pixel=0;
+ if ( sub_pixel ) delete [] sub_pixel;
+ sub_pixel=NULL;
+ if ( nb_sub_pixel > 0 ) {
+ sub_pixel=new raster_position[nb_pos];
+ if ( polygon ) {
+ for (int i=0;i<nb_sub_pixel;i++) LoadSubPixelPosition(i);
+ }
+ }
+}
+void raster_glyph::LoadSubPixelPosition(int no)
+{
+ if ( no < 0 || no >= nb_sub_pixel ) return;
+ if ( sub_pixel[no].top <= sub_pixel[no].bottom ) return;
+ if ( polygon == NULL ) {
+ if ( daddy == NULL ) return;
+ daddy->LoadRasterGlyph(glyph_id);
+ if ( polygon == NULL ) return;
+ }
+
+ float sub_delta=((float)no)/((float)nb_sub_pixel);
+
+ polygon->CalcBBox();
+
+ float l=polygon->leftX,r=polygon->rightX,t=polygon->topY,b=polygon->bottomY;
+ int il,ir,it,ib;
+ il=(int)floor(l);
+ ir=(int)ceil(r);
+ it=(int)floor(t);
+ ib=(int)ceil(b);
+
+ // version par FloatLigne
+ int curPt;
+ float curY;
+ polygon->BeginQuickRaster(curY, curPt);
+
+ FloatLigne* theI=new FloatLigne();
+
+ polygon->DirectQuickScan(curY,curPt,(float)(it-1)+sub_delta,true,1.0);
+
+ for (int y=it-1;y<ib;y++) {
+ theI->Reset();
+ polygon->QuickScan(curY,curPt,((float)(y+1))+sub_delta,theI,1.0);
+ theI->Flatten();
+
+ sub_pixel[no].AppendRuns(theI->runs, y);
+ }
+ polygon->EndQuickRaster();
+ delete theI;
+}
+
+void raster_glyph::Blit(const NR::Point &at,NRPixBlock &over)
+{
+ if ( nb_sub_pixel <= 0 ) return;
+ int pv=(int)ceil(at[1]);
+ double dec=4*(ceil(at[1])-at[1]);
+ int no=(int)floor(dec);
+ sub_pixel[no].Blit(at[0],pv,over);
+}
+
+
+
+static void
+glyph_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 = 65025 - (255 - c0_24) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ 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 = 65025 - (255 - c0_24) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ } 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 = 65025 - (255 - ca) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ d += 1;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+ }
+}
diff --git a/src/libnrtype/RasterFont.h b/src/libnrtype/RasterFont.h
new file mode 100644
index 000000000..03665c69a
--- /dev/null
+++ b/src/libnrtype/RasterFont.h
@@ -0,0 +1,66 @@
+/*
+ * RasterFont.h
+ * testICU
+ *
+ */
+
+#ifndef my_raster_font
+#define my_raster_font
+
+#include <ext/hash_map>
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <libnrtype/font-style.h>
+
+// one rasterfont is one way to draw a font on the screen
+// the way it's drawn is stored in style
+class raster_font {
+public:
+ font_instance* daddy;
+ int refCount;
+
+ font_style style;
+
+ __gnu_cxx::hash_map<int,int> glyph_id_to_raster_glyph_no;
+ // an array of glyphs in this rasterfont.
+ // it's a bit redundant with the one in the daddy font_instance, but these glyphs
+ // contains the real rasterization data
+ int nbBase,maxBase;
+ raster_glyph** bases;
+
+ explicit raster_font(font_style const &fstyle);
+ ~raster_font(void);
+
+ void Unref(void);
+ void Ref(void);
+
+ // utility functions
+ NR::Point Advance(int glyph_id);
+ void BBox(int glyph_id,NRRect *area);
+
+ // attempts to load a glyph and return a raster_glyph on which you can call Blit
+ raster_glyph* GetGlyph(int glyph_id);
+ // utility
+ void LoadRasterGlyph(int glyph_id); // refreshes outline/polygon if needed
+ void RemoveRasterGlyph(raster_glyph* who);
+
+private:
+ /* Disable the default copy constructor and operator=: they do the wrong thing for refCount. */
+ raster_font(raster_font const &);
+ raster_font &operator=(raster_font const &);
+};
+
+#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/libnrtype/TextWrapper.cpp b/src/libnrtype/TextWrapper.cpp
new file mode 100644
index 000000000..ce8797322
--- /dev/null
+++ b/src/libnrtype/TextWrapper.cpp
@@ -0,0 +1,937 @@
+/*
+ * TextWrapper.cpp
+ * testICU
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "TextWrapper.h"
+
+#include <libnrtype/font-instance.h>
+#include "libnrtype/text-boundary.h"
+#include "libnrtype/one-glyph.h"
+#include "libnrtype/one-box.h"
+#include "libnrtype/one-para.h"
+
+#include <svg/svg.h>
+
+text_wrapper::text_wrapper(void)
+{
+ // voids everything
+ utf8_text = NULL;
+ uni32_text = NULL;
+ glyph_text = NULL;
+ utf8_length = 0;
+ uni32_length = 0;
+ glyph_length = 0;
+ utf8_codepoint = NULL;
+ uni32_codepoint = NULL;
+ default_font = NULL;
+ bounds = NULL;
+ nbBound = maxBound = 0;
+ boxes = NULL;
+ nbBox = maxBox = 0;
+ paras = NULL;
+ nbPara = maxPara = 0;
+ kern_x = kern_y = NULL;
+ last_addition = -1;
+ // inits the pangolayout with default params
+ font_factory *font_src = font_factory::Default();
+ pLayout = pango_layout_new(font_src->fontContext);
+ pango_layout_set_single_paragraph_mode(pLayout, true);
+ pango_layout_set_width(pLayout, -1);
+}
+text_wrapper::~text_wrapper(void)
+{
+ // frees everything
+ //printf("delete\n");
+ g_object_unref(pLayout);
+ if ( utf8_text ) free(utf8_text);
+ if ( uni32_text ) free(uni32_text);
+ if ( glyph_text ) free(glyph_text);
+ if ( utf8_codepoint ) free(utf8_codepoint);
+ if ( uni32_codepoint ) free(uni32_codepoint);
+ if ( default_font ) default_font->Unref();
+ if ( boxes ) free(boxes);
+ if ( paras ) free(paras);
+ if ( kern_x ) free(kern_x);
+ if ( kern_y ) free(kern_y);
+ for (unsigned i = 0; i < nbBound; i++) {
+ switch ( bounds[i].type ) {
+ default:
+ break;
+ }
+ }
+ if ( bounds ) free(bounds);
+ default_font = NULL;
+
+}
+
+void text_wrapper::SetDefaultFont(font_instance *iFont)
+{
+ // refcounts the font for our internal uses
+ if ( iFont ) iFont->Ref();
+ if ( default_font ) default_font->Unref();
+ default_font = iFont;
+}
+
+void text_wrapper::AppendUTF8(char const *text, int len)
+{
+ // appends text to what needs to be handled
+ if ( utf8_length <= 0 ) {
+ // a first check to prevent the text from containing a leading line return (which
+ // is probably a bug anyway)
+ if ( text[0] == '\n' || text[0] == '\r' ) {
+ /* fixme: Should the below be `0 <= len' ? The existing code looks wrong
+ * for the case that len==0.
+ * TODO: Document the meaning of the len parameter. */
+ if ( len > 0 ) {
+ while ( len > 0 && ( *text == '\n' || *text == '\r' ) ) {text++; len--;}
+ } else {
+ while ( *text == '\n' || *text == '\r' ) text++;
+ }
+ }
+ }
+ if ( len == 0 || text == NULL || *text == 0 ) return;
+ g_return_if_fail(g_utf8_validate(text, len, NULL));
+
+ // compute the length
+ int const nlen = ( len < 0
+ ? strlen(text)
+ : len );
+ /* effic: Use g_utf8_validate's last param to do this. */
+
+ // prepare to store the additional text
+ /* effic: (Not an issue for the sole caller at the time of writing.) This implementation
+ takes quadratic time if the text is composed of n appends. Use a proper data structure.
+ STL vector would suffice. */
+ utf8_text = (char*)realloc(utf8_text, (utf8_length + nlen + 1) * sizeof(char));
+ uni32_codepoint = (int*)realloc(uni32_codepoint, (utf8_length + nlen + 1) * sizeof(int));
+
+ // copy the source text in the newly lengthened array
+ memcpy(utf8_text + utf8_length, text, nlen * sizeof(char));
+ utf8_length += nlen;
+ utf8_text[utf8_length] = 0;
+ // remember where the text ended, before we recompute it, for the dx/dy we'll add after that (if any)
+ last_addition = uni32_length;
+ // free old uni32 structures (instead of incrementally putting the text)
+ if ( uni32_text ) free(uni32_text);
+ if ( utf8_codepoint ) free(utf8_codepoint);
+ uni32_text = NULL;
+ utf8_codepoint = NULL;
+ uni32_length = 0;
+ {
+ // recompute length of uni32 text
+ char *p = utf8_text;
+ while ( *p ) {
+ p = g_utf8_next_char(p); // since we validated the input text, we can use this 'fast' macro
+ uni32_length++;
+ }
+ }
+ // realloc the arrays
+ uni32_text = (gunichar*)malloc((uni32_length + 1) * sizeof(gunichar));
+ utf8_codepoint = (int*)malloc((uni32_length + 1) * sizeof(int));
+ {
+ // read the utf8 string and compute codepoints positions
+ char *p = utf8_text;
+ int i = 0;
+ int l_o = 0;
+ while ( *p ) {
+ // get the new codepoint
+ uni32_text[i] = g_utf8_get_char(p);
+ // compute the offset in the utf8_string
+ unsigned int n_o = (unsigned int)(p - utf8_text);
+ // record the codepoint's start
+ utf8_codepoint[i] = n_o;
+ // record the codepoint's correspondance in the utf8 string
+ for (unsigned int j = l_o; j < n_o; j++) uni32_codepoint[j] = i - 1;
+ // and move on
+ l_o = n_o;
+ p = g_utf8_next_char(p);
+ i++;
+ }
+ // the termination of the loop
+ for (int j = l_o; j < utf8_length; j++) uni32_codepoint[j] = uni32_length - 1;
+ uni32_codepoint[utf8_length] = uni32_length;
+ uni32_text[uni32_length] = 0;
+ utf8_codepoint[uni32_length] = utf8_length;
+ }
+ // if needed, fill the dx/dy arrays with 0 for the newly created part
+ // these will be filled by a KernXForLastAddition() right after this function
+ // note that the SVG spec doesn't require you to give a dx for each codepoint,
+ // so setting the dx to 0 is mandatory
+ if ( uni32_length > last_addition ) {
+ if ( kern_x ) {
+ kern_x = (double*)realloc(kern_x, (uni32_length + 1) * sizeof(double));
+ for (int i = last_addition; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ if ( kern_y ) {
+ kern_y = (double*)realloc(kern_y, (uni32_length + 1) * sizeof(double));
+ for (int i = last_addition; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ }
+}
+
+void text_wrapper::DoLayout(void)
+{
+ // THE function
+ // first some sanity checks
+ if ( default_font == NULL ) return;
+ if ( uni32_length <= 0 || utf8_length <= 0 ) return;
+ // prepare the pangolayout object
+ {
+ //char *tc = pango_font_description_to_string(default_font->descr);
+ //printf("layout with %s\n", tc);
+ //free(tc);
+ }
+ pango_layout_set_font_description(pLayout, default_font->descr);
+ pango_layout_set_text(pLayout, utf8_text, utf8_length);
+ // reset the glyph string
+ if ( glyph_text ) free(glyph_text);
+ glyph_text = NULL;
+ glyph_length = 0;
+
+ double pango_to_ink = (1.0 / ((double)PANGO_SCALE)); // utility
+ int max_g = 0;
+ PangoLayoutIter *pIter = pango_layout_get_iter(pLayout); // and go!
+ do {
+ PangoLayoutLine *pLine = pango_layout_iter_get_line(pIter); // no need for unref
+ int plOffset = pLine->start_index; // start of the line in the uni32_text
+ PangoRectangle ink_r, log_r;
+ pango_layout_iter_get_line_extents(pIter, &ink_r, &log_r);
+ double plY = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.y); // start position of this line of the layout
+ double plX = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.x);
+ GSList *curR = pLine->runs; // get ready to iterate over the runs of this line
+ while ( curR ) {
+ PangoLayoutRun *pRun = (PangoLayoutRun*)curR->data;
+ int prOffset = pRun->item->offset; // start of the run in the line
+ if ( pRun ) {
+ // a run has uniform font/directionality/etc...
+ int o_g_l = glyph_length; // save the index of the first glyph we'll add
+ for (int i = 0; i < pRun->glyphs->num_glyphs; i++) { // add glyph sequentially, reading them from the run
+ // realloc the structures
+ if ( glyph_length >= max_g ) {
+ max_g = 2 * glyph_length + 1;
+ glyph_text = (one_glyph*)realloc(glyph_text, (max_g + 1) * sizeof(one_glyph));
+ }
+ // fill the glyph info
+ glyph_text[glyph_length].font = pRun->item->analysis.font;
+ glyph_text[glyph_length].gl = pRun->glyphs->glyphs[i].glyph;
+ glyph_text[glyph_length].uni_st = plOffset + prOffset + pRun->glyphs->log_clusters[i];
+ // depending on the directionality, the last uni32 codepoint for this glyph is the first of the next char
+ // or the first of the previous
+ if ( pRun->item->analysis.level == 1 ) {
+ // rtl
+ if ( i < pRun->glyphs->num_glyphs - 1 ) {
+ glyph_text[glyph_length + 1].uni_en = glyph_text[glyph_length].uni_st;
+ }
+ glyph_text[glyph_length].uni_dir = 1;
+ glyph_text[glyph_length + 1].uni_dir = 1; // set the directionality for the next too, so that the last glyph in
+ // the array has the correct direction
+ } else {
+ // ltr
+ if ( i > 0 ) {
+ glyph_text[glyph_length - 1].uni_en = glyph_text[glyph_length].uni_st;
+ }
+ glyph_text[glyph_length].uni_dir = 0;
+ glyph_text[glyph_length + 1].uni_dir = 0;
+ }
+ // set the position
+ // the layout is an infinite line
+ glyph_text[glyph_length].x = plX + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.x_offset);
+ glyph_text[glyph_length].y = plY + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.y_offset);
+ // advance to the next glyph
+ plX += pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.width);
+ // and set the next glyph's position, in case it's the terminating glyph
+ glyph_text[glyph_length + 1].x = plX;
+ glyph_text[glyph_length + 1].y = plY;
+ glyph_length++;
+ }
+ // and finish filling the info
+ // notably, the uni_en of the last char in ltr text and the uni_en of the first in rtl are still not set
+ if ( pRun->item->analysis.level == 1 ) {
+ // rtl
+ if ( glyph_length > o_g_l ) glyph_text[o_g_l].uni_en = plOffset + prOffset + pRun->item->length;
+ } else {
+ if ( glyph_length > 0 ) glyph_text[glyph_length - 1].uni_en = plOffset + prOffset + pRun->item->length;
+ }
+ // the terminating glyph has glyph_id=0 because it means 'no glyph'
+ glyph_text[glyph_length].gl = 0;
+ // and is associated with no text (but you cannot set uni_st=uni_en=0, because the termination
+ // is expected to be the glyph for the termination of the uni32_text)
+ glyph_text[glyph_length].uni_st = glyph_text[glyph_length].uni_en = plOffset + prOffset + pRun->item->length;
+ }
+ curR = curR->next;
+ }
+ } while ( pango_layout_iter_next_line(pIter) );
+ pango_layout_iter_free(pIter);
+
+ // grunt work done. now some additional info for layout: computing letters, mostly (one letter = several glyphs sometimes)
+ PangoLogAttr *pAttrs = NULL;
+ int nbAttr = 0;
+ // get the layout attrs, they hold the boundaries pango computed
+ pango_layout_get_log_attrs(pLayout, &pAttrs, &nbAttr);
+ // feed to MakeTextBoundaries which knows what to do with these
+ MakeTextBoundaries(pAttrs, nbAttr);
+ // the array of boundaries is full, but out-of-order
+ SortBoundaries();
+ // boundary array is ready to be used, call chunktext to fill the *_start fields of the glyphs, and compute
+ // the boxed version of the text for sp-typeset
+ ChunkText();
+ // get rid of the attributes
+ if ( pAttrs ) g_free(pAttrs);
+
+ // cleaning up
+ for (int i = 0; i < glyph_length; i++) {
+ glyph_text[i].uni_st = uni32_codepoint[glyph_text[i].uni_st];
+ glyph_text[i].uni_en = uni32_codepoint[glyph_text[i].uni_en];
+ glyph_text[i].x /= 512; // why is this not default_font->daddy->fontsize?
+ glyph_text[i].y /= 512;
+ }
+ if ( glyph_length > 0 ) {
+ glyph_text[glyph_length].x /= 512;
+ glyph_text[glyph_length].y /= 512;
+ }
+}
+
+void text_wrapper::ChunkText(void)
+{
+ int c_st = -1, c_en = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int g_st = glyph_text[i].uni_st, g_en = glyph_text[i].uni_en;
+ glyph_text[i].char_start = false;
+ glyph_text[i].word_start = false;
+ glyph_text[i].para_start = false;
+ // boundaries depend on the directionality
+ // letter boundaries correspond to the glyphs starting one letter when you read them left to right (always)
+ // because that's the order they are stored into in the glyph_text array
+ if ( glyph_text[i].uni_dir == 0 ) {
+ if ( IsBound(bnd_char, g_st, c_st) ) { // check if there is a charcater (=letter in pango speak) at this position
+ // can be a 'start' boundary or a 'end' boundary, doesn't matter, as long
+ // as you get from one letter to the next at this position
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].char_start = true;
+ }
+ if ( IsBound(bnd_word, g_st, c_st) ) {
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].word_start = true;
+ }
+ if ( IsBound(bnd_para, g_st, c_st) ) {
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].para_start = true;
+ }
+ } else {
+ if ( IsBound(bnd_char, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].char_start = true;
+ }
+ if ( IsBound(bnd_word, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].word_start = true;
+ }
+ if ( IsBound(bnd_para, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].para_start = true;
+ }
+ }
+ }
+
+ if ( glyph_length > 0 ) {
+ glyph_text[glyph_length].char_start = true;
+ glyph_text[glyph_length].word_start = true;
+ glyph_text[glyph_length].para_start = true;
+ }
+ {
+ // doing little boxes
+ int g_st = -1, g_en = -1;
+ while ( NextWord(g_st, g_en) ) {
+ // check uniformity of fonts
+ if ( g_st < g_en ) {
+ int n_st = g_st;
+ int n_en = g_st;
+ bool first = true;
+ do {
+ n_st = n_en;
+ PangoFont *curPF = glyph_text[n_st].font;
+ do {
+ n_en++;
+ } while ( n_en < g_en && glyph_text[n_en].font == curPF );
+ if ( nbBox >= maxBox ) {
+ maxBox = 2 * nbBox + 1;
+ boxes = (one_box*)realloc(boxes, maxBox * sizeof(one_box));
+ }
+ boxes[nbBox].g_st = n_st;
+ boxes[nbBox].g_en = n_en;
+ boxes[nbBox].word_start = first;
+ boxes[nbBox].word_end = (n_en >= g_en);
+ nbBox++;
+ first = false;
+ } while ( n_en < g_en );
+ }
+ }
+ }
+ {
+ // doing little paras
+ int g_st = -1, g_en = -1;
+ while ( NextPara(g_st, g_en) ) {
+ int b_st = 0;
+ while ( b_st < nbBox && boxes[b_st].g_st < g_st ) b_st++;
+ if ( b_st < nbBox && boxes[b_st].g_st == g_st ) {
+ int b_en = b_st;
+ while ( b_en < nbBox && boxes[b_en].g_en < g_en ) b_en++;
+ if ( b_en < nbBox && boxes[b_en].g_en == g_en ) {
+ if ( nbPara >= maxPara ) {
+ maxPara = 2 * nbPara + 1;
+ paras = (one_para*)realloc(paras, maxPara * sizeof(one_para));
+ }
+ paras[nbPara].b_st = b_st;
+ paras[nbPara].b_en = b_en;
+ nbPara++;
+ }
+ }
+ }
+ }
+}
+
+void text_wrapper::MakeVertical(void)
+{
+ if ( glyph_length <= 0 ) return;
+ font_factory *font_src = font_factory::Default();
+
+ // explanation: when laying out text vertically, you must keep the glyphs of a single letter together
+ double baseY = glyph_text[0].y;
+ double lastY = baseY;
+ int g_st = 0, g_en = 0;
+ int nbLetter = 0;
+ PangoFont *curPF = NULL;
+ font_instance *curF = NULL;
+ do {
+ // move to the next letter boundary
+ g_st = g_en;
+ do {
+ g_en++;
+ } while ( g_en < glyph_length && glyph_text[g_en].char_start == false );
+ // got a letter
+ if ( g_st < g_en && g_en <= glyph_length ) {
+ // we need to compute the letter's width (in case sometimes we implement the flushleft and flushright)
+ // and the total height for this letter. for example accents usually have 0 width, so this is not
+ // stupid
+ double n_adv = 0;
+ double minX = glyph_text[g_st].x, maxX = glyph_text[g_st].x;
+ for (int i = g_st; i < g_en; i++) {
+ if ( glyph_text[i].font != curPF ) { // font is not the same as the one of the previous glyph
+ // so we need to update curF
+ if ( curF ) curF->Unref();
+ curF = NULL;
+ curPF = glyph_text[i].font;
+ if ( curPF ) {
+ PangoFontDescription *pfd = pango_font_describe(curPF);
+ curF = font_src->Face(pfd);
+ pango_font_description_free(pfd);
+ }
+ }
+ double x = ( curF
+ ? curF->Advance(glyph_text[i].gl, true)
+ : 0 );
+ if ( x > n_adv ) n_adv = x;
+ if ( glyph_text[i].x < minX ) minX = glyph_text[i].x;
+ if ( glyph_text[i].x > maxX ) maxX = glyph_text[i].x;
+ }
+ lastY += n_adv;
+ // and put the glyphs of this letter at their new position
+ for (int i = g_st; i < g_en; i++) {
+ glyph_text[i].x -= minX;
+ glyph_text[i].y += lastY;
+ }
+ g_st = g_en;
+ }
+ nbLetter++;
+ } while ( g_st < glyph_length );
+ if ( curF ) curF->Unref();
+}
+
+void text_wrapper::MergeWhiteSpace(void)
+{
+ if ( glyph_length <= 0 ) return;
+ // scans the glyphs and shifts them accordingly
+ double delta_x = 0, delta_y = 0;
+ bool inWhite = true;
+ int wpos = 0, rpos = 0; // wpos is the position where we read glyphs, rpos is the position where we write them back
+ // since we only eat whitespace, wpos <= rpos
+ for (rpos = 0; rpos < glyph_length; rpos++) {
+ // copy the glyph at its new position
+ glyph_text[wpos].gl = glyph_text[rpos].gl;
+ glyph_text[wpos].uni_st = glyph_text[rpos].uni_st;
+ glyph_text[wpos].uni_en = glyph_text[rpos].uni_en;
+ glyph_text[wpos].font = glyph_text[rpos].font;
+ glyph_text[wpos].x = glyph_text[rpos].x - delta_x;
+ glyph_text[wpos].y = glyph_text[rpos].y - delta_y;
+ wpos++; // move the write position
+ if ( g_unichar_isspace(uni32_text[glyph_text[rpos].uni_st]) ) {
+ if ( inWhite ) {
+ // eat me: 2 steps: first add the shift in position to the cumulated shift
+ delta_x += glyph_text[rpos + 1].x - glyph_text[rpos].x;
+ delta_y += glyph_text[rpos + 1].y - glyph_text[rpos].y;
+ // then move the write position back. this way, we'll overwrite the previous whitespace with the new glyph
+ // since this is only done after the first whitespace, we only keep the first whitespace
+ wpos--;
+ }
+ inWhite = true;
+ } else {
+ inWhite = false;
+ }
+ }
+ // and the terminating glyph (we should probably copy the rest of the glyph's info, too)
+ glyph_text[wpos].x = glyph_text[rpos].x - delta_x;
+ glyph_text[wpos].y = glyph_text[rpos].y - delta_y;
+ // sets the new length
+ glyph_length = wpos;
+}
+
+// utility: computes the number of letters in the layout
+int text_wrapper::NbLetter(int g_st, int g_en)
+{
+ if ( glyph_length <= 0 ) return 0;
+ if ( g_st < 0 || g_st >= g_en ) {
+ g_st = 0;
+ g_en = glyph_length;
+ }
+ int nbLetter = 0;
+ for (int i = g_st; i < g_en; i++) {
+ if ( glyph_text[i].char_start ) nbLetter++;
+ }
+ return nbLetter;
+}
+
+void text_wrapper::AddLetterSpacing(double dx, double dy, int g_st, int g_en)
+{
+ if ( glyph_length <= 0 ) return;
+ if ( g_st < 0 || g_st >= g_en ) {
+ g_st = 0;
+ g_en = glyph_length;
+ }
+ int nbLetter = 0;
+
+ // letterspacing means: add 'dx * (nbLetter - 1)' to the x position
+ // so we just scan the glyph string
+ for (int i = g_st; i < g_en; i++) {
+ if ( i > g_st && glyph_text[i].char_start ) nbLetter++;
+ glyph_text[i].x += dx * nbLetter;
+ glyph_text[i].y += dy * nbLetter;
+ }
+ if ( glyph_text[g_en].char_start ) nbLetter++;
+ glyph_text[g_en].x += dx * nbLetter;
+ glyph_text[g_en].y += dy * nbLetter;
+}
+
+/** @name Movement commands
+ * Miscellaneous functions for moving about glyphs.
+ * \a st and \en are start and end glyph indices.
+ * The three methods differ only in whether they look for .char_start, .word_start or .para_start.
+ * \return True iff a next character was found. (False iff we've already reached the end.)
+ */
+//@{
+bool text_wrapper::NextChar(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].char_start == false );
+ return true;
+}
+bool text_wrapper::NextWord(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].word_start == false );
+ return true;
+}
+bool text_wrapper::NextPara(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].para_start == false );
+ return true;
+}
+//@}
+
+// boundary handling
+/**
+ * Append \a ib to our bounds array.
+ * \return The index of the new element.
+ */
+unsigned text_wrapper::AddBoundary(text_boundary const &ib)
+{
+ if ( nbBound >= maxBound ) {
+ maxBound = 2 * nbBound + 1;
+ bounds = (text_boundary*)realloc(bounds, maxBound * sizeof(text_boundary));
+ }
+ unsigned const ix = nbBound++;
+ bounds[ix] = ib;
+ return ix;
+}
+
+/**
+ * Add the start \& end boundaries \a is \& \a ie to bounds.
+ */
+void text_wrapper::AddTwinBoundaries(text_boundary const &is, text_boundary const &ie)
+{
+ unsigned const ns = AddBoundary(is);
+ unsigned const ne = AddBoundary(ie);
+ bounds[ns].start = true;
+ bounds[ns].other = ne;
+ bounds[ne].start = false;
+ bounds[ne].other = ns;
+}
+
+static int CmpBound(void const *a, void const *b) {
+ text_boundary const &ta = *reinterpret_cast<text_boundary const *>(a);
+ text_boundary const &tb = *reinterpret_cast<text_boundary const *>(b);
+ if ( ta.uni_pos < tb.uni_pos ) return -1;
+ if ( ta.uni_pos > tb.uni_pos ) return 1;
+ /* TODO: I'd guess that for a given uni_pos it would be better for the end boundary to precede the start boundary. */
+ if ( ta.start && !tb.start ) return -1;
+ if ( !ta.start && tb.start ) return 1;
+ return 0;
+}
+/**
+ * Sort this.bounds by b.uni_pos, updating the .other index values appropriately.
+ */
+void text_wrapper::SortBoundaries(void)
+{
+ /* effic: If this function (including descendents such as the qsort calll) ever takes
+ * non-negligible time, then we can fairly easily improve it by changing MakeBoundaries add in
+ * sorted order. It would just have to remember for itself the index of each start boundary
+ * for updating the .other fields appropriately.
+ *
+ * A simpler speedup is just to change qsort to std::sort, which can inline the comparison
+ * function.
+ */
+
+ /* The 'other' field needs to be updated after sorting by qsort, so we build the inverse
+ * permutation. */
+ for (unsigned i = 0; i < nbBound; i++) {
+ bounds[i].old_ix = i;
+ }
+ qsort(bounds, nbBound, sizeof(text_boundary), CmpBound);
+ unsigned *const old2new = g_new(unsigned, nbBound);
+ for (unsigned new_ix = 0; new_ix < nbBound; new_ix++) { // compute inverse permutation
+ old2new[bounds[new_ix].old_ix] = new_ix;
+ }
+ for (unsigned i = 0; i < nbBound; i++) { // update 'other'
+ if ( bounds[i].other < nbBound ) {
+ bounds[i].other = old2new[bounds[i].other];
+ }
+ }
+ g_free(old2new);
+}
+void text_wrapper::MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr)
+{
+ if ( pAttrs == NULL || nAttr <= 0 || uni32_length <= 0 ) return;
+ if ( nAttr > uni32_length + 1 ) nAttr = uni32_length + 1;
+ int last_c_st = -1;
+ int last_w_st = -1;
+ int last_s_st = -1;
+ int last_p_st = 0;
+ // reads the text and adds a pair of boundaries each time we encounter a stop
+ // last_* are used to keep track of the start of new text chunk
+ for (int i = 0; i <= nAttr; i++) {
+ text_boundary nbs;
+ text_boundary nbe;
+ nbs.uni_pos = i;
+ nbs.start = true;
+ nbe.uni_pos = i;
+ nbe.start = false;
+ // letters
+ if ( i == nAttr || pAttrs[i].is_cursor_position ) {
+ if ( last_c_st >= 0 ) {
+ nbs.type = nbe.type = bnd_char;
+ nbs.uni_pos = last_c_st;
+ nbe.uni_pos = i;
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_c_st = i;
+ }
+ // words
+ if ( i == nAttr || pAttrs[i].is_word_start ) {
+ if ( last_w_st >= 0 ) {
+ nbs.type = nbe.type = bnd_word;
+ nbs.uni_pos = last_w_st;
+ nbe.uni_pos = i;
+ nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 );
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_w_st = i;
+ }
+ if ( i < nAttr && pAttrs[i].is_word_end ) {
+ if ( last_w_st >= 0 ) {
+ nbs.type = nbe.type = bnd_word;
+ nbs.uni_pos = last_w_st;
+ nbe.uni_pos = i;
+ nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 );
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_w_st = i;
+ }
+ // sentences
+ if ( i == nAttr || pAttrs[i].is_sentence_boundary ) {
+ if ( last_s_st >= 0 ) {
+ nbs.type = nbe.type = bnd_sent;
+ nbs.uni_pos = last_s_st;
+ nbe.uni_pos = i;
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_s_st = i;
+ }
+ // paragraphs
+ if ( uni32_text[i] == '\n' || uni32_text[i] == '\r' || i == nAttr ) { // too simple to be true?
+ nbs.type = nbe.type = bnd_para;
+ nbs.uni_pos = last_p_st;
+ nbe.uni_pos = i + 1;
+ AddTwinBoundaries(nbs, nbe);
+ last_p_st = i + 1;
+ }
+ }
+}
+
+bool text_wrapper::IsBound(BoundaryType const bnd_type, int g_st, int &c_st)
+{
+ if ( c_st < 0 ) c_st = 0;
+ int scan_dir = 0;
+ while ( unsigned(c_st) < nbBound ) {
+ if ( bounds[c_st].uni_pos == g_st && bounds[c_st].type == bnd_type ) {
+ return true;
+ }
+ if ( bounds[c_st].uni_pos < g_st ) {
+ if ( scan_dir < 0 ) break;
+ c_st++;
+ scan_dir = 1;
+ } else if ( bounds[c_st].uni_pos > g_st ) {
+ if ( scan_dir > 0 ) break;
+ c_st--;
+ scan_dir = -1;
+ } else {
+ // good pos, wrong type
+ while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) {
+ c_st--;
+ }
+ if ( bounds[c_st].uni_pos < g_st ) c_st++;
+ while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) {
+ if ( bounds[c_st].type == bnd_type ) {
+ return true;
+ }
+ c_st++;
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+/* Unused. Retained only because I haven't asked cyreve (Richard Hughes) whether he intends ever
+ * to use it. You can probably safely remove it. */
+//bool text_wrapper::Contains(BoundaryType const bnd_type, int g_st, int g_en, int &c_st, int &c_en)
+//{
+// if ( c_st < 0 ) c_st = 0;
+// bool found = false;
+// int scan_dir = 0;
+// while ( unsigned(c_st) < nbBound ) {
+// if ( bounds[c_st].type == bnd_type ) {
+// if ( bounds[c_st].start ) {
+// c_en = bounds[c_st].other;
+// } else {
+// }
+// }
+// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) {
+// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) {
+// // character found
+// found = true;
+// break;
+// }
+// }
+// if ( bounds[c_st].uni_pos < g_st ) {
+// if ( scan_dir < 0 ) break;
+// c_st++;
+// scan_dir = 1;
+// } else if ( bounds[c_st].uni_pos > g_st ) {
+// if ( scan_dir > 0 ) break;
+// c_st--;
+// scan_dir = -1;
+// } else {
+// // good pos, wrong type
+// while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) {
+// c_st--;
+// }
+// if ( bounds[c_st].uni_pos < g_st ) c_st++;
+// while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) {
+// if ( bounds[c_st].type == bnd_type ) {
+// if ( bounds[c_st].start ) {
+// c_en = bounds[c_st].other;
+// } else {
+// }
+// }
+// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) {
+// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) {
+// // character found
+// return true;
+// }
+// }
+// c_st++;
+// }
+//
+// break;
+// }
+// }
+// return found;
+//}
+
+void text_wrapper::MeasureBoxes(void)
+{
+ font_factory *f_src = font_factory::Default();
+ for (int i = 0; i < nbBox; i++) {
+ boxes[i].ascent = 0;
+ boxes[i].descent = 0;
+ boxes[i].leading = 0;
+ boxes[i].width = 0;
+
+ PangoFont *curPF = glyph_text[boxes[i].g_st].font;
+ if ( curPF ) {
+ PangoFontDescription *pfd = pango_font_describe(curPF);
+ font_instance *curF = f_src->Face(pfd);
+ if ( curF ) {
+ curF->FontMetrics(boxes[i].ascent, boxes[i].descent, boxes[i].leading);
+ curF->Unref();
+ }
+ pango_font_description_free(pfd);
+ boxes[i].width = glyph_text[boxes[i].g_en].x - glyph_text[boxes[i].g_st].x;
+ }
+ }
+}
+
+
+void text_wrapper::KernXForLastAddition(double *i_kern_x, int i_len, double scale)
+{
+ if ( i_kern_x == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_x == NULL ) {
+ kern_x = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ if ( i_len > last_len ) i_len = last_len;
+ for (int i = 0; i < i_len; i++) kern_x[last_addition + i] = i_kern_x[i] * scale;
+}
+
+void text_wrapper::KernYForLastAddition(double *i_kern_y, int i_len, double scale)
+{
+ if ( i_kern_y == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_y == NULL ) {
+ kern_y = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ if ( i_len > last_len ) i_len = last_len;
+ for (int i = 0; i < i_len; i++) kern_y[last_addition + i] = i_kern_y[i] * scale;
+}
+
+void text_wrapper::KernXForLastAddition(GList *i_kern_x, double scale)
+{
+ if ( i_kern_x == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_x == NULL ) {
+ kern_x = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ GList *l = i_kern_x;
+ for (int i = 0; i < last_len && l && l->data; i++, l = l->next) {
+ kern_x[last_addition + i] = ((SVGLength *) l->data)->computed * scale;
+ }
+}
+
+void text_wrapper::KernYForLastAddition(GList *i_kern_y, double scale)
+{
+ if ( i_kern_y == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_y == NULL ) {
+ kern_y = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ GList *l = i_kern_y;
+ for (int i = 0; i < last_len && l && l->data; i++, l = l->next) {
+ kern_y[last_addition + i] = ((SVGLength *) l->data)->computed * scale;
+ }
+}
+
+
+void text_wrapper::AddDxDy(void)
+{
+ if ( glyph_length <= 0 ) return;
+ if ( kern_x ) {
+ double sum = 0;
+ int l_pos = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int n_pos = glyph_text[i].uni_st;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j];
+ }
+ l_pos = n_pos;
+
+ glyph_text[i].x += sum;
+ }
+ {
+ int n_pos = uni32_length;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j];
+ }
+ l_pos = n_pos;
+ glyph_text[glyph_length].x += sum;
+ }
+ }
+ if ( kern_y ) {
+ double sum = 0;
+ int l_pos = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int n_pos = glyph_text[i].uni_st;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j];
+ }
+ l_pos = n_pos;
+
+ glyph_text[i].y += sum;
+ }
+ {
+ int n_pos = uni32_length;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j];
+ }
+ l_pos = n_pos;
+ glyph_text[glyph_length].y += sum;
+ }
+ }
+}
+
+
+/*
+ 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/libnrtype/TextWrapper.h b/src/libnrtype/TextWrapper.h
new file mode 100644
index 000000000..188f04ee1
--- /dev/null
+++ b/src/libnrtype/TextWrapper.h
@@ -0,0 +1,139 @@
+/*
+ * TextWrapper.h
+ * testICU
+ *
+ */
+
+#ifndef my_text_wrapper
+#define my_text_wrapper
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pango/pango.h>
+
+#include <libnrtype/nrtype-forward.h>
+#include "libnrtype/boundary-type.h"
+
+// miscanellous but useful data for a given text: chunking into logical pieces
+// pieces include sentence/word, needed for example for the word-spacing property,
+// and more important stuff like letter (ie visual letters)
+
+struct text_boundary;
+struct one_glyph;
+struct one_box;
+struct one_para;
+
+class text_wrapper {
+public:
+ char *utf8_text; // source text
+ gunichar *uni32_text; // ucs4 text computed from utf8_text
+ one_glyph *glyph_text; // glyph string computed for uni32_text
+
+ // maps between the 2
+ // These should most definitely be size_t, not int.
+ // I am quite sure (but not bored enough to actually test it
+ // on a 500MHz machine with 256MB RAM ) that this will crash
+ // for text longer than 2GB on architectures where
+ // sizeof(size_t) != sizeof(int)
+ int utf8_length; // utf8_text length
+ int uni32_length; // uni32_text length
+ int glyph_length; /**< Number of glyph in the glyph_text array.
+ * The size of the array is (glyph_length+1) in fact; the last glyph is kind of a '0' char. */
+ int *uni32_codepoint; // uni32_codepoint[i] is the index in uni32_text corresponding to utf8_text[i]
+ int *utf8_codepoint; // utf8_codepoint[i] is the index in utf8_text of the beginning of uni32_text[i]
+
+ // layout
+ font_instance *default_font; // font set as the default font (would need at least one alternate per language)
+ PangoLayout *pLayout; // private structure
+
+ // kerning additions
+ int last_addition; // index in uni32_text of the beginning of the text added by the last AppendUTF8 call
+ double *kern_x; // dx[i] is the dx for the ith unicode char
+ double *kern_y;
+
+ // boundaries, in an array
+ unsigned nbBound, maxBound;
+ text_boundary *bounds;
+
+ // text organization
+ int nbBox, maxBox;
+ one_box *boxes;
+ int nbPara, maxPara;
+ one_para *paras;
+
+ text_wrapper(void);
+ ~text_wrapper(void);
+
+ // filling the structure with input data
+ void SetDefaultFont(font_instance *iFont);
+
+ /**
+ * Append the specified text to utf8_text and uni32_codepoint.
+ *
+ * Note: Despite the name, the current implementation is primarily suited for a single
+ * call to set the text, rather than repeated calls to AppendUTF8: the implementation is
+ * Omega(n) in the new total length of the string, rather than just in the length of the
+ * text being appended. This can probably be addressed fairly easily (see comments in
+ * code) if this is an issue for new callers.
+ *
+ * \pre text is valid UTF-8, or null.
+ * Formally: text==NULL || g_utf8_validate(text, len, NULL).
+ *
+ * \param len Our sole existing caller (widgets/font_selector.cpp) uses len=-1. N.B. The current
+ * implementation may be buggy for non-negative len, especially for len==0.
+ */
+ void AppendUTF8(char const *text, int len);
+
+ // adds dx or dy for the text added by the last AppendUTF8() call
+ void KernXForLastAddition(double *i_kern_x, int i_len, double scale = 1.0);
+ void KernYForLastAddition(double *i_kern_y, int i_len, double scale = 1.0);
+ void KernXForLastAddition(GList *i_kern_x, double scale = 1.0);
+ void KernYForLastAddition(GList *i_kern_y, double scale = 1.0);
+ // compute the layout and stuff
+ void DoLayout(void);
+ // semi-private: computes boundaries in the input text
+ void ChunkText(void);
+ // utility function to move to the next element
+ bool NextChar(int &st, int &en) const;
+ bool NextWord(int &st, int &en) const;
+ bool NextPara(int &st, int &en) const;
+
+ // post-processing after the initial layout
+ // for the xml-space property: merges consecutive whitespace, and eats leading whitespace in the text
+ void MergeWhiteSpace(void);
+ // makes vertical 'x' and 'y' fields in the glyph_text based on the computed positions
+ void MakeVertical(void);
+ // as the names says...
+ void AddLetterSpacing(double dx, double dy, int g_st = -1, int g_en = -1);
+ // adds the kerning specified by the KernXForLastAddition call to the layout
+ void AddDxDy(void);
+
+ // boundary handling
+private:
+ unsigned AddBoundary(text_boundary const &ib);
+public:
+ void AddTwinBoundaries(text_boundary const &is, text_boundary const &ie);
+ void SortBoundaries(void);
+ void MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr);
+ //bool Contains(BoundaryType type, int g_st, int g_en, int &c_st, int &c_en);
+ bool IsBound(BoundaryType type, int g_st, int &c_st);
+
+ void MeasureBoxes(void);
+ int NbLetter(int g_st, int g_en);
+};
+
+#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/libnrtype/boundary-type.h b/src/libnrtype/boundary-type.h
new file mode 100644
index 000000000..7f8ecea90
--- /dev/null
+++ b/src/libnrtype/boundary-type.h
@@ -0,0 +1,31 @@
+#ifndef LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE
+#define LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE
+
+/** \file Definition of the BoundaryType enum. */
+
+/**
+ * The different kinds of semantic boundaries in text; or rather,
+ * the different things that may be delimited by a text_boundary.
+ */
+enum BoundaryType {
+ bnd_none = 0,
+ bnd_char,
+ bnd_word,
+ bnd_sent, /**< Sentence. Not currently used, and pango (1.8) does a bad job of determining
+ * sentence boundaries anyway. */
+ bnd_para
+};
+
+
+#endif /* !LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/font-glyph.h b/src/libnrtype/font-glyph.h
new file mode 100644
index 000000000..c79888c12
--- /dev/null
+++ b/src/libnrtype/font-glyph.h
@@ -0,0 +1,29 @@
+#ifndef SEEN_LIBNRTYPE_FONT_GLYPH_H
+#define SEEN_LIBNRTYPE_FONT_GLYPH_H
+
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-forward.h>
+
+// the info for a glyph in a font. it's totally resolution- and fontsize-independent
+struct font_glyph {
+ double h_advance, h_width; // width != advance because of kerning adjustements
+ double v_advance, v_width;
+ double bbox[4]; // bbox of the path (and the artbpath), not the bbox of the glyph
+ // as the fonts sometimes contain
+ Path* outline; // outline as a livarot Path
+ void* artbpath; // outline as a artbpath, for text->curve stuff (should be unified with livarot)
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_GLYPH_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/libnrtype/font-instance.h b/src/libnrtype/font-instance.h
new file mode 100644
index 000000000..5d57ff549
--- /dev/null
+++ b/src/libnrtype/font-instance.h
@@ -0,0 +1,121 @@
+#ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H
+#define SEEN_LIBNRTYPE_FONT_INSTANCE_H
+
+#include <ext/hash_map>
+#include <pango/pango-types.h>
+#include <pango/pango-font.h>
+#include <require-config.h>
+#include "FontFactory.h"
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <libnrtype/font-style.h>
+#include <livarot/livarot-forward.h>
+
+// the font_instance are the template of several raster_font; they provide metrics and outlines
+// that are drawn by the raster_font, so the raster_font needs info relative to the way the
+// font need to be drawn. note that fontsize is a scale factor in the transform matrix
+// of the style
+// the various raster_font in use at a given time are held in a hash_map whose indices are the
+// styles, hence the 2 following 'classes'
+struct font_style_hash : public std::unary_function<font_style, size_t> {
+ size_t operator()(font_style const &x) const;
+};
+
+struct font_style_equal : public std::binary_function<font_style, font_style, bool> {
+ bool operator()(font_style const &a, font_style const &b);
+};
+
+class font_instance {
+public:
+ // hashmap to get the raster_font for a given style
+ __gnu_cxx::hash_map<font_style, raster_font*, font_style_hash, font_style_equal> loadedStyles;
+ // the real source of the font
+ PangoFont* pFont;
+ // depending on the rendering backend, different temporary data
+
+ // that's the font's fingerprint; this particular PangoFontDescription gives the entry at which this font_instance
+ // resides in the font_factory loadedFaces hash_map
+ PangoFontDescription* descr;
+ // refcount
+ int refCount;
+ // font_factory owning this font_instance
+ font_factory* daddy;
+
+ // common glyph definitions for all the rasterfonts
+ __gnu_cxx::hash_map<int, int> id_to_no;
+ int nbGlyph, maxGlyph;
+ font_glyph* glyphs;
+
+ font_instance(void);
+ ~font_instance(void);
+
+ void Ref(void);
+ void Unref(void);
+
+ bool IsOutlineFont(void); // utility
+ void InstallFace(PangoFont* iFace); // utility; should reset the pFont field if loading failed
+ // in case the PangoFont is a bitmap font, for example. that way, the calling function
+ // will be able to check the validity of the font before installing it in loadedFaces
+ void InitTheFace();
+
+ int MapUnicodeChar(gunichar c); // calls the relevant unicode->glyph index function
+ void LoadGlyph(int glyph_id); // the main backend-dependent function
+ // loads the given glyph's info
+
+ // nota: all coordinates returned by these functions are on a [0..1] scale; you need to multiply
+ // by the fontsize to get the real sizes
+ Path* Outline(int glyph_id, Path *copyInto=NULL);
+ // queries the outline of the glyph (in livarot Path form), and copies it into copyInto instead
+ // of allocating a new Path if copyInto != NULL
+ void* ArtBPath(int glyph_id);
+ // returns the artbpath for this glyph. no refcounting needed, it's deallocated when the
+ // font_instance dies
+ double Advance(int glyph_id, bool vertical);
+ // nominal advance of the font.
+ bool FontMetrics(double &ascent, double &descent, double &leading);
+ bool FontSlope(double &run, double &rise);
+ // for generating slanted cursors for oblique fonts
+ NR::Rect BBox(int glyph_id);
+
+ // creates a rasterfont for the given style
+ raster_font* RasterFont(NR::Matrix const &trs, double stroke_width,
+ bool vertical = false, JoinType stroke_join = join_straight,
+ ButtType stroke_cap = butt_straight, float miter_limit = 4.0);
+ // the dashes array in iStyle is copied
+ raster_font* RasterFont(font_style const &iStyle);
+ // private use: tells the font_instance that the raster_font 'who' has died
+ void RemoveRasterFont(raster_font *who);
+
+ // attribute queries
+ unsigned Name(gchar *str, unsigned size);
+ unsigned PSName(gchar *str, unsigned size);
+ unsigned Family(gchar *str, unsigned size);
+ unsigned Attribute(gchar const *key, gchar *str, unsigned size);
+
+private:
+ void FreeTheFace();
+
+#ifdef USE_PANGO_WIN32
+ HFONT theFace;
+#else
+ FT_Face theFace;
+ // it's a pointer in fact; no worries to ref/unref it, pango does its magic
+ // as long as pFont is valid, theFace is too
+#endif
+
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_INSTANCE_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/libnrtype/font-style-to-pos.cpp b/src/libnrtype/font-style-to-pos.cpp
new file mode 100644
index 000000000..02f5ef37c
--- /dev/null
+++ b/src/libnrtype/font-style-to-pos.cpp
@@ -0,0 +1,120 @@
+#include "font-style-to-pos.h"
+#include <style.h>
+
+/* 'lighter' and 'darker' have to be resolved earlier */
+/**
+Given a style struct (CSS representation), sets the corresponding fields in a NRTypePosDef.
+ */
+NRTypePosDef
+font_style_to_pos (SPStyle const &style)
+{
+ NRTypePosDef ret;
+
+ switch (style.font_weight.computed) {
+ case SP_CSS_FONT_WEIGHT_100:
+ ret.weight = NR_POS_WEIGHT_CSS100;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_200:
+ ret.weight = NR_POS_WEIGHT_CSS200;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_300:
+ ret.weight = NR_POS_WEIGHT_CSS300;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_400:
+ case SP_CSS_FONT_WEIGHT_NORMAL:
+ ret.weight = NR_POS_WEIGHT_CSS400;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_500:
+ ret.weight = NR_POS_WEIGHT_CSS500;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_600:
+ ret.weight = NR_POS_WEIGHT_CSS600;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_700:
+ case SP_CSS_FONT_WEIGHT_BOLD:
+ ret.weight = NR_POS_WEIGHT_CSS700;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_800:
+ ret.weight = NR_POS_WEIGHT_CSS800;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_900:
+ ret.weight = NR_POS_WEIGHT_CSS900;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_LIGHTER:
+ case SP_CSS_FONT_WEIGHT_BOLDER:
+ default:
+ g_warning("Unrecognized font_weight.computed value");
+ ret.weight = NR_POS_WEIGHT_NORMAL;
+ break;
+ }
+
+ switch (style.font_style.computed) {
+ case SP_CSS_FONT_STYLE_ITALIC:
+ ret.italic = 1;
+ break;
+
+ case SP_CSS_FONT_STYLE_OBLIQUE:
+ ret.oblique = 1;
+ break;
+
+ case SP_CSS_FONT_STYLE_NORMAL:
+ default:
+ ret.italic = 0;
+ ret.oblique = 0;
+ break;
+ }
+
+ switch (style.font_stretch.computed) {
+ case SP_CSS_FONT_STRETCH_ULTRA_CONDENSED:
+ case SP_CSS_FONT_STRETCH_EXTRA_CONDENSED:
+ ret.stretch = NR_POS_STRETCH_EXTRA_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_CONDENSED:
+ case SP_CSS_FONT_STRETCH_NARROWER:
+ ret.stretch = NR_POS_STRETCH_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_CONDENSED:
+ ret.stretch = NR_POS_STRETCH_SEMI_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_EXPANDED:
+ ret.stretch = NR_POS_STRETCH_SEMI_EXPANDED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXPANDED:
+ case SP_CSS_FONT_STRETCH_WIDER:
+ ret.stretch = NR_POS_STRETCH_EXPANDED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXTRA_EXPANDED:
+ case SP_CSS_FONT_STRETCH_ULTRA_EXPANDED:
+ ret.stretch = NR_POS_STRETCH_EXTRA_EXPANDED;
+ break;
+
+ default:
+ ret.stretch = NR_POS_STRETCH_NORMAL;
+ break;
+ }
+
+ switch (style.font_variant.computed) {
+ case SP_CSS_FONT_VARIANT_SMALL_CAPS:
+ ret.variant = NR_POS_VARIANT_SMALLCAPS;
+ break;
+ default:
+ ret.variant = NR_POS_VARIANT_NORMAL;
+ break;
+ }
+
+ return ret;
+}
diff --git a/src/libnrtype/font-style-to-pos.h b/src/libnrtype/font-style-to-pos.h
new file mode 100644
index 000000000..f58fdda3f
--- /dev/null
+++ b/src/libnrtype/font-style-to-pos.h
@@ -0,0 +1,20 @@
+#ifndef __FONT_STYLE_TO_POS_H__
+#define __FONT_STYLE_TO_POS_H__
+
+#include <forward.h> /* SPStyle */
+#include <libnrtype/nr-type-pos-def.h>
+
+NRTypePosDef font_style_to_pos (SPStyle const &style);
+
+#endif /* __FONT_STYLE_TO_POS_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/libnrtype/font-style.h b/src/libnrtype/font-style.h
new file mode 100644
index 000000000..748d2020a
--- /dev/null
+++ b/src/libnrtype/font-style.h
@@ -0,0 +1,38 @@
+#ifndef SEEN_LIBNRTYPE_FONT_STYLE_H
+#define SEEN_LIBNRTYPE_FONT_STYLE_H
+
+#include <libnr/nr-matrix.h>
+#include <livarot/LivarotDefs.h>
+#include <livarot/livarot-forward.h>
+
+// structure that holds data describing how to render glyphs of a font
+
+// Different raster styles.
+struct font_style {
+ NR::Matrix transform; // the ctm. contains the font-size
+ bool vertical; // should be rendered vertically or not?
+ // good font support would take the glyph alternates for vertical mode, when present
+ double stroke_width; // if 0, the glyph is filled; otherwise stroked
+ JoinType stroke_join;
+ ButtType stroke_cap;
+ float stroke_miter_limit;
+ int nbDash;
+ double dash_offset;
+ double* dashes;
+
+ void Apply(Path *src, Shape *dst); // utility: applies the style to the path and stores the result in the shape
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_STYLE_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/libnrtype/libnrtype.def b/src/libnrtype/libnrtype.def
new file mode 100644
index 000000000..34051de2d
--- /dev/null
+++ b/src/libnrtype/libnrtype.def
@@ -0,0 +1,59 @@
+EXPORTS
+ nr_font_generic_free
+ nr_font_generic_glyph_advance_get
+ nr_font_generic_glyph_area_get
+ nr_font_generic_glyph_outline_get
+ nr_font_generic_glyph_outline_unref
+ nr_font_generic_new
+ nr_font_generic_rasterfont_free
+ nr_font_generic_rasterfont_new
+ nr_font_glyph_advance_get
+ nr_font_glyph_area_get
+ nr_font_glyph_outline_get
+ nr_font_glyph_outline_unref
+ nr_font_new_default
+ nr_font_ref
+ nr_font_unref
+ nr_name_list_release
+ nr_rasterfont_generic_free
+ nr_rasterfont_generic_glyph_advance_get
+ nr_rasterfont_generic_glyph_area_get
+ nr_rasterfont_generic_glyph_mask_render
+ nr_rasterfont_generic_new
+ nr_rasterfont_glyph_advance_get
+ nr_rasterfont_glyph_area_get
+ nr_rasterfont_glyph_mask_render
+ nr_rasterfont_new
+ nr_rasterfont_ref
+ nr_rasterfont_unref
+ nr_type_dict_insert
+ nr_type_dict_lookup
+ nr_type_dict_new
+ nr_type_directory_family_list_get
+ nr_type_directory_forget_face
+ nr_type_directory_lookup
+ nr_type_directory_lookup_fuzzy
+ nr_type_directory_style_list_get
+ nr_type_empty_build_def
+ nr_type_ft2_build_def
+; nr_type_gnome_build_def
+; nr_type_gnome_families_get
+; nr_type_gnome_typefaces_get
+ nr_type_w32_build_def
+ nr_type_w32_families_get
+ nr_type_w32_typefaces_get
+; nr_type_xft_build_def
+; nr_type_xft_families_get
+; nr_type_xft_typefaces_get
+ nr_typeface_attribute_get
+ nr_typeface_family_name_get
+ nr_typeface_ft2_get_type
+ nr_typeface_get_type
+ nr_typeface_glyph_advance_get
+ nr_typeface_glyph_outline_get
+ nr_typeface_glyph_outline_unref
+; nr_typeface_gnome_get_type
+ nr_typeface_lookup_default
+ nr_typeface_name_get
+ nr_typeface_new
+ nr_typeface_w32_get_type
diff --git a/src/libnrtype/makefile.in b/src/libnrtype/makefile.in
new file mode 100644
index 000000000..fbebd763d
--- /dev/null
+++ b/src/libnrtype/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) libnrtype/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) libnrtype/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/libnrtype/nr-type-pos-def.cpp b/src/libnrtype/nr-type-pos-def.cpp
new file mode 100644
index 000000000..fef04039e
--- /dev/null
+++ b/src/libnrtype/nr-type-pos-def.cpp
@@ -0,0 +1,268 @@
+#include "nr-type-pos-def.h"
+#include <glib.h>
+#include <string.h>
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent style (normal/italic/oblique).
+*/
+int
+parse_name_for_style (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint style;
+ // first dab at i18n... french and german
+ if (strstr (c, "italic") || strstr (c, "italique") || strstr (c, "kursiv")) {
+ style = NR_POS_STYLE_ITALIC;
+ } else if (strstr (c, "oblique")) {
+ style = NR_POS_STYLE_OBLIQUE;
+ } else {
+ style = NR_POS_STYLE_NORMAL;
+ }
+
+ g_free (c);
+ return style;
+}
+
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent weight.
+*/
+int
+parse_name_for_weight (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint weight;
+ if (strstr (c, "thin")) {
+ weight = NR_POS_WEIGHT_THIN;
+ } else if (strstr (c, "extra light")) {
+ weight = NR_POS_WEIGHT_EXTRA_LIGHT;
+ } else if (strstr (c, "ultra light")) {
+ weight = NR_POS_WEIGHT_ULTRA_LIGHT;
+ } else if (strstr (c, "light")) {
+ weight = NR_POS_WEIGHT_LIGHT;
+ } else if (strstr (c, "book")) {
+ weight = NR_POS_WEIGHT_BOOK;
+ } else if (strstr (c, "medium")) {
+ weight = NR_POS_WEIGHT_MEDIUM;
+ } else if (strstr (c, "semi bold")) {
+ weight = NR_POS_WEIGHT_SEMIBOLD;
+ } else if (strstr (c, "semibold")) {
+ weight = NR_POS_WEIGHT_SEMIBOLD;
+ } else if (strstr (c, "demi bold")) {
+ weight = NR_POS_WEIGHT_DEMIBOLD;
+ } else if (strstr (c, "demibold") || strstr (c, "demi")) {
+ weight = NR_POS_WEIGHT_DEMIBOLD;
+ } else if (strstr (c, "ultra bold")) {
+ weight = NR_POS_WEIGHT_ULTRA_BOLD;
+ } else if (strstr (c, "extra bold") || strstr (c, "xbold") || strstr (c, "xtrabold")) {
+ weight = NR_POS_WEIGHT_EXTRA_BOLD;
+ } else if (strstr (c, "black") || strstr (c, "heavy")) {
+ weight = NR_POS_WEIGHT_BLACK;
+ } else if (strstr (c, "bold")) {
+ /* Must come after the checks for `blah bold'. */
+ weight = NR_POS_WEIGHT_BOLD;
+ } else {
+ weight = NR_POS_WEIGHT_NORMAL;
+ }
+
+ g_free (c);
+ return weight;
+}
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent stretch.
+*/
+int
+parse_name_for_stretch (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint stretch;
+ if (strstr (c, "ultra narrow") || strstr (c, "ultra condensed") || strstr (c, "extra condensed")) {
+ stretch = NR_POS_STRETCH_EXTRA_CONDENSED;
+ } else if (strstr (c, "ultra wide") || strstr (c, "ultra expanded") || strstr (c, "ultra extended") || strstr (c, "extra expanded")) {
+ stretch = NR_POS_STRETCH_EXTRA_EXPANDED;
+ } else if (strstr (c, "semi condensed") || strstr (c, "semicondensed")) {
+ stretch = NR_POS_STRETCH_SEMI_CONDENSED;
+ } else if (strstr (c, "semi extended") || strstr (c, "semiextended")) {
+ stretch = NR_POS_STRETCH_SEMI_EXPANDED;
+ } else if (strstr (c, "narrow") || strstr (c, "condensed")) {
+ stretch = NR_POS_STRETCH_CONDENSED;
+ } else if (strstr (c, "wide") || strstr (c, "expanded") || strstr (c, "extended")) {
+ stretch = NR_POS_STRETCH_EXPANDED;
+ } else {
+ stretch = NR_POS_STRETCH_NORMAL;
+ }
+
+ g_free (c);
+ return stretch;
+}
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent variant (normal/smallcaps).
+*/
+int
+parse_name_for_variant (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint variant;
+ if (strstr (c, "small caps") || strstr (c, "smallcaps") || strstr (c, "caps")) {
+ variant = NR_POS_VARIANT_SMALLCAPS;
+ } else {
+ variant = NR_POS_VARIANT_NORMAL;
+ }
+
+ g_free (c);
+ return variant;
+}
+
+/**
+ * Given a style constant, returns the CSS value for font-style.
+*/
+const char *
+style_to_css (int style)
+{
+ switch (style) {
+ case NR_POS_STYLE_NORMAL:
+ return "normal";
+ break;
+ case NR_POS_STYLE_ITALIC:
+ return "italic";
+ break;
+ case NR_POS_STYLE_OBLIQUE:
+ return "oblique";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Given a weight constant, returns the CSS value for font-weight.
+*/
+const char *
+weight_to_css (int weight)
+{
+ switch (weight) {
+ case NR_POS_WEIGHT_THIN:
+ return "100";
+ break;
+ case NR_POS_WEIGHT_EXTRA_LIGHT:
+ return "200";
+ break;
+ case NR_POS_WEIGHT_LIGHT:
+ return "300";
+ break;
+ case NR_POS_WEIGHT_BOOK:
+ return "normal";
+ break;
+ case NR_POS_WEIGHT_MEDIUM:
+ return "500";
+ break;
+ case NR_POS_WEIGHT_SEMIBOLD:
+ return "600";
+ break;
+ case NR_POS_WEIGHT_BOLD:
+ return "bold";
+ break;
+ case NR_POS_WEIGHT_EXTRA_BOLD:
+ return "800";
+ break;
+ case NR_POS_WEIGHT_BLACK:
+ return "900";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * Given a stretch constant, returns the CSS value for font-stretch.
+*/
+const char *
+stretch_to_css (int stretch)
+{
+ switch (stretch) {
+ case NR_POS_STRETCH_EXTRA_CONDENSED:
+ return "extra-condensed";
+ break;
+ case NR_POS_STRETCH_CONDENSED:
+ return "condensed";
+ break;
+ case NR_POS_STRETCH_SEMI_CONDENSED:
+ return "semi-condensed";
+ break;
+ case NR_POS_STRETCH_NORMAL:
+ return "normal";
+ break;
+ case NR_POS_STRETCH_SEMI_EXPANDED:
+ return "semi-expanded";
+ break;
+ case NR_POS_STRETCH_EXPANDED:
+ return "expanded";
+ break;
+ case NR_POS_STRETCH_EXTRA_EXPANDED:
+ return "extra-expanded";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * Given a variant constant, returns the CSS value for font-variant.
+*/
+const char *
+variant_to_css (int stretch)
+{
+ switch (stretch) {
+ case NR_POS_VARIANT_SMALLCAPS:
+ return "small-caps";
+ break;
+ case NR_POS_VARIANT_NORMAL:
+ return "normal";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Constructor for NRTypePostDef. Sets the italic, oblique, weight,
+ * stretch, and variant.
+ */
+NRTypePosDef::NRTypePosDef(char const *description) {
+ // we cannot use strcasestr, it's linux only... so we must lowercase the string first
+ g_assert ( description != NULL );
+ gchar *c = g_ascii_strdown (description, -1);
+
+ /* copied from nr-type-directory.cpp:nr_type_calculate_position. */
+
+ italic = (strstr (c, "italic") != NULL);
+ oblique = (strstr (c, "oblique") != NULL);
+
+ weight = parse_name_for_weight (c);
+
+ stretch = parse_name_for_stretch (c);
+
+ variant = parse_name_for_variant (c);
+
+ g_free (c);
+}
diff --git a/src/libnrtype/nr-type-pos-def.h b/src/libnrtype/nr-type-pos-def.h
new file mode 100644
index 000000000..ab9f5b45c
--- /dev/null
+++ b/src/libnrtype/nr-type-pos-def.h
@@ -0,0 +1,102 @@
+#ifndef __NR_TYPE_POS_DEF_H__
+#define __NR_TYPE_POS_DEF_H__
+
+#define NR_POS_STYLE_NORMAL 0
+#define NR_POS_STYLE_ITALIC 1
+#define NR_POS_STYLE_OBLIQUE 2
+
+#define NR_POS_WEIGHT_THIN 32
+#define NR_POS_WEIGHT_EXTRA_LIGHT 64
+#define NR_POS_WEIGHT_ULTRA_LIGHT 64
+#define NR_POS_WEIGHT_LIGHT 96
+#define NR_POS_WEIGHT_BOOK 128
+#define NR_POS_WEIGHT_NORMAL 128
+#define NR_POS_WEIGHT_MEDIUM 144
+#define NR_POS_WEIGHT_SEMIBOLD 160
+#define NR_POS_WEIGHT_DEMIBOLD 160
+#define NR_POS_WEIGHT_BOLD 192
+#define NR_POS_WEIGHT_ULTRA_BOLD 224
+#define NR_POS_WEIGHT_EXTRA_BOLD 224
+#define NR_POS_WEIGHT_BLACK 255
+
+#define NR_POS_STRETCH_ULTRA_CONDENSED 48
+#define NR_POS_STRETCH_EXTRA_CONDENSED 48
+#define NR_POS_STRETCH_CONDENSED 88
+#define NR_POS_STRETCH_SEMI_CONDENSED 108
+#define NR_POS_STRETCH_NORMAL 128
+#define NR_POS_STRETCH_SEMI_EXPANDED 148
+#define NR_POS_STRETCH_EXPANDED 168
+#define NR_POS_STRETCH_EXTRA_EXPANDED 228
+#define NR_POS_STRETCH_ULTRA_EXPANDED 228
+
+// This is an enumerate, rather than on/off property,
+// for I sincerely hope the vocabulary of this property will be
+// extended by the W3C in the future to allow for more fancy fonts
+#define NR_POS_VARIANT_NORMAL 0
+#define NR_POS_VARIANT_SMALLCAPS 1
+
+/* Mapping from CSS weight numbers.
+
+ for i in `seq 9`; do
+ if [ $i -le 4 ]; then w=$((32 * $i));
+ elif [ $i = 5 ]; then w=144;
+ elif [ $i -lt 9 ]; then w=$((32 * $(($i - 1))));
+ else w=255;
+ fi;
+ printf '#define NR_POS_WEIGHT_CSS%d00\t\t%3d\n' $i $w;
+ done
+
+ This calculation approximately matches the old to-and-from-text code,
+ I don't claim it to be reasonable. ("approximately": some of the old
+ code wrote strings like "semi" and "heavy" that weren't being parsed
+ at the other end, and it had CSS100 darker than CSS200.)
+ */
+#define NR_POS_WEIGHT_CSS100 32
+#define NR_POS_WEIGHT_CSS200 64
+#define NR_POS_WEIGHT_CSS300 96
+#define NR_POS_WEIGHT_CSS400 128
+#define NR_POS_WEIGHT_CSS500 144
+#define NR_POS_WEIGHT_CSS600 160
+#define NR_POS_WEIGHT_CSS700 192
+#define NR_POS_WEIGHT_CSS800 224
+#define NR_POS_WEIGHT_CSS900 255
+
+
+class NRTypePosDef {
+public:
+ unsigned int italic : 1;
+ unsigned int oblique : 1;
+ unsigned int weight : 8;
+ unsigned int stretch : 8;
+ unsigned int variant : 8;
+ /* These can probably be made sensible sizes rather than bitfields; for the moment we'll
+ keep the old definition. */
+
+public:
+ NRTypePosDef() :
+ italic(0),
+ oblique(0),
+ weight(NR_POS_WEIGHT_NORMAL),
+ stretch(NR_POS_STRETCH_NORMAL),
+ variant(NR_POS_VARIANT_NORMAL)
+ { }
+
+ NRTypePosDef(char const *description);
+
+ bool signature() {return this->italic +
+ this->oblique * 255 +
+ this->weight * 255 * 255 +
+ this->stretch * 255 * 255 * 255 +
+ this->variant * 255 * 255 * 255 * 255;};
+};
+
+int parse_name_for_style (char const *c);
+int parse_name_for_weight (char const *c);
+int parse_name_for_stretch (char const *c);
+int parse_name_for_variant (char const *c);
+const char *style_to_css (int style);
+const char *weight_to_css (int weight);
+const char *stretch_to_css (int stretch);
+const char *variant_to_css (int variant);
+
+#endif /* __NR_TYPE_POS_DEF_H__ */
diff --git a/src/libnrtype/nr-type-primitives.cpp b/src/libnrtype/nr-type-primitives.cpp
new file mode 100644
index 000000000..616134383
--- /dev/null
+++ b/src/libnrtype/nr-type-primitives.cpp
@@ -0,0 +1,167 @@
+#define __NR_TYPE_PRIMITIVES_C__
+
+/*
+ * Typeface and script library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+/* This should be enough for approximately 10000 fonts */
+#define NR_DICTSIZE 2777
+
+#include <stdlib.h>
+#include <string.h>
+#include <libnr/nr-macros.h>
+#include "nr-type-primitives.h"
+
+/**
+ * An entry in a list of key->value pairs
+ */
+struct NRTDEntry {
+ NRTDEntry *next;
+ const gchar *key;
+ void *val;
+};
+
+/**
+ * Type Dictionary, consisting of size number of key-value entries
+ */
+struct NRTypeDict {
+ unsigned int size;
+ NRTDEntry **entries;
+};
+
+static NRTDEntry *nr_td_entry_new (void);
+
+/**
+ * Calls the destructor for each item in list
+ */
+void
+nr_name_list_release (NRNameList *list)
+{
+ if (list->destructor) {
+ list->destructor (list);
+ }
+}
+
+void
+nr_style_list_release (NRStyleList *list)
+{
+ if (list->destructor) {
+ list->destructor (list);
+ }
+}
+
+/**
+ * Creates a new typeface dictionary of size NR_DICTSIZE
+ * and initalizes all the entries to NULL
+ */
+NRTypeDict *
+nr_type_dict_new (void)
+{
+ NRTypeDict *td;
+ int i;
+
+ td = nr_new (NRTypeDict, 1);
+
+ td->size = NR_DICTSIZE;
+ td->entries = nr_new (NRTDEntry *, td->size);
+ for (i = 0; i < NR_DICTSIZE; i++) {
+ td->entries[i] = NULL;
+ }
+
+ return td;
+}
+
+/**
+ * Hashes a string and returns the int
+ */
+static unsigned int
+nr_str_hash (const gchar *p)
+{
+ unsigned int h;
+
+ h = *p;
+
+ if (h != 0) {
+ for (p += 1; *p; p++) h = (h << 5) - h + *p;
+ }
+
+ return h;
+}
+
+/**
+ * Inserts a key/value into a typeface dictionary
+ */
+void
+nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val)
+{
+ if (key) {
+ NRTDEntry *tde;
+ unsigned int hval;
+
+ hval = nr_str_hash (key) % td->size;
+
+ for (tde = td->entries[hval]; tde; tde = tde->next) {
+ if (!strcmp (key, tde->key)) {
+ tde->val = val;
+ return;
+ }
+ }
+
+ tde = nr_td_entry_new ();
+ tde->next = td->entries[hval];
+ tde->key = key;
+ tde->val = val;
+ td->entries[hval] = tde;
+ }
+}
+
+/**
+ * Looks up the given key from the typeface dictionary
+ */
+void *
+nr_type_dict_lookup (NRTypeDict *td, const gchar *key)
+{
+ if (key) {
+ NRTDEntry *tde;
+ unsigned int hval;
+ hval = nr_str_hash (key) % td->size;
+ for (tde = td->entries[hval]; tde; tde = tde->next) {
+ if (!strcmp (key, tde->key)) return tde->val;
+ }
+ }
+
+ return NULL;
+}
+
+#define NR_TDE_BLOCK_SIZE 32
+
+static NRTDEntry *nr_tde_free_list;
+
+/**
+ * Creates a new TDEntry
+ */
+static NRTDEntry *
+nr_td_entry_new (void)
+{
+ NRTDEntry *tde;
+
+ if (!nr_tde_free_list) {
+ int i;
+ nr_tde_free_list = nr_new (NRTDEntry, NR_TDE_BLOCK_SIZE);
+ for (i = 0; i < (NR_TDE_BLOCK_SIZE - 1); i++) {
+ nr_tde_free_list[i].next = nr_tde_free_list + i + 1;
+ }
+ nr_tde_free_list[i].next = NULL;
+ }
+
+ tde = nr_tde_free_list;
+ nr_tde_free_list = tde->next;
+
+ return tde;
+}
+
diff --git a/src/libnrtype/nr-type-primitives.h b/src/libnrtype/nr-type-primitives.h
new file mode 100644
index 000000000..9bb181c4b
--- /dev/null
+++ b/src/libnrtype/nr-type-primitives.h
@@ -0,0 +1,50 @@
+#ifndef __NR_TYPE_PRIMITIVES_H__
+#define __NR_TYPE_PRIMITIVES_H__
+
+/*
+ * Typeface and script library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * g++ port: Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This code is in public domain
+ */
+
+#include <glib.h>
+
+struct NRNameList;
+struct NRStyleList;
+struct NRTypeDict;
+
+typedef void (* NRNameListDestructor) (NRNameList *list);
+typedef void (* NRStyleListDestructor) (NRStyleList *list);
+
+struct NRNameList {
+ guint length;
+ guchar **names;
+ guchar **families;
+ NRNameListDestructor destructor;
+};
+
+struct NRStyleRecord {
+ const char *name;
+ const char *descr;
+};
+
+struct NRStyleList {
+ guint length;
+ NRStyleRecord *records;
+ NRStyleListDestructor destructor;
+};
+
+void nr_name_list_release (NRNameList *list);
+void nr_style_list_release (NRStyleList *list);
+
+NRTypeDict *nr_type_dict_new (void);
+
+void nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val);
+
+void *nr_type_dict_lookup (NRTypeDict *td, const gchar *key);
+
+#endif
diff --git a/src/libnrtype/nrtype-forward.h b/src/libnrtype/nrtype-forward.h
new file mode 100644
index 000000000..f3344f2fd
--- /dev/null
+++ b/src/libnrtype/nrtype-forward.h
@@ -0,0 +1,23 @@
+#ifndef SEEN_LIBNRTYPE_NRTYPE_FORWARD_H
+#define SEEN_LIBNRTYPE_NRTYPE_FORWARD_H
+
+class font_factory;
+struct font_glyph;
+class font_instance;
+struct font_style;
+class raster_font;
+class raster_glyph;
+class raster_position;
+
+#endif /* !SEEN_LIBNRTYPE_NRTYPE_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/libnrtype/one-box.h b/src/libnrtype/one-box.h
new file mode 100644
index 000000000..1040e2be9
--- /dev/null
+++ b/src/libnrtype/one-box.h
@@ -0,0 +1,28 @@
+/** \file Definition of struct one_box. */
+
+#ifndef LIBNRTYPE_ONE_BOX_H_INKSCAPE
+#define LIBNRTYPE_ONE_BOX_H_INKSCAPE
+
+// text chunking 2, the comeback
+// this time for sp-typeset
+struct one_box {
+ int g_st, g_en; ///< First and last glyph of this word.
+ double ascent, descent, leading;
+ double width;
+ bool word_start, word_end;
+};
+
+
+#endif /* !LIBNRTYPE_ONE_BOX_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+
diff --git a/src/libnrtype/one-glyph.h b/src/libnrtype/one-glyph.h
new file mode 100644
index 000000000..667c7743b
--- /dev/null
+++ b/src/libnrtype/one-glyph.h
@@ -0,0 +1,46 @@
+/** \file Definition of struct one_glyph. */
+
+/*
+ * License: May be redistributed with or without modifications under the terms of the Gnu General
+ * Public License as published by the Free Software Foundation, version 2 or (at your option) any
+ * later version.
+ */
+
+#ifndef LIBNRTYPE_ONE_GLYPH_H_INKSCAPE
+#define LIBNRTYPE_ONE_GLYPH_H_INKSCAPE
+
+#include <pango/pango-types.h>
+
+/**
+ * Information for a single glyph.
+ *
+ * Pango converts the text into glyphs, but scatters the info for a given glyph; here is a
+ * structure holding what inkscape needs to know.
+ */
+struct one_glyph {
+ int gl; ///< glyph_id
+ double x, y; ///< glyph position in the layout (nominal sizes, in the [0..1] range).
+ bool char_start; /**< Whether this glyph is the beginning of a letter. (RTL is taken in
+ * account.) */
+ bool word_start; ///< Whether this glyph is the beginning of a word.
+ bool para_start; ///< Whether this glyph is the beginning of a paragraph (for indentation).
+ char uni_dir; ///< BiDi orientation of the run containing this glyph.
+ int uni_st, uni_en; /**< Start and end positions of the text corresponding to this glyph.
+ * You always have uni_st < uni_en. */
+ PangoFont *font; /**< Font this glyph uses. (For bidi text, you need several fonts.)
+ * When rendering glyphs, check if this font is the one you're using. */
+};
+
+
+#endif /* !LIBNRTYPE_ONE_GLYPH_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/one-para.h b/src/libnrtype/one-para.h
new file mode 100644
index 000000000..60e59531f
--- /dev/null
+++ b/src/libnrtype/one-para.h
@@ -0,0 +1,20 @@
+#ifndef LIBNRTYPE_ONE_PARA_H_INKSCAPE
+#define LIBNRTYPE_ONE_PARA_H_INKSCAPE
+
+struct one_para {
+ int b_st, b_en;
+};
+
+
+#endif /* !LIBNRTYPE_ONE_PARA_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/raster-glyph.h b/src/libnrtype/raster-glyph.h
new file mode 100644
index 000000000..e39cc4e41
--- /dev/null
+++ b/src/libnrtype/raster-glyph.h
@@ -0,0 +1,49 @@
+#ifndef SEEN_LIBNRTYPE_RASTER_GLYPH_H
+#define SEEN_LIBNRTYPE_RASTER_GLYPH_H
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-forward.h>
+
+// a little utility class that holds data to render a styled glyph
+// ie. it's like a polygon. its function is to wrap the subpixel positionning
+class raster_glyph {
+public:
+ // raster_font that created me
+ raster_font* daddy;
+ // the glyph i am (the style is in daddy)
+ int glyph_id;
+ // internal structure: the styled path, and the associated uncrossed polygon
+ // they could be removed after the raster_position have been computed
+ Path* outline; // transformed by the matrix in style (may be factorized, but is small)
+ Shape* polygon;
+ // subpixel positions
+ // nb_sub_pixel is set to 4 when the glyph is created (it's hardcoded)
+ int nb_sub_pixel;
+ raster_position* sub_pixel;
+
+ raster_glyph(void);
+ ~raster_glyph(void);
+
+ // utility
+ void SetSubPixelPositionning(int nb_pos);
+ void LoadSubPixelPosition(int no);
+
+ // the interesting function: blits the glyph onto over
+ // over should be a mask, ie a NRPixBlock with one 8bit plane
+ void Blit(NR::Point const &at, NRPixBlock &over); // alpha only
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_RASTER_GLYPH_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/libnrtype/raster-position.h b/src/libnrtype/raster-position.h
new file mode 100644
index 000000000..ef12b93ec
--- /dev/null
+++ b/src/libnrtype/raster-position.h
@@ -0,0 +1,46 @@
+#ifndef SEEN_LIBNRTYPE_RASTER_POSITION_H
+#define SEEN_LIBNRTYPE_RASTER_POSITION_H
+
+#include <vector>
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-forward.h>
+
+// one subpixel position
+// it's basically a set of trapezoids (=float_ligne_run) representing the black areas of the glyph
+// all trapezoids are in the same array, hence the run_on_line array to give the number of
+// trapezoids on each line
+// trapezoids store the x-positions as float, and are shifted to the x blit position
+// so it's "exact" in the x direction and subpixel in the y direction
+class raster_position {
+public:
+ int top, bottom; // baseline is y=0
+ // top is the first pixel, bottom is the last
+ int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i
+ int nbRun;
+ float_ligne_run* runs;
+
+public:
+ raster_position();
+ ~raster_position();
+
+ // stuff runs into the structure
+ void AppendRuns(std::vector<float_ligne_run> const &r, int y);
+ // blits the trapezoids.
+ void Blit(float ph, int pv, NRPixBlock &over);
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_RASTER_POSITION_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/libnrtype/text-boundary.h b/src/libnrtype/text-boundary.h
new file mode 100644
index 000000000..83a825b55
--- /dev/null
+++ b/src/libnrtype/text-boundary.h
@@ -0,0 +1,49 @@
+#ifndef TEXT_BOUNDARY_H_INKSCAPE
+#define TEXT_BOUNDARY_H_INKSCAPE
+
+/** \file Definition of text_boundary. */
+
+/*
+ * License: May be redistributed with or without modifications under the terms of the Gnu General
+ * Public License as published by the Free Software Foundation, version 2 or (at your option) any
+ * later version.
+ */
+
+#include "libnrtype/boundary-type.h"
+
+
+/**
+ * A character/word/paragraph boundary in the text, used by TextWrapper.
+ *
+ * (Boundaries are paired.)
+ */
+struct text_boundary {
+ /** Index of the boundary in the text: first char of the text chunk if 'start' boundary, char
+ * right after the boundary otherwise.
+ */
+ int uni_pos;
+ BoundaryType type; ///< Kind of boundary.
+ bool start; ///< Indicates whether this marks the beginning or end of a chunk.
+ unsigned other; ///< Index in bounds[] of the corresponding end/beginning boundary.
+ unsigned old_ix; ///< Temporary storage used solely SortBoundaries.
+ /// Data for this boundary; usually, one int is enough.
+ union {
+ int i;
+ double f;
+ void *p;
+ } data;
+};
+
+
+#endif /* !TEXT_BOUNDARY_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :