[perl-Cairo] Make it possible to modify and create Cairo::Paths
- From: Torsten SchÃnfeld <tsch src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [perl-Cairo] Make it possible to modify and create Cairo::Paths
- Date: Sun, 7 Aug 2011 18:02:16 +0000 (UTC)
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]