[perl-Cairo] Make it possible to modify and create Cairo::Paths



commit 861203aec1479564b6812278f2721ebf544a9399
Author: Torsten SchÃnfeld <kaffeetisch gmx de>
Date:   Sun Aug 7 19:58:54 2011 +0200

    Make it possible to modify and create Cairo::Paths
    
    Extend the tied interface of Cairo::Path to allow basic modifications.
    And when converting an SV to a cairo_path_t, accept bare array
    references.

 CairoPath.xs  |  442 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 lib/Cairo.pm  |   16 ++-
 t/CairoPath.t |   67 ++++++++-
 3 files changed, 464 insertions(+), 61 deletions(-)
---
diff --git a/CairoPath.xs b/CairoPath.xs
index 767564f..227a88a 100644
--- a/CairoPath.xs
+++ b/CairoPath.xs
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2005 by the cairo perl team (see the file README)
+ * Copyright (c) 2004-2011 by the cairo perl team (see the file README)
  *
  * Licensed under the LGPL, see LICENSE file for more information.
  *
@@ -7,6 +7,7 @@
  */
 
 #include <cairo-perl.h>
+#include <cairo-perl-private.h>
 
 #include "ppport.h"
 
@@ -22,29 +23,38 @@ cairo_perl_mg_find (SV *sv, int type)
 				return mg;
 		}
 	}
-	return 0;
+	return NULL;
 }
 
-SV *
-newSVCairoPath (cairo_path_t * path)
+static void *
+cairo_perl_mg_get (SV * sv)
+{
+	MAGIC * mg;
+	if (!cairo_perl_sv_is_ref (sv) ||
+	    !(mg = cairo_perl_mg_find (SvRV (sv), PERL_MAGIC_ext)))
+		return NULL;
+	return mg->mg_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static SV *
+create_tie (SV * sv, void * object, const char * package)
 {
-	AV * av;
 	SV * tie;
 	HV * stash;
 	MAGIC * mg;
 
-	av = newAV ();
-
 	/* Create a tied reference. */
-	tie = newRV_noinc ((SV *) av);
-	stash = gv_stashpv ("Cairo::Path", TRUE);
+	tie = newRV_noinc (sv);
+	stash = gv_stashpv (package, TRUE);
 	sv_bless (tie, stash);
-	sv_magic ((SV *) av, tie, PERL_MAGIC_tied, Nullch, 0);
+	sv_magic (sv, tie, PERL_MAGIC_tied, Nullch, 0);
 
 	/* Associate the array with the original path via magic. */
-	sv_magic ((SV *) av, 0, PERL_MAGIC_ext, (const char *) path, 0);
+	sv_magic (sv, 0, PERL_MAGIC_ext, (const char *) object, 0);
 
-	mg = mg_find ((SV *) av, PERL_MAGIC_ext);
+	mg = mg_find (sv, PERL_MAGIC_ext);
 
 	/* Mark the mg as belonging to us. */
 	mg->mg_private = MY_MAGIC_SIG;
@@ -52,21 +62,222 @@ newSVCairoPath (cairo_path_t * path)
 #if PERL_REVISION <= 5 && PERL_VERSION <= 6
 	/* perl 5.6.x doesn't actually set mg_ptr when namlen == 0, so do it
 	 * now. */
-	mg->mg_ptr = (char *) path;
+	mg->mg_ptr = (char *) object;
 #endif /* 5.6.x */
 
 	return tie;
 }
 
+static SV *
+create_tied_av (void * object, const char * package)
+{
+	return create_tie ((SV *) newAV (), object, package);
+}
+
+static SV *
+create_tied_hv (void * object, const char * package)
+{
+	return create_tie ((SV *) newHV (), object, package);
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define FETCH_POINT(i)						\
+	if ((svp = av_fetch (points, i, 0)) &&			\
+	    cairo_perl_sv_is_defined (*svp))			\
+	{							\
+		point = (AV *) SvRV (*svp);			\
+		if ((svp = av_fetch (point, 0, 0)))		\
+			data[i+1].point.x = SvNV (*svp);	\
+		if ((svp = av_fetch (point, 1, 0)))		\
+			data[i+1].point.y = SvNV (*svp);	\
+	}
+
+static void
+fill_data_from_array (cairo_path_data_t * data, cairo_path_data_type_t type, AV * points)
+{
+	SV ** svp;
+	AV * point;
+	switch (type) {
+	    case CAIRO_PATH_MOVE_TO:
+		data->header.type = CAIRO_PATH_MOVE_TO;
+		data->header.length = 2;
+		FETCH_POINT (0);
+		break;
+
+	    case CAIRO_PATH_LINE_TO:
+		data->header.type = CAIRO_PATH_LINE_TO;
+		data->header.length = 2;
+		FETCH_POINT (0);
+		break;
+
+	    case CAIRO_PATH_CURVE_TO:
+		data->header.type = CAIRO_PATH_CURVE_TO;
+		data->header.length = 4;
+		FETCH_POINT (0);
+		FETCH_POINT (1);
+		FETCH_POINT (2);
+		break;
+
+	    case CAIRO_PATH_CLOSE_PATH:
+		data->header.type = CAIRO_PATH_CLOSE_PATH;
+		data->header.length = 1;
+		break;
+	}
+}
+
+/* This uses cairo_perl_alloc_temp.  So the return value is only valid until
+ * the next FREETMPS occurs.  At the moment, the only cairo function that
+ * *takes* a cairo_path_t is cairo_append_path, and it acts on the path
+ * immediately and does not store it.  So that's fine. */
+static cairo_path_t *
+path_from_array (SV * sv)
+{
+	AV *av;
+	int i, num_data;
+	cairo_path_t *path;
+	cairo_path_data_t *data;
+
+	if (!cairo_perl_sv_is_array_ref (sv))
+		croak ("a Cairo::Path has to be an array reference");
+
+	av = (AV *) SvRV (sv);
+
+	num_data = 0;
+	for (i = 0; i <= av_len (av); i++) {
+		SV **svp;
+		HV *hv;
+
+		svp = av_fetch (av, i, 0);
+		if (!svp || !cairo_perl_sv_is_hash_ref (*svp))
+			croak ("a Cairo::Path has to contain hash references");
+		hv = (HV *) SvRV (*svp);
+
+		svp = hv_fetch (hv, "type", 4, 0);
+		if (!svp || !cairo_perl_sv_is_defined (*svp))
+			croak ("hash references inside a Cairo::Path must have a 'type' key");
+
+		switch (cairo_path_data_type_from_sv (*svp)) {
+		    case CAIRO_PATH_MOVE_TO:
+		    case CAIRO_PATH_LINE_TO:
+			num_data += 2;
+			break;
+		    case CAIRO_PATH_CURVE_TO:
+			num_data += 4;
+			break;
+		    case CAIRO_PATH_CLOSE_PATH:
+			num_data += 1;
+			break;
+		}
+	}
+
+	path = cairo_perl_alloc_temp (sizeof (cairo_path_t));
+	path->num_data = num_data;
+	path->data = cairo_perl_alloc_temp (path->num_data * sizeof (cairo_path_data_t));
+	path->status = CAIRO_STATUS_SUCCESS;
+
+	data = path->data;
+	for (i = 0; i <= av_len (av); i++) {
+		SV **svp;
+		HV *hv;
+		AV *points;
+
+		svp = av_fetch (av, i, 0);
+		hv = (HV *) SvRV (*svp);
+
+		svp = hv_fetch (hv, "points", 6, 0);
+		if (!svp || !cairo_perl_sv_is_array_ref (*svp))
+			croak ("hash references inside a Cairo::Path must "
+			       "contain a 'points' key which contains an array "
+			       "reference of points");
+		points = (AV *) SvRV (*svp);
+
+		svp = hv_fetch (hv, "type", 4, 0);
+		fill_data_from_array (data, cairo_path_data_type_from_sv (*svp), points);
+		data += data->header.length;
+	}
+
+	return path;
+}
+
+SV *
+newSVCairoPath (cairo_path_t * path)
+{
+	return create_tied_av (path, "Cairo::Path");
+}
+
 cairo_path_t *
 SvCairoPath (SV * sv)
 {
-	MAGIC * mg;
-	if (!sv || !SvROK (sv) || !(mg = cairo_perl_mg_find (SvRV (sv), PERL_MAGIC_ext)))
-		return NULL;
-	return (cairo_path_t *) mg->mg_ptr;
+	cairo_path_t * path;
+	path = cairo_perl_mg_get (sv);
+	if (!path) {
+		path = path_from_array (sv);
+	}
+	return path;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static SV *
+newSVCairoPathData (cairo_path_data_t * data)
+{
+	return create_tied_hv (data, "Cairo::Path::Data");
+}
+
+static cairo_path_data_t *
+SvCairoPathData (SV * sv)
+{
+	return cairo_perl_mg_get (sv);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static SV *
+newSVCairoPathPoints (cairo_path_data_t * data)
+{
+	return create_tied_av (data, "Cairo::Path::Points");
+}
+
+static cairo_path_data_t *
+SvCairoPathPoints (SV * sv)
+{
+	return cairo_perl_mg_get (sv);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static SV *
+newSVCairoPathPoint (cairo_path_data_t * data)
+{
+	return create_tied_av (data, "Cairo::Path::Point");
+}
+
+static cairo_path_data_t *
+SvCairoPathPoint (SV * sv)
+{
+	return cairo_perl_mg_get (sv);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static IV
+n_points (cairo_path_data_t * data)
+{
+	switch (data->header.type) {
+	    case CAIRO_PATH_MOVE_TO:
+	    case CAIRO_PATH_LINE_TO:
+		return 1;
+	    case CAIRO_PATH_CURVE_TO:
+		return 3;
+	    case CAIRO_PATH_CLOSE_PATH:
+		return 0;
+	}
+	return -1;
 }
 
+/* ------------------------------------------------------------------------- */
+
 MODULE = Cairo::Path	PACKAGE = Cairo::Path
 
 void DESTROY (SV * sv)
@@ -101,44 +312,169 @@ SV * FETCH (cairo_path_t * path, IV index)
 	for (i = 0; i < path->num_data; i += path->data[i].header.length) {
 		if (counter++ == index) {
 			cairo_path_data_t *data = &path->data[i];
-			HV *hash = newHV ();
-			AV *points = newAV (), *tmp;
-
-			switch (data->header.type) {
-			    case CAIRO_PATH_MOVE_TO:
-			    case CAIRO_PATH_LINE_TO:
-				tmp = newAV ();
-				av_store (tmp, 0, newSVnv (data[1].point.x));
-				av_store (tmp, 1, newSVnv (data[1].point.y));
-				av_store (points, 0, newRV_noinc ((SV *) tmp));
-				break;
-			    case CAIRO_PATH_CURVE_TO:
-				tmp = newAV ();
-				av_store (tmp, 0, newSVnv (data[1].point.x));
-				av_store (tmp, 1, newSVnv (data[1].point.y));
-				av_store (points, 0, newRV_noinc ((SV *) tmp));
-
-				tmp = newAV ();
-				av_store (tmp, 0, newSVnv (data[2].point.x));
-				av_store (tmp, 1, newSVnv (data[2].point.y));
-				av_store (points, 1, newRV_noinc ((SV *) tmp));
-
-				tmp = newAV ();
-				av_store (tmp, 0, newSVnv (data[3].point.x));
-				av_store (tmp, 1, newSVnv (data[3].point.y));
-				av_store (points, 2, newRV_noinc ((SV *) tmp));
-				break;
-			    case CAIRO_PATH_CLOSE_PATH:
-				break;
-			}
-
-			hv_store (hash, "type", 4, cairo_path_data_type_to_sv (data->header.type), 0);
-			hv_store (hash, "points", 6, newRV_noinc ((SV *) points), 0);
-
-			RETVAL = newRV_noinc ((SV *) hash);
-
+			RETVAL = newSVCairoPathData (data);
 			break;
 		}
 	}
     OUTPUT:
 	RETVAL
+
+# --------------------------------------------------------------------------- #
+
+MODULE = Cairo::Path	PACKAGE = Cairo::Path::Data
+
+SV * FETCH (SV * sv, const char * key)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathData (sv);
+	if (strEQ (key, "type")) {
+		RETVAL = cairo_path_data_type_to_sv (data->header.type);
+	} else if (strEQ (key, "points")) {
+		RETVAL = newSVCairoPathPoints (data);
+	} else {
+		croak ("Unknown key '%s' for Cairo::Path::Data", key);
+		RETVAL = NULL;
+	}
+    OUTPUT:
+	RETVAL
+
+SV * STORE (SV * sv, const char * key, SV * value)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathData (sv);
+	if (strEQ (key, "points")) {
+		RETVAL = newSVCairoPathPoints (data);
+		fill_data_from_array (data, data->header.type, (AV *) SvRV (value));
+	} else {
+		croak ("Unhandled key '%s' for Cairo::Path::Data; "
+		       "only changing 'points' is supported", key);
+		RETVAL = NULL;
+	}
+    OUTPUT:
+	RETVAL
+
+bool EXISTS (sv, const char * key)
+    CODE:
+	if (strEQ (key, "type")) {
+		RETVAL = TRUE;
+	} else if (strEQ (key, "points")) {
+		RETVAL = TRUE;
+	} else {
+		RETVAL = FALSE;
+	}
+    OUTPUT:
+	RETVAL
+
+const char * FIRSTKEY (sv)
+    CODE:
+	RETVAL = "type";
+    OUTPUT:
+	RETVAL
+
+const char * NEXTKEY (sv, const char * lastkey)
+    CODE:
+	if (strEQ (lastkey, "type")) {
+		RETVAL = "points";
+	} else {
+		RETVAL = NULL;
+	}
+    OUTPUT:
+	RETVAL
+
+# --------------------------------------------------------------------------- #
+
+MODULE = Cairo::Path	PACKAGE = Cairo::Path::Points
+
+IV FETCHSIZE (SV * sv)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathPoints (sv);
+	RETVAL = n_points (data);
+    OUTPUT:
+	RETVAL
+
+SV * FETCH (SV * sv, IV index)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathPoints (sv);
+	if (index >= 0 && index < n_points (data)) {
+		RETVAL = newSVCairoPathPoint (&data[index + 1]);
+	} else {
+		RETVAL = &PL_sv_undef;
+	}
+    OUTPUT:
+	RETVAL
+
+SV * STORE (SV * sv, IV index, SV * value)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathPoints (sv);
+	if (index >= 0 && index < n_points (data)) {
+		cairo_path_data_t * point;
+		AV * av;
+		SV ** svp;
+		point = &data[index + 1];
+		RETVAL = newSVCairoPathPoint (point);
+		av = (AV *) SvRV (value);
+		if ((svp = av_fetch (av, 0, 0)))
+			point->point.x = SvNV (*svp);
+		if ((svp = av_fetch (av, 1, 0)))
+			point->point.y = SvNV (*svp);
+	} else {
+		RETVAL = &PL_sv_undef;
+	}
+    OUTPUT:
+	RETVAL
+
+# --------------------------------------------------------------------------- #
+
+MODULE = Cairo::Path	PACKAGE = Cairo::Path::Point
+
+IV FETCHSIZE (sv)
+    CODE:
+	RETVAL = 2;
+    OUTPUT:
+	RETVAL
+
+SV * FETCH (SV * sv, IV index)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathPoint (sv);
+	switch (index) {
+	    case 0:
+		RETVAL = newSVnv (data->point.x);
+		break;
+	    case 1:
+		RETVAL = newSVnv (data->point.y);
+		break;
+	    default:
+		RETVAL = &PL_sv_undef;
+		break;
+	}
+    OUTPUT:
+	RETVAL
+
+SV * STORE (SV * sv, IV index, NV value)
+    PREINIT:
+	cairo_path_data_t * data;
+    CODE:
+	data = SvCairoPathPoint (sv);
+	switch (index) {
+	    case 0:
+		RETVAL = newSVnv (data->point.x = value);
+		break;
+	    case 1:
+		RETVAL = newSVnv (data->point.y = value);
+		break;
+	    default:
+		RETVAL = &PL_sv_undef;
+		break;
+	}
+    OUTPUT:
+	RETVAL
diff --git a/lib/Cairo.pm b/lib/Cairo.pm
index d83ab85..f6251f4 100644
--- a/lib/Cairo.pm
+++ b/lib/Cairo.pm
@@ -353,8 +353,8 @@ C<$cr-E<gt>restore> to restore to the saved state.
   ];
 
 I<Cairo::Path> is a data structure for holding a path. This data structure
-serves as the return value for C<$cr-E<gt>copy_path_data> and
-C<$cr-E<gt>copy_path_data_flat> as well the input value for
+serves as the return value for C<$cr-E<gt>copy_path> and
+C<$cr-E<gt>copy_path_flat> as well the input value for
 C<$cr-E<gt>append_path>.
 
 I<Cairo::Path> is represented as an array reference that contains path
@@ -394,6 +394,18 @@ The semantics and ordering of the coordinate values are consistent with
 C<$cr-E<gt>move_to>, C<$cr-E<gt>line_to>, C<$cr-E<gt>curve_to>, and
 C<$cr-E<gt>close_path>.
 
+Note that the paths returned by Cairo are implemented as tied array references
+which do B<not> support adding, removing or shuffling of path segments.  For
+these operations, you need to make a shallow copy first:
+
+  my @path_clone = @{$path};
+  # now you can alter @path_clone which ever way you want
+
+The points of a single path element can be changed directly, however, without
+the need for a shallow copy:
+
+  $path->[$i]{points} = [[3, 4], [5, 6], [7, 8]];
+
 =over
 
 =item $path = $cr->copy_path
diff --git a/t/CairoPath.t b/t/CairoPath.t
index de63a2d..e515172 100644
--- a/t/CairoPath.t
+++ b/t/CairoPath.t
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 #
-# Copyright (c) 2004-2005 by the cairo perl team (see the file README)
+# Copyright (c) 2004-2011 by the cairo perl team (see the file README)
 #
 # Licensed under the LGPL, see LICENSE file for more information.
 #
@@ -11,7 +11,7 @@ use strict;
 use warnings;
 use Cairo;
 
-use Test::More tests => 4;
+use Test::More tests => 6;
 
 use constant IMG_WIDTH => 256;
 use constant IMG_HEIGHT => 256;
@@ -25,9 +25,64 @@ $cr->line_to (3, 4);
 $cr->curve_to (5, 6, 7, 8, 9, 10);
 $cr->close_path;
 
+my $expected_path = [
+  { type => "move-to", points => [[1, 2]] },
+  { type => "line-to", points => [[3, 4]] },
+  { type => "curve-to", points => [[5, 6], [7, 8], [9, 10]] },
+  { type => "close-path", points => [] },
+  { type => "move-to", points => [[1, 2]] }
+];
+
 my $path = $cr->copy_path;
+is_deeply ($path, $expected_path);
+
+sub paths_agree {
+  my ($cr, $path, $expected_path) = @_;
+  $cr->new_path;
+  $cr->append_path ($path);
+  is_deeply ($cr->copy_path, $expected_path);
+}
+
+# Modifying single point values.
+foreach ($path, $expected_path) {
+  $_->[1]{points}[0][0] = 33;
+  $_->[1]{points}[0][1] = 44;
+
+  $_->[2]{points}[2][0] = 99;
+  $_->[2]{points}[2][1] = 1010;
+}
+paths_agree ($cr, $path, $expected_path);
 
-is_deeply ($path->[0], { type => "move-to", points => [[1, 2]] });
-is_deeply ($path->[1], { type => "line-to", points => [[3, 4]] });
-is_deeply ($path->[2], { type => "curve-to", points => [[5, 6], [7, 8], [9, 10]] });
-is_deeply ($path->[3], { type => "close-path", points => [] });
+# Modifying single points.
+foreach ($path, $expected_path) {
+  $_->[1]{points}[0] = [333, 444];
+  $_->[2]{points}[2] = [77, 88];
+}
+paths_agree ($cr, $path, $expected_path);
+
+# Replacing all points.
+foreach ($path, $expected_path) {
+  $_->[1]{points} = [[3333, 4444]];
+  $_->[2]{points} = [[55, 66], [77, 88], [99, 1010]];
+}
+paths_agree ($cr, $path, $expected_path);
+
+# Replacing and adding path segments.
+my @cloned_path = @{$path};
+foreach (\ cloned_path, $expected_path) {
+  $_->[1] = {
+    type => 'curve-to',
+    points => [[55, 66], [77, 88], [99, 1010]] };
+  $_->[2] = {
+    type => 'line-to',
+    points => [[3333, 4444]] };
+  splice @{$_}, 3, 0, {
+    type => 'line-to',
+    points => [[23, 42]] };
+}
+paths_agree ($cr, \ cloned_path, $expected_path);
+
+# Passing bare arrays into Cairo.
+$cr->new_path;
+$cr->append_path ($expected_path);
+is_deeply ($cr->copy_path, $expected_path);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]