diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/libnr | |
| download | inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip | |
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to '')
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 ¶, UnbrokenSpanPosition *start_span_pos, + std::vector<ChunkInfo> *chunk_info, LineHeight *line_height); + + static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶, + UnbrokenSpanPosition const &span_pos) + { + return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index]; + } + + bool _buildChunksInScanRun(ParagraphInfo const ¶, + 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 ¶, 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 ¶, 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 ¶, 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, + ¶->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 ¶, + 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 ¶, + 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(¶); + unsigned para_end_input_index = _buildSpansForPara(¶); + + 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 : |
