libart 2.3.16
authorkramm <kramm>
Tue, 22 Feb 2005 18:53:50 +0000 (18:53 +0000)
committerkramm <kramm>
Tue, 22 Feb 2005 18:53:50 +0000 (18:53 +0000)
41 files changed:
lib/art/Makefile.in [new file with mode: 0644]
lib/art/art_affine.c [new file with mode: 0644]
lib/art/art_alphagamma.c [new file with mode: 0644]
lib/art/art_bpath.c [new file with mode: 0644]
lib/art/art_gray_svp.c [new file with mode: 0644]
lib/art/art_misc.c [new file with mode: 0644]
lib/art/art_pixbuf.c [new file with mode: 0644]
lib/art/art_rect.c [new file with mode: 0644]
lib/art/art_rect_svp.c [new file with mode: 0644]
lib/art/art_rect_uta.c [new file with mode: 0644]
lib/art/art_render.c [new file with mode: 0644]
lib/art/art_render_gradient.c [new file with mode: 0644]
lib/art/art_render_mask.c [new file with mode: 0644]
lib/art/art_render_svp.c [new file with mode: 0644]
lib/art/art_rgb.c [new file with mode: 0644]
lib/art/art_rgb_a_affine.c [new file with mode: 0644]
lib/art/art_rgb_affine.c [new file with mode: 0644]
lib/art/art_rgb_affine_private.c [new file with mode: 0644]
lib/art/art_rgb_bitmap_affine.c [new file with mode: 0644]
lib/art/art_rgb_pixbuf_affine.c [new file with mode: 0644]
lib/art/art_rgb_rgba_affine.c [new file with mode: 0644]
lib/art/art_rgb_svp.c [new file with mode: 0644]
lib/art/art_rgba.c [new file with mode: 0644]
lib/art/art_svp.c [new file with mode: 0644]
lib/art/art_svp_intersect.c [new file with mode: 0644]
lib/art/art_svp_ops.c [new file with mode: 0644]
lib/art/art_svp_point.c [new file with mode: 0644]
lib/art/art_svp_render_aa.c [new file with mode: 0644]
lib/art/art_svp_vpath.c [new file with mode: 0644]
lib/art/art_svp_vpath_stroke.c [new file with mode: 0644]
lib/art/art_svp_wind.c [new file with mode: 0644]
lib/art/art_uta.c [new file with mode: 0644]
lib/art/art_uta_ops.c [new file with mode: 0644]
lib/art/art_uta_rect.c [new file with mode: 0644]
lib/art/art_uta_svp.c [new file with mode: 0644]
lib/art/art_uta_vpath.c [new file with mode: 0644]
lib/art/art_vpath.c [new file with mode: 0644]
lib/art/art_vpath_bpath.c [new file with mode: 0644]
lib/art/art_vpath_dash.c [new file with mode: 0644]
lib/art/art_vpath_svp.c [new file with mode: 0644]
lib/art/gen_art_config.c [new file with mode: 0644]

diff --git a/lib/art/Makefile.in b/lib/art/Makefile.in
new file mode 100644 (file)
index 0000000..460a408
--- /dev/null
@@ -0,0 +1,22 @@
+top_builddir = ../..
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+include ../../Makefile.common
+
+libart_objects = art_affine.$(O) art_alphagamma.$(O) art_bpath.$(O) art_gray_svp.$(O) art_misc.$(O) art_pixbuf.$(O) art_rect.$(O) art_rect_svp.$(O) art_rect_uta.$(O) art_render.$(O) art_render_gradient.$(O) art_render_mask.$(O) art_render_svp.$(O) art_rgb.$(O) art_rgb_a_affine.$(O) art_rgb_affine.$(O) art_rgb_affine_private.$(O) art_rgb_bitmap_affine.$(O) art_rgb_pixbuf_affine.$(O) art_rgb_rgba_affine.$(O) art_rgb_svp.$(O) art_rgba.$(O) art_svp.$(O) art_svp_intersect.$(O) art_svp_ops.$(O) art_svp_point.$(O) art_svp_render_aa.$(O) art_svp_vpath.$(O) art_svp_vpath_stroke.$(O) art_svp_wind.$(O) art_uta.$(O) art_uta_ops.$(O) art_uta_rect.$(O) art_uta_svp.$(O) art_uta_vpath.$(O) art_vpath.$(O) art_vpath_bpath.$(O) art_vpath_dash.$(O) art_vpath_svp.$(O)
+
+all: libart$(A)
+       
+libart$(A): $(libart_objects)
+       $(AR) r libart$(A) $(libart_objects)
+       $(RANLIB) libart$(A)
+
+%.$(O): %.c 
+       $(C) $< -o $@
+
+install:
+uninstall:
+clean: 
+       rm -f *.o *.obj *.lo *.a *.lib *.la gmon.out
+
+
diff --git a/lib/art/art_affine.c b/lib/art/art_affine.c
new file mode 100644 (file)
index 0000000..9f332a3
--- /dev/null
@@ -0,0 +1,458 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Simple manipulations with affine transformations */
+
+#include "config.h"
+#include "art_affine.h"
+#include "art_misc.h" /* for M_PI */
+
+#include <math.h>
+#include <stdio.h> /* for sprintf */
+#include <string.h> /* for strcpy */
+
+
+/* According to a strict interpretation of the libart structure, this
+   routine should go into its own module, art_point_affine.  However,
+   it's only two lines of code, and it can be argued that it is one of
+   the natural basic functions of an affine transformation.
+*/
+
+/**
+ * art_affine_point: Do an affine transformation of a point.
+ * @dst: Where the result point is stored.
+ * @src: The original point.
+ @ @affine: The affine transformation.
+ **/
+void
+art_affine_point (ArtPoint *dst, const ArtPoint *src,
+                 const double affine[6])
+{
+  double x, y;
+
+  x = src->x;
+  y = src->y;
+  dst->x = x * affine[0] + y * affine[2] + affine[4];
+  dst->y = x * affine[1] + y * affine[3] + affine[5];
+}
+
+/**
+ * art_affine_invert: Find the inverse of an affine transformation.
+ * @dst: Where the resulting affine is stored.
+ * @src: The original affine transformation.
+ *
+ * All non-degenerate affine transforms are invertible. If the original
+ * affine is degenerate or nearly so, expect numerical instability and
+ * very likely core dumps on Alpha and other fp-picky architectures.
+ * Otherwise, @dst multiplied with @src, or @src multiplied with @dst
+ * will be (to within roundoff error) the identity affine.
+ **/
+void
+art_affine_invert (double dst[6], const double src[6])
+{
+  double r_det;
+
+  r_det = 1.0 / (src[0] * src[3] - src[1] * src[2]);
+  dst[0] = src[3] * r_det;
+  dst[1] = -src[1] * r_det;
+  dst[2] = -src[2] * r_det;
+  dst[3] = src[0] * r_det;
+  dst[4] = -src[4] * dst[0] - src[5] * dst[2];
+  dst[5] = -src[4] * dst[1] - src[5] * dst[3];
+}
+
+/**
+ * art_affine_flip: Flip an affine transformation horizontally and/or vertically.
+ * @dst_affine: Where the resulting affine is stored.
+ * @src_affine: The original affine transformation.
+ * @horiz: Whether or not to flip horizontally.
+ * @vert: Whether or not to flip horizontally.
+ *
+ * Flips the affine transform. FALSE for both @horiz and @vert implements
+ * a simple copy operation. TRUE for both @horiz and @vert is a
+ * 180 degree rotation. It is ok for @src_affine and @dst_affine to
+ * be equal pointers.
+ **/
+void
+art_affine_flip (double dst_affine[6], const double src_affine[6], int horz, int vert)
+{
+  dst_affine[0] = horz ? - src_affine[0] : src_affine[0];
+  dst_affine[1] = horz ? - src_affine[1] : src_affine[1];
+  dst_affine[2] = vert ? - src_affine[2] : src_affine[2];
+  dst_affine[3] = vert ? - src_affine[3] : src_affine[3];
+  dst_affine[4] = horz ? - src_affine[4] : src_affine[4];
+  dst_affine[5] = vert ? - src_affine[5] : src_affine[5];
+}
+
+#define EPSILON 1e-6
+
+/* It's ridiculous I have to write this myself. This is hardcoded to
+   six digits of precision, which is good enough for PostScript.
+
+   The return value is the number of characters (i.e. strlen (str)).
+   It is no more than 12. */
+static int
+art_ftoa (char str[80], double x)
+{
+  char *p = str;
+  int i, j;
+
+  p = str;
+  if (fabs (x) < EPSILON / 2)
+    {
+      strcpy (str, "0");
+      return 1;
+    }
+  if (x < 0)
+    {
+      *p++ = '-';
+      x = -x;
+    }
+  if ((int)floor ((x + EPSILON / 2) < 1))
+    {
+      *p++ = '0';
+      *p++ = '.';
+      i = sprintf (p, "%06d", (int)floor ((x + EPSILON / 2) * 1e6));
+      while (i && p[i - 1] == '0')
+       i--;
+      if (i == 0)
+       i--;
+      p += i;
+    }
+  else if (x < 1e6)
+    {
+      i = sprintf (p, "%d", (int)floor (x + EPSILON / 2));
+      p += i;
+      if (i < 6)
+       {
+         int ix;
+
+         *p++ = '.';
+         x -= floor (x + EPSILON / 2);
+         for (j = i; j < 6; j++)
+           x *= 10;
+         ix = floor (x + 0.5);
+
+         for (j = 0; j < i; j++)
+           ix *= 10;
+
+         /* A cheap hack, this routine can round wrong for fractions
+            near one. */
+         if (ix == 1000000)
+           ix = 999999;
+
+         sprintf (p, "%06d", ix);
+         i = 6 - i;
+         while (i && p[i - 1] == '0')
+           i--;
+         if (i == 0)
+           i--;
+         p += i;
+       }
+    }
+  else
+    p += sprintf (p, "%g", x);
+
+  *p = '\0';
+  return p - str;
+}
+
+
+
+#include <stdlib.h>
+/**
+ * art_affine_to_string: Convert affine transformation to concise PostScript string representation.
+ * @str: Where to store the resulting string.
+ * @src: The affine transform.
+ *
+ * Converts an affine transform into a bit of PostScript code that
+ * implements the transform. Special cases of scaling, rotation, and
+ * translation are detected, and the corresponding PostScript
+ * operators used (this greatly aids understanding the output
+ * generated). The identity transform is mapped to the null string.
+ **/
+void
+art_affine_to_string (char str[128], const double src[6])
+{
+  char tmp[80];
+  int i, ix;
+
+#if 0
+  for (i = 0; i < 1000; i++)
+    {
+      double d = rand () * .1 / RAND_MAX;
+      art_ftoa (tmp, d);
+      printf ("%g %f %s\n", d, d, tmp);
+    }
+#endif
+  if (fabs (src[4]) < EPSILON && fabs (src[5]) < EPSILON)
+    {
+      /* could be scale or rotate */
+      if (fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON)
+       {
+         /* scale */
+         if (fabs (src[0] - 1) < EPSILON && fabs (src[3] - 1) < EPSILON)
+           {
+             /* identity transform */
+             str[0] = '\0';
+             return;
+           }
+         else
+           {
+             ix = 0;
+             ix += art_ftoa (str + ix, src[0]);
+             str[ix++] = ' ';
+             ix += art_ftoa (str + ix, src[3]);
+             strcpy (str + ix, " scale");
+             return;
+           }
+       }
+      else
+       {
+         /* could be rotate */
+         if (fabs (src[0] - src[3]) < EPSILON &&
+             fabs (src[1] + src[2]) < EPSILON &&
+             fabs (src[0] * src[0] + src[1] * src[1] - 1) < 2 * EPSILON)
+           {
+             double theta;
+
+             theta = (180 / M_PI) * atan2 (src[1], src[0]);
+             art_ftoa (tmp, theta);
+             sprintf (str, "%s rotate", tmp);
+             return;
+           }
+       }
+    }
+  else
+    {
+      /* could be translate */
+      if (fabs (src[0] - 1) < EPSILON && fabs (src[1]) < EPSILON &&
+         fabs (src[2]) < EPSILON && fabs (src[3] - 1) < EPSILON)
+       {
+         ix = 0;
+         ix += art_ftoa (str + ix, src[4]);
+         str[ix++] = ' ';
+         ix += art_ftoa (str + ix, src[5]);
+         strcpy (str + ix, " translate");
+         return;
+       }
+    }
+
+  ix = 0;
+  str[ix++] = '[';
+  str[ix++] = ' ';
+  for (i = 0; i < 6; i++)
+    {
+      ix += art_ftoa (str + ix, src[i]);
+      str[ix++] = ' ';
+    }
+  strcpy (str + ix, "] concat");
+}
+
+/**
+ * art_affine_multiply: Multiply two affine transformation matrices.
+ * @dst: Where to store the result.
+ * @src1: The first affine transform to multiply.
+ * @src2: The second affine transform to multiply.
+ *
+ * Multiplies two affine transforms together, i.e. the resulting @dst
+ * is equivalent to doing first @src1 then @src2. Note that the
+ * PostScript concat operator multiplies on the left, i.e.  "M concat"
+ * is equivalent to "CTM = multiply (M, CTM)";
+ *
+ * It is safe to call this function with @dst equal to @src1 or @src2.
+ **/
+void
+art_affine_multiply (double dst[6], const double src1[6], const double src2[6])
+{
+  double d0, d1, d2, d3, d4, d5;
+
+  d0 = src1[0] * src2[0] + src1[1] * src2[2];
+  d1 = src1[0] * src2[1] + src1[1] * src2[3];
+  d2 = src1[2] * src2[0] + src1[3] * src2[2];
+  d3 = src1[2] * src2[1] + src1[3] * src2[3];
+  d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4];
+  d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5];
+  dst[0] = d0;
+  dst[1] = d1;
+  dst[2] = d2;
+  dst[3] = d3;
+  dst[4] = d4;
+  dst[5] = d5;
+}
+
+/**
+ * art_affine_identity: Set up the identity matrix.
+ * @dst: Where to store the resulting affine transform.
+ *
+ * Sets up an identity matrix.
+ **/
+void
+art_affine_identity (double dst[6])
+{
+  dst[0] = 1;
+  dst[1] = 0;
+  dst[2] = 0;
+  dst[3] = 1;
+  dst[4] = 0;
+  dst[5] = 0;
+}
+
+
+/**
+ * art_affine_scale: Set up a scaling matrix.
+ * @dst: Where to store the resulting affine transform.
+ * @sx: X scale factor.
+ * @sy: Y scale factor.
+ *
+ * Sets up a scaling matrix.
+ **/
+void
+art_affine_scale (double dst[6], double sx, double sy)
+{
+  dst[0] = sx;
+  dst[1] = 0;
+  dst[2] = 0;
+  dst[3] = sy;
+  dst[4] = 0;
+  dst[5] = 0;
+}
+
+/**
+ * art_affine_rotate: Set up a rotation affine transform.
+ * @dst: Where to store the resulting affine transform.
+ * @theta: Rotation angle in degrees.
+ *
+ * Sets up a rotation matrix. In the standard libart coordinate
+ * system, in which increasing y moves downward, this is a
+ * counterclockwise rotation. In the standard PostScript coordinate
+ * system, which is reversed in the y direction, it is a clockwise
+ * rotation.
+ **/
+void
+art_affine_rotate (double dst[6], double theta)
+{
+  double s, c;
+
+  s = sin (theta * M_PI / 180.0);
+  c = cos (theta * M_PI / 180.0);
+  dst[0] = c;
+  dst[1] = s;
+  dst[2] = -s;
+  dst[3] = c;
+  dst[4] = 0;
+  dst[5] = 0;
+}
+
+/**
+ * art_affine_shear: Set up a shearing matrix.
+ * @dst: Where to store the resulting affine transform.
+ * @theta: Shear angle in degrees.
+ *
+ * Sets up a shearing matrix. In the standard libart coordinate system
+ * and a small value for theta, || becomes \\. Horizontal lines remain
+ * unchanged.
+ **/
+void
+art_affine_shear (double dst[6], double theta)
+{
+  double t;
+
+  t = tan (theta * M_PI / 180.0);
+  dst[0] = 1;
+  dst[1] = 0;
+  dst[2] = t;
+  dst[3] = 1;
+  dst[4] = 0;
+  dst[5] = 0;
+}
+
+/**
+ * art_affine_translate: Set up a translation matrix.
+ * @dst: Where to store the resulting affine transform.
+ * @tx: X translation amount.
+ * @tx: Y translation amount.
+ *
+ * Sets up a translation matrix.
+ **/
+void
+art_affine_translate (double dst[6], double tx, double ty)
+{
+  dst[0] = 1;
+  dst[1] = 0;
+  dst[2] = 0;
+  dst[3] = 1;
+  dst[4] = tx;
+  dst[5] = ty;
+}
+
+/**
+ * art_affine_expansion: Find the affine's expansion factor.
+ * @src: The affine transformation.
+ *
+ * Finds the expansion factor, i.e. the square root of the factor
+ * by which the affine transform affects area. In an affine transform
+ * composed of scaling, rotation, shearing, and translation, returns
+ * the amount of scaling.
+ *
+ * Return value: the expansion factor.
+ **/
+double
+art_affine_expansion (const double src[6])
+{
+  return sqrt (fabs (src[0] * src[3] - src[1] * src[2]));
+}
+
+/**
+ * art_affine_rectilinear: Determine whether the affine transformation is rectilinear.
+ * @src: The original affine transformation.
+ *
+ * Determines whether @src is rectilinear, i.e.  grid-aligned
+ * rectangles are transformed to other grid-aligned rectangles.  The
+ * implementation has epsilon-tolerance for roundoff errors.
+ *
+ * Return value: TRUE if @src is rectilinear.
+ **/
+int
+art_affine_rectilinear (const double src[6])
+{
+  return ((fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON) ||
+         (fabs (src[0]) < EPSILON && fabs (src[3]) < EPSILON));
+}
+
+/**
+ * art_affine_equal: Determine whether two affine transformations are equal.
+ * @matrix1: An affine transformation.
+ * @matrix2: Another affine transformation.
+ *
+ * Determines whether @matrix1 and @matrix2 are equal, with
+ * epsilon-tolerance for roundoff errors.
+ *
+ * Return value: TRUE if @matrix1 and @matrix2 are equal.
+ **/
+int
+art_affine_equal (double matrix1[6], double matrix2[6])
+{
+  return (fabs (matrix1[0] - matrix2[0]) < EPSILON &&
+         fabs (matrix1[1] - matrix2[1]) < EPSILON &&
+         fabs (matrix1[2] - matrix2[2]) < EPSILON &&
+         fabs (matrix1[3] - matrix2[3]) < EPSILON &&
+         fabs (matrix1[4] - matrix2[4]) < EPSILON &&
+         fabs (matrix1[5] - matrix2[5]) < EPSILON);
+}
diff --git a/lib/art/art_alphagamma.c b/lib/art/art_alphagamma.c
new file mode 100644 (file)
index 0000000..4651883
--- /dev/null
@@ -0,0 +1,85 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Some functions to build alphagamma tables */
+
+#include "config.h"
+#include "art_alphagamma.h"
+
+#include <math.h>
+
+/**
+ * art_alphagamma_new: Create a new #ArtAlphaGamma.
+ * @gamma: Gamma value.
+ *
+ * Create a new #ArtAlphaGamma for a specific value of @gamma. When
+ * correctly implemented (which is generally not the case in libart),
+ * alpha compositing with an alphagamma parameter is equivalent to
+ * applying the gamma transformation to source images, doing the alpha
+ * compositing (in linear intensity space), then applying the inverse
+ * gamma transformation, bringing it back to a gamma-adjusted
+ * intensity space.
+ *
+ * Return value: The newly created #ArtAlphaGamma.
+ **/
+ArtAlphaGamma *
+art_alphagamma_new (double gamma)
+{
+  int tablesize;
+  ArtAlphaGamma *alphagamma;
+  int i;
+  int *table;
+  art_u8 *invtable;
+  double s, r_gamma;
+
+  tablesize = ceil (gamma * 8);
+  if (tablesize < 10)
+    tablesize = 10;
+
+  alphagamma = (ArtAlphaGamma *)art_alloc (sizeof(ArtAlphaGamma) +
+                                          ((1 << tablesize) - 1) *
+                                          sizeof(art_u8));
+  alphagamma->gamma = gamma;
+  alphagamma->invtable_size = tablesize;
+
+  table = alphagamma->table;
+  for (i = 0; i < 256; i++)
+    table[i] = (int)floor (((1 << tablesize) - 1) *
+                          pow (i * (1.0 / 255), gamma) + 0.5);
+
+  invtable = alphagamma->invtable;
+  s = 1.0 / ((1 << tablesize) - 1);
+  r_gamma = 1.0 / gamma;
+  for (i = 0; i < 1 << tablesize; i++)
+    invtable[i] = (int)floor (255 * pow (i * s, r_gamma) + 0.5);
+
+  return alphagamma;
+}
+
+/**
+ * art_alphagamma_free: Free an #ArtAlphaGamma.
+ * @alphagamma: An #ArtAlphaGamma.
+ *
+ * Frees the #ArtAlphaGamma.
+ **/
+void
+art_alphagamma_free (ArtAlphaGamma *alphagamma)
+{
+  art_free (alphagamma);
+}
diff --git a/lib/art/art_bpath.c b/lib/art/art_bpath.c
new file mode 100644 (file)
index 0000000..a25acbf
--- /dev/null
@@ -0,0 +1,92 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Basic constructors and operations for bezier paths */
+
+#include "config.h"
+#include "art_bpath.h"
+
+#include <math.h>
+
+
+/**
+ * art_bpath_affine_transform: Affine transform an #ArtBpath.
+ * @src: The source #ArtBpath.
+ * @matrix: The affine transform.
+ *
+ * Affine transform the bezpath, returning a newly allocated #ArtBpath
+ * (allocated using art_alloc()).
+ *
+ * Result (x', y') = (matrix[0] * x + matrix[2] * y + matrix[4],
+ *                    matrix[1] * x + matrix[3] * y + matrix[5])
+ *
+ * Return value: the transformed #ArtBpath.
+ **/
+ArtBpath *
+art_bpath_affine_transform (const ArtBpath *src, const double matrix[6])
+{
+  int i;
+  int size;
+  ArtBpath *new;
+  ArtPathcode code;
+  double x, y;
+
+  for (i = 0; src[i].code != ART_END; i++);
+  size = i;
+
+  new = art_new (ArtBpath, size + 1);
+
+  for (i = 0; i < size; i++)
+    {
+      code = src[i].code;
+      new[i].code = code;
+      if (code == ART_CURVETO)
+       {
+         x = src[i].x1;
+         y = src[i].y1;
+         new[i].x1 = matrix[0] * x + matrix[2] * y + matrix[4];
+         new[i].y1 = matrix[1] * x + matrix[3] * y + matrix[5];
+         x = src[i].x2;
+         y = src[i].y2;
+         new[i].x2 = matrix[0] * x + matrix[2] * y + matrix[4];
+         new[i].y2 = matrix[1] * x + matrix[3] * y + matrix[5];
+       }
+      else
+       {
+         new[i].x1 = 0;
+         new[i].y1 = 0;
+         new[i].x2 = 0;
+         new[i].y2 = 0;
+       }
+      x = src[i].x3;
+      y = src[i].y3;
+      new[i].x3 = matrix[0] * x + matrix[2] * y + matrix[4];
+      new[i].y3 = matrix[1] * x + matrix[3] * y + matrix[5];
+    }
+  new[i].code = ART_END;
+  new[i].x1 = 0;
+  new[i].y1 = 0;
+  new[i].x2 = 0;
+  new[i].y2 = 0;
+  new[i].x3 = 0;
+  new[i].y3 = 0;
+
+  return new;
+}
+
diff --git a/lib/art/art_gray_svp.c b/lib/art/art_gray_svp.c
new file mode 100644 (file)
index 0000000..e5d5df9
--- /dev/null
@@ -0,0 +1,123 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Render a sorted vector path into a graymap. */
+
+#include "config.h"
+#include "art_gray_svp.h"
+
+#include <string.h>    /* for memset */
+#include "art_misc.h"
+
+#include "art_svp.h"
+#include "art_svp_render_aa.h"
+
+typedef struct _ArtGraySVPData ArtGraySVPData;
+
+struct _ArtGraySVPData {
+  art_u8 *buf;
+  int rowstride;
+  int x0, x1;
+};
+
+static void
+art_gray_svp_callback (void *callback_data, int y,
+                      int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtGraySVPData *data = (ArtGraySVPData *)callback_data;
+  art_u8 *linebuf;
+  int run_x0, run_x1;
+  int running_sum = start;
+  int x0, x1;
+  int k;
+
+#if 0
+  printf ("start = %d", start);
+  running_sum = start;
+  for (k = 0; k < n_steps; k++)
+    {
+      running_sum += steps[k].delta;
+      printf (" %d:%d", steps[k].x, running_sum >> 16);
+    }
+  printf ("\n");
+#endif
+
+  linebuf = data->buf;
+  x0 = data->x0;
+  x1 = data->x1;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0)
+       memset (linebuf, running_sum >> 16, run_x1 - x0);
+
+      for (k = 0; k < n_steps - 1; k++)
+       {
+         running_sum += steps[k].delta;
+         run_x0 = run_x1;
+         run_x1 = steps[k + 1].x;
+         if (run_x1 > run_x0)
+           memset (linebuf + run_x0 - x0, running_sum >> 16, run_x1 - run_x0);
+       }
+      running_sum += steps[k].delta;
+      if (x1 > run_x1)
+       memset (linebuf + run_x1 - x0, running_sum >> 16, x1 - run_x1);
+    }
+  else
+    {
+      memset (linebuf, running_sum >> 16, x1 - x0);
+    }
+
+  data->buf += data->rowstride;
+}
+
+/**
+ * art_gray_svp_aa: Render the vector path into the bytemap.
+ * @svp: The SVP to render.
+ * @x0: The view window's left coord.
+ * @y0: The view window's top coord.
+ * @x1: The view window's right coord.
+ * @y1: The view window's bottom coord.
+ * @buf: The buffer where the bytemap is stored.
+ * @rowstride: the rowstride for @buf.
+ *
+ * Each pixel gets a value proportional to the area within the pixel
+ * overlapping the (filled) SVP. Pixel (x, y) is stored at:
+ *
+ *    @buf[(y - * @y0) * @rowstride + (x - @x0)]
+ *
+ * All pixels @x0 <= x < @x1, @y0 <= y < @y1 are generated. A
+ * stored value of zero is no coverage, and a value of 255 is full
+ * coverage. The area within the pixel (x, y) is the region covered
+ * by [x..x+1] and [y..y+1].
+ **/
+void
+art_gray_svp_aa (const ArtSVP *svp,
+                int x0, int y0, int x1, int y1,
+                art_u8 *buf, int rowstride)
+{
+  ArtGraySVPData data;
+
+  data.buf = buf;
+  data.rowstride = rowstride;
+  data.x0 = x0;
+  data.x1 = x1;
+  art_svp_render_aa (svp, x0, y0, x1, y1, art_gray_svp_callback, &data);
+}
diff --git a/lib/art/art_misc.c b/lib/art/art_misc.c
new file mode 100644 (file)
index 0000000..1927062
--- /dev/null
@@ -0,0 +1,79 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Various utility functions RLL finds useful. */
+
+#include "config.h"
+#include "art_misc.h"
+
+#ifdef HAVE_UINSTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+
+/**
+ * art_die: Print the error message to stderr and exit with a return code of 1.
+ * @fmt: The printf-style format for the error message.
+ *
+ * Used for dealing with severe errors.
+ **/
+void
+art_die (const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+  exit (1);
+}
+
+/**
+ * art_warn: Print the warning message to stderr.
+ * @fmt: The printf-style format for the warning message.
+ *
+ * Used for generating warnings.
+ **/
+void
+art_warn (const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+}
+
+/**
+ * art_dprint: Print the debug message to stderr.
+ * @fmt: The printf-style format for the debug message.
+ *
+ * Used for generating debug output.
+ **/
+void
+art_dprint (const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+}
+
diff --git a/lib/art/art_pixbuf.c b/lib/art/art_pixbuf.c
new file mode 100644 (file)
index 0000000..e993753
--- /dev/null
@@ -0,0 +1,285 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_pixbuf.h"
+
+#include "art_misc.h"
+#include <string.h>
+
+/**
+ * art_pixbuf_new_rgb_dnotify: Create a new RGB #ArtPixBuf with explicit destroy notification.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ * @dfunc_data: The private data passed to @dfunc.
+ * @dfunc: The destroy notification function.
+ *
+ * Creates a generic data structure for holding a buffer of RGB
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * @dfunc is called with @dfunc_data and @pixels as arguments when the
+ * #ArtPixBuf is destroyed. Using a destroy notification function
+ * allows a wide range of memory management disciplines for the pixel
+ * memory. A NULL value for @dfunc is also allowed and means that no
+ * special action will be taken on destruction.
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_rgb_dnotify (art_u8 *pixels, int width, int height, int rowstride,
+                           void *dfunc_data, ArtDestroyNotify dfunc)
+{
+  ArtPixBuf *pixbuf;
+
+  pixbuf = art_new (ArtPixBuf, 1);
+
+  pixbuf->format = ART_PIX_RGB;
+  pixbuf->n_channels = 3;
+  pixbuf->has_alpha = 0;
+  pixbuf->bits_per_sample = 8;
+
+  pixbuf->pixels = (art_u8 *) pixels;
+  pixbuf->width = width;
+  pixbuf->height = height;
+  pixbuf->rowstride = rowstride;
+  pixbuf->destroy_data = dfunc_data;
+  pixbuf->destroy = dfunc;
+
+  return pixbuf;
+}
+
+/**
+ * art_pixbuf_new_rgba_dnotify: Create a new RGBA #ArtPixBuf with explicit destroy notification.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ * @dfunc_data: The private data passed to @dfunc.
+ * @dfunc: The destroy notification function.
+ *
+ * Creates a generic data structure for holding a buffer of RGBA
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * @dfunc is called with @dfunc_data and @pixels as arguments when the
+ * #ArtPixBuf is destroyed. Using a destroy notification function
+ * allows a wide range of memory management disciplines for the pixel
+ * memory. A NULL value for @dfunc is also allowed and means that no
+ * special action will be taken on destruction.
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_rgba_dnotify (art_u8 *pixels, int width, int height, int rowstride,
+                            void *dfunc_data, ArtDestroyNotify dfunc)
+{
+  ArtPixBuf *pixbuf;
+
+  pixbuf = art_new (ArtPixBuf, 1);
+
+  pixbuf->format = ART_PIX_RGB;
+  pixbuf->n_channels = 4;
+  pixbuf->has_alpha = 1;
+  pixbuf->bits_per_sample = 8;
+
+  pixbuf->pixels = (art_u8 *) pixels;
+  pixbuf->width = width;
+  pixbuf->height = height;
+  pixbuf->rowstride = rowstride;
+  pixbuf->destroy_data = dfunc_data;
+  pixbuf->destroy = dfunc;
+
+  return pixbuf;
+}
+
+/**
+ * art_pixbuf_new_const_rgb: Create a new RGB #ArtPixBuf with constant pixel data.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ *
+ * Creates a generic data structure for holding a buffer of RGB
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * No action is taken when the #ArtPixBuf is destroyed. Thus, this
+ * function is useful when the pixel data is constant or statically
+ * allocated.
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_const_rgb (const art_u8 *pixels, int width, int height, int rowstride)
+{
+  return art_pixbuf_new_rgb_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL);
+}
+
+/**
+ * art_pixbuf_new_const_rgba: Create a new RGBA #ArtPixBuf with constant pixel data.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ *
+ * Creates a generic data structure for holding a buffer of RGBA
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * No action is taken when the #ArtPixBuf is destroyed. Thus, this
+ * function is suitable when the pixel data is constant or statically
+ * allocated.
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_const_rgba (const art_u8 *pixels, int width, int height, int rowstride)
+{
+  return art_pixbuf_new_rgba_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL);
+}
+
+static void
+art_pixel_destroy (void *func_data, void *data)
+{
+  art_free (data);
+}
+
+/**
+ * art_pixbuf_new_rgb: Create a new RGB #ArtPixBuf.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ *
+ * Creates a generic data structure for holding a buffer of RGB
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * The @pixels buffer is freed with art_free() when the #ArtPixBuf is
+ * destroyed. Thus, this function is suitable when the pixel data is
+ * allocated with art_alloc().
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_rgb (art_u8 *pixels, int width, int height, int rowstride)
+{
+  return art_pixbuf_new_rgb_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy);
+}
+
+/**
+ * art_pixbuf_new_rgba: Create a new RGBA #ArtPixBuf.
+ * @pixels: A buffer containing the actual pixel data.
+ * @width: The width of the pixbuf.
+ * @height: The height of the pixbuf.
+ * @rowstride: The rowstride of the pixbuf.
+ *
+ * Creates a generic data structure for holding a buffer of RGBA
+ * pixels.  It is possible to think of an #ArtPixBuf as a
+ * virtualization over specific pixel buffer formats.
+ *
+ * The @pixels buffer is freed with art_free() when the #ArtPixBuf is
+ * destroyed. Thus, this function is suitable when the pixel data is
+ * allocated with art_alloc().
+ *
+ * Return value: The newly created #ArtPixBuf.
+ **/
+ArtPixBuf *
+art_pixbuf_new_rgba (art_u8 *pixels, int width, int height, int rowstride)
+{
+  return art_pixbuf_new_rgba_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy);
+}
+
+/**
+ * art_pixbuf_free: Destroy an #ArtPixBuf.
+ * @pixbuf: The #ArtPixBuf to be destroyed.
+ *
+ * Destroys the #ArtPixBuf, calling the destroy notification function
+ * (if non-NULL) so that the memory for the pixel buffer can be
+ * properly reclaimed.
+ **/
+void
+art_pixbuf_free (ArtPixBuf *pixbuf)
+{
+  ArtDestroyNotify destroy = pixbuf->destroy;
+  void *destroy_data = pixbuf->destroy_data;
+  art_u8 *pixels = pixbuf->pixels;
+
+  pixbuf->pixels = NULL;
+  pixbuf->destroy = NULL;
+  pixbuf->destroy_data = NULL;
+
+  if (destroy)
+    destroy (destroy_data, pixels);
+
+  art_free (pixbuf);
+}
+
+/**
+ * art_pixbuf_free_shallow:
+ * @pixbuf: The #ArtPixBuf to be destroyed.
+ *
+ * Destroys the #ArtPixBuf without calling the destroy notification function.
+ *
+ * This function is deprecated. Use the _dnotify variants for
+ * allocation instead.
+ **/
+void
+art_pixbuf_free_shallow (ArtPixBuf *pixbuf)
+{
+  art_free (pixbuf);
+}
+
+/**
+ * art_pixbuf_duplicate: Duplicate a pixbuf.
+ * @pixbuf: The #ArtPixBuf to duplicate.
+ *
+ * Duplicates a pixbuf, including duplicating the buffer.
+ *
+ * Return value: The duplicated pixbuf.
+ **/
+ArtPixBuf *
+art_pixbuf_duplicate (const ArtPixBuf *pixbuf)
+{
+  ArtPixBuf *result;
+  int size;
+
+  result = art_new (ArtPixBuf, 1);
+
+  result->format = pixbuf->format;
+  result->n_channels = pixbuf->n_channels;
+  result->has_alpha = pixbuf->has_alpha;
+  result->bits_per_sample = pixbuf->bits_per_sample;
+
+  size = (pixbuf->height - 1) * pixbuf->rowstride +
+    pixbuf->width * ((pixbuf->n_channels * pixbuf->bits_per_sample + 7) >> 3);
+  result->pixels = art_alloc (size);
+  memcpy (result->pixels, pixbuf->pixels, size);
+
+  result->width = pixbuf->width;
+  result->height = pixbuf->height;
+  result->rowstride = pixbuf->rowstride;
+  result->destroy_data = NULL;
+  result->destroy = art_pixel_destroy;
+
+  return result;
+}
diff --git a/lib/art/art_rect.c b/lib/art/art_rect.c
new file mode 100644 (file)
index 0000000..c9dd5b3
--- /dev/null
@@ -0,0 +1,215 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rect.h"
+
+#include <math.h>
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+
+/* rectangle primitives stolen from gzilla */
+
+/**
+ * art_irect_copy: Make a copy of an integer rectangle.
+ * @dest: Where the copy is stored.
+ * @src: The source rectangle.
+ *
+ * Copies the rectangle.
+ **/
+void
+art_irect_copy (ArtIRect *dest, const ArtIRect *src) {
+  dest->x0 = src->x0;
+  dest->y0 = src->y0;
+  dest->x1 = src->x1;
+  dest->y1 = src->y1;
+}
+
+/**
+ * art_irect_union: Find union of two integer rectangles.
+ * @dest: Where the result is stored.
+ * @src1: A source rectangle.
+ * @src2: Another source rectangle.
+ *
+ * Finds the smallest rectangle that includes @src1 and @src2.
+ **/
+void
+art_irect_union (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) {
+  if (art_irect_empty (src1)) {
+    art_irect_copy (dest, src2);
+  } else if (art_irect_empty (src2)) {
+    art_irect_copy (dest, src1);
+  } else {
+    dest->x0 = MIN (src1->x0, src2->x0);
+    dest->y0 = MIN (src1->y0, src2->y0);
+    dest->x1 = MAX (src1->x1, src2->x1);
+    dest->y1 = MAX (src1->y1, src2->y1);
+  }
+}
+
+/**
+ * art_irect_intersection: Find intersection of two integer rectangles.
+ * @dest: Where the result is stored.
+ * @src1: A source rectangle.
+ * @src2: Another source rectangle.
+ *
+ * Finds the intersection of @src1 and @src2.
+ **/
+void
+art_irect_intersect (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) {
+  dest->x0 = MAX (src1->x0, src2->x0);
+  dest->y0 = MAX (src1->y0, src2->y0);
+  dest->x1 = MIN (src1->x1, src2->x1);
+  dest->y1 = MIN (src1->y1, src2->y1);
+}
+
+/**
+ * art_irect_empty: Determine whether integer rectangle is empty.
+ * @src: The source rectangle.
+ *
+ * Return value: TRUE if @src is an empty rectangle, FALSE otherwise.
+ **/
+int
+art_irect_empty (const ArtIRect *src) {
+  return (src->x1 <= src->x0 || src->y1 <= src->y0);
+}
+
+#if 0
+gboolean irect_point_inside (ArtIRect *rect, GzwPoint *point) {
+  return (point->x >= rect->x0 && point->y >= rect->y0 &&
+         point->x < rect->x1 && point->y < rect->y1);
+}
+#endif
+
+/**
+ * art_drect_copy: Make a copy of a rectangle.
+ * @dest: Where the copy is stored.
+ * @src: The source rectangle.
+ *
+ * Copies the rectangle.
+ **/
+void
+art_drect_copy (ArtDRect *dest, const ArtDRect *src) {
+  dest->x0 = src->x0;
+  dest->y0 = src->y0;
+  dest->x1 = src->x1;
+  dest->y1 = src->y1;
+}
+
+/**
+ * art_drect_union: Find union of two rectangles.
+ * @dest: Where the result is stored.
+ * @src1: A source rectangle.
+ * @src2: Another source rectangle.
+ *
+ * Finds the smallest rectangle that includes @src1 and @src2.
+ **/
+void
+art_drect_union (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) {
+  if (art_drect_empty (src1)) {
+    art_drect_copy (dest, src2);
+  } else if (art_drect_empty (src2)) {
+    art_drect_copy (dest, src1);
+  } else {
+    dest->x0 = MIN (src1->x0, src2->x0);
+    dest->y0 = MIN (src1->y0, src2->y0);
+    dest->x1 = MAX (src1->x1, src2->x1);
+    dest->y1 = MAX (src1->y1, src2->y1);
+  }
+}
+
+/**
+ * art_drect_intersection: Find intersection of two rectangles.
+ * @dest: Where the result is stored.
+ * @src1: A source rectangle.
+ * @src2: Another source rectangle.
+ *
+ * Finds the intersection of @src1 and @src2.
+ **/
+void
+art_drect_intersect (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) {
+  dest->x0 = MAX (src1->x0, src2->x0);
+  dest->y0 = MAX (src1->y0, src2->y0);
+  dest->x1 = MIN (src1->x1, src2->x1);
+  dest->y1 = MIN (src1->y1, src2->y1);
+}
+
+/**
+ * art_irect_empty: Determine whether rectangle is empty.
+ * @src: The source rectangle.
+ *
+ * Return value: TRUE if @src is an empty rectangle, FALSE otherwise.
+ **/
+int
+art_drect_empty (const ArtDRect *src) {
+  return (src->x1 <= src->x0 || src->y1 <= src->y0);
+}
+
+/**
+ * art_drect_affine_transform: Affine transform rectangle.
+ * @dst: Where to store the result.
+ * @src: The source rectangle.
+ * @matrix: The affine transformation.
+ *
+ * Find the smallest rectangle enclosing the affine transformed @src.
+ * The result is exactly the affine transformation of @src when
+ * @matrix specifies a rectilinear affine transformation, otherwise it
+ * is a conservative approximation.
+ **/
+void
+art_drect_affine_transform (ArtDRect *dst, const ArtDRect *src, const double matrix[6])
+{
+  double x00, y00, x10, y10;
+  double x01, y01, x11, y11;
+
+  x00 = src->x0 * matrix[0] + src->y0 * matrix[2] + matrix[4];
+  y00 = src->x0 * matrix[1] + src->y0 * matrix[3] + matrix[5];
+  x10 = src->x1 * matrix[0] + src->y0 * matrix[2] + matrix[4];
+  y10 = src->x1 * matrix[1] + src->y0 * matrix[3] + matrix[5];
+  x01 = src->x0 * matrix[0] + src->y1 * matrix[2] + matrix[4];
+  y01 = src->x0 * matrix[1] + src->y1 * matrix[3] + matrix[5];
+  x11 = src->x1 * matrix[0] + src->y1 * matrix[2] + matrix[4];
+  y11 = src->x1 * matrix[1] + src->y1 * matrix[3] + matrix[5];
+  dst->x0 = MIN (MIN (x00, x10), MIN (x01, x11));
+  dst->y0 = MIN (MIN (y00, y10), MIN (y01, y11));
+  dst->x1 = MAX (MAX (x00, x10), MAX (x01, x11));
+  dst->y1 = MAX (MAX (y00, y10), MAX (y01, y11));
+}
+
+/**
+ * art_drect_to_irect: Convert rectangle to integer rectangle.
+ * @dst: Where to store resulting integer rectangle.
+ * @src: The source rectangle.
+ *
+ * Find the smallest integer rectangle that encloses @src.
+ **/
+void
+art_drect_to_irect (ArtIRect *dst, ArtDRect *src)
+{
+  dst->x0 = floor (src->x0);
+  dst->y0 = floor (src->y0);
+  dst->x1 = ceil (src->x1);
+  dst->y1 = ceil (src->y1);
+}
diff --git a/lib/art/art_rect_svp.c b/lib/art/art_rect_svp.c
new file mode 100644 (file)
index 0000000..d981996
--- /dev/null
@@ -0,0 +1,82 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rect_svp.h"
+
+#include "art_misc.h"
+#include "art_svp.h"
+#include "art_rect.h"
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+
+/**
+ * art_drect_svp: Find the bounding box of a sorted vector path.
+ * @bbox: Where to store the bounding box.
+ * @svp: The SVP.
+ *
+ * Finds the bounding box of the SVP.
+ **/
+void
+art_drect_svp (ArtDRect *bbox, const ArtSVP *svp)
+{
+  int i;
+
+  if (svp->n_segs == 0)
+    {
+      bbox->x0 = 0;
+      bbox->y0 = 0;
+      bbox->x1 = 0;
+      bbox->y1 = 0;
+      return;
+    }
+
+  art_drect_copy (bbox, &svp->segs[0].bbox);
+  
+  for (i = 1; i < svp->n_segs; i++)
+    {
+      bbox->x0 = MIN (bbox->x0, svp->segs[i].bbox.x0);
+      bbox->y0 = MIN (bbox->y0, svp->segs[i].bbox.y0);
+      bbox->x1 = MAX (bbox->x1, svp->segs[i].bbox.x1);
+      bbox->y1 = MAX (bbox->y1, svp->segs[i].bbox.y1);
+    }
+}
+
+/**
+ * art_drect_svp_union: Compute the bounding box of the svp and union it in to the existing bounding box.
+ * @bbox: Initial boundin box and where to store the bounding box.
+ * @svp: The SVP.
+ *
+ * Finds the bounding box of the SVP, computing its union with an
+ * existing bbox.
+ **/
+void
+art_drect_svp_union (ArtDRect *bbox, const ArtSVP *svp)
+{
+  ArtDRect svp_bbox;
+
+  art_drect_svp (&svp_bbox, svp);
+  art_drect_union (bbox, bbox, &svp_bbox);
+}
diff --git a/lib/art/art_rect_uta.c b/lib/art/art_rect_uta.c
new file mode 100644 (file)
index 0000000..cd002f8
--- /dev/null
@@ -0,0 +1,134 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rect_uta.h"
+
+/* Functions to decompose a microtile array into a list of rectangles. */
+
+/**
+ * art_rect_list_from_uta: Decompose uta into list of rectangles.
+ * @uta: The source uta.
+ * @max_width: The maximum width of the resulting rectangles.
+ * @max_height: The maximum height of the resulting rectangles.
+ * @p_nrects: Where to store the number of returned rectangles.
+ *
+ * Allocates a new list of rectangles, sets *@p_nrects to the number
+ * in the list. This list should be freed with art_free().
+ *
+ * Each rectangle bounded in size by (@max_width, @max_height).
+ * However, these bounds must be at least the size of one tile.
+ *
+ * This routine provides a precise implementation, i.e. the rectangles
+ * cover exactly the same area as the uta. It is thus appropriate in
+ * cases where the overhead per rectangle is small compared with the
+ * cost of filling in extra pixels.
+ *
+ * Return value: An array containing the resulting rectangles.
+ **/
+ArtIRect *
+art_rect_list_from_uta (ArtUta *uta, int max_width, int max_height,
+                       int *p_nrects)
+{
+  ArtIRect *rects;
+  int n_rects, n_rects_max;
+  int x, y;
+  int width, height;
+  int ix;
+  int left_ix;
+  ArtUtaBbox *utiles;
+  ArtUtaBbox bb;
+  int x0, y0, x1, y1;
+  int *glom;
+  int glom_rect;
+
+  n_rects = 0;
+  n_rects_max = 1;
+  rects = art_new (ArtIRect, n_rects_max);
+
+  width = uta->width;
+  height = uta->height;
+  utiles = uta->utiles;
+
+  glom = art_new (int, width * height);
+  for (ix = 0; ix < width * height; ix++)
+    glom[ix] = -1;
+
+  ix = 0;
+  for (y = 0; y < height; y++)
+    for (x = 0; x < width; x++)
+      {
+       bb = utiles[ix];
+       if (bb)
+         {
+           x0 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X0(bb);
+           y0 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y0(bb);
+           y1 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y1(bb);
+
+           left_ix = ix;
+           /* now try to extend to the right */
+           while (x != width - 1 &&
+                  ART_UTA_BBOX_X1(bb) == ART_UTILE_SIZE &&
+                  (((bb & 0xffffff) ^ utiles[ix + 1]) & 0xffff00ff) == 0 &&
+                  (((uta->x0 + x + 1) << ART_UTILE_SHIFT) +
+                   ART_UTA_BBOX_X1(utiles[ix + 1]) -
+                   x0) <= max_width)
+             {
+               bb = utiles[ix + 1];
+               ix++;
+               x++;
+             }
+           x1 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X1(bb);
+
+
+           /* if rectangle nonempty */
+           if ((x1 ^ x0) | (y1 ^ y0))
+             {
+               /* try to glom onto an existing rectangle */
+               glom_rect = glom[left_ix];
+               if (glom_rect != -1 &&
+                   x0 == rects[glom_rect].x0 &&
+                   x1 == rects[glom_rect].x1 &&
+                   y0 == rects[glom_rect].y1 &&
+                   y1 - rects[glom_rect].y0 <= max_height)
+                 {
+                   rects[glom_rect].y1 = y1;
+                 }
+               else
+                 {
+                   if (n_rects == n_rects_max)
+                     art_expand (rects, ArtIRect, n_rects_max);
+                   rects[n_rects].x0 = x0;
+                   rects[n_rects].y0 = y0;
+                   rects[n_rects].x1 = x1;
+                   rects[n_rects].y1 = y1;
+                   glom_rect = n_rects;
+                   n_rects++;
+                 }
+               if (y != height - 1)
+                 glom[left_ix + width] = glom_rect;
+             }
+         }
+       ix++;
+      }
+
+  art_free (glom);
+  *p_nrects = n_rects;
+  return rects;
+}
diff --git a/lib/art/art_render.c b/lib/art/art_render.c
new file mode 100644 (file)
index 0000000..65b344c
--- /dev/null
@@ -0,0 +1,1383 @@
+/*
+ * art_render.c: Modular rendering architecture.
+ *
+ * Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2000 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 "config.h"
+#include "art_render.h"
+
+#include "art_rgb.h"
+
+typedef struct _ArtRenderPriv ArtRenderPriv;
+
+struct _ArtRenderPriv {
+  ArtRender super;
+
+  ArtImageSource *image_source;
+
+  int n_mask_source;
+  ArtMaskSource **mask_source;
+
+  int n_callbacks;
+  ArtRenderCallback **callbacks;
+};
+
+ArtRender *
+art_render_new (int x0, int y0, int x1, int y1,
+               art_u8 *pixels, int rowstride,
+               int n_chan, int depth, ArtAlphaType alpha_type,
+               ArtAlphaGamma *alphagamma)
+{
+  ArtRenderPriv *priv;
+  ArtRender *result;
+
+  priv = art_new (ArtRenderPriv, 1);
+  result = &priv->super;
+
+  if (n_chan > ART_MAX_CHAN)
+    {
+      art_warn ("art_render_new: n_chan = %d, exceeds %d max\n",
+               n_chan, ART_MAX_CHAN);
+      return NULL;
+    }
+  if (depth > ART_MAX_DEPTH)
+    {
+      art_warn ("art_render_new: depth = %d, exceeds %d max\n",
+               depth, ART_MAX_DEPTH);
+      return NULL;
+    }
+  if (x0 >= x1)
+    {
+      art_warn ("art_render_new: x0 >= x1 (x0 = %d, x1 = %d)\n", x0, x1);
+      return NULL;
+    }
+  result->x0 = x0;
+  result->y0 = y0;
+  result->x1 = x1;
+  result->y1 = y1;
+  result->pixels = pixels;
+  result->rowstride = rowstride;
+  result->n_chan = n_chan;
+  result->depth = depth;
+  result->alpha_type = alpha_type;
+
+  result->clear = ART_FALSE;
+  result->opacity = 0x10000;
+  result->compositing_mode = ART_COMPOSITE_NORMAL;
+  result->alphagamma = alphagamma;
+
+  result->alpha_buf = NULL;
+  result->image_buf = NULL;
+
+  result->run = NULL;
+  result->span_x = NULL;
+
+  result->need_span = ART_FALSE;
+
+  priv->image_source = NULL;
+
+  priv->n_mask_source = 0;
+  priv->mask_source = NULL;
+
+  return result;
+}
+
+/* todo on clear routines: I haven't really figured out what to do
+   with clearing the alpha channel. It _should_ be possible to clear
+   to an arbitrary RGBA color. */
+
+/**
+ * art_render_clear: Set clear color.
+ * @clear_color: Color with which to clear dest.
+ *
+ * Sets clear color, equivalent to actually clearing the destination
+ * buffer before rendering. This is the most general form.
+ **/
+void
+art_render_clear (ArtRender *render, const ArtPixMaxDepth *clear_color)
+{
+  int i;
+  int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE);
+
+  render->clear = ART_TRUE;
+  for (i = 0; i < n_ch; i++)
+    render->clear_color[i] = clear_color[i];
+}
+
+/**
+ * art_render_clear_rgb: Set clear color, given in RGB format.
+ * @clear_rgb: Clear color, in 0xRRGGBB format.
+ *
+ * Sets clear color, equivalent to actually clearing the destination
+ * buffer before rendering.
+ **/
+void
+art_render_clear_rgb (ArtRender *render, art_u32 clear_rgb)
+{
+  if (render->n_chan != 3)
+    art_warn ("art_render_clear_rgb: called on render with %d channels, only works with 3\n",
+             render->n_chan);
+  else
+    {
+      int r, g, b;
+
+      render->clear = ART_TRUE;
+      r = clear_rgb >> 16;
+      g = (clear_rgb >> 8) & 0xff;
+      b = clear_rgb & 0xff;
+      render->clear_color[0] = ART_PIX_MAX_FROM_8(r);
+      render->clear_color[1] = ART_PIX_MAX_FROM_8(g);
+      render->clear_color[2] = ART_PIX_MAX_FROM_8(b);
+    }
+}
+
+static void
+art_render_nop_done (ArtRenderCallback *self, ArtRender *render)
+{
+}
+
+static void
+art_render_clear_render_rgb8 (ArtRenderCallback *self, ArtRender *render,
+                             art_u8 *dest, int y)
+{
+  int width = render->x1 - render->x0;
+  art_u8 r, g, b;
+  ArtPixMaxDepth color_max;
+
+  color_max = render->clear_color[0];
+  r = ART_PIX_8_FROM_MAX (color_max);
+  color_max = render->clear_color[1];
+  g = ART_PIX_8_FROM_MAX (color_max);
+  color_max = render->clear_color[2];
+  b = ART_PIX_8_FROM_MAX (color_max);
+
+  art_rgb_fill_run (dest, r, g, b, width);
+}
+
+static void
+art_render_clear_render_8 (ArtRenderCallback *self, ArtRender *render,
+                          art_u8 *dest, int y)
+{
+  int width = render->x1 - render->x0;
+  int i, j;
+  int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE);
+  int ix;
+  art_u8 color[ART_MAX_CHAN + 1];
+
+  for (j = 0; j < n_ch; j++)
+    {
+      ArtPixMaxDepth color_max = render->clear_color[j];
+      color[j] = ART_PIX_8_FROM_MAX (color_max);
+    }
+
+  ix = 0;
+  for (i = 0; i < width; i++)
+    for (j = 0; j < n_ch; j++)
+      dest[ix++] = color[j];
+}
+
+const ArtRenderCallback art_render_clear_rgb8_obj =
+{
+  art_render_clear_render_rgb8,
+  art_render_nop_done
+};
+
+const ArtRenderCallback art_render_clear_8_obj =
+{
+  art_render_clear_render_8,
+  art_render_nop_done
+};
+
+#if ART_MAX_DEPTH >= 16
+
+static void
+art_render_clear_render_16 (ArtRenderCallback *self, ArtRender *render,
+                           art_u8 *dest, int y)
+{
+  int width = render->x1 - render->x0;
+  int i, j;
+  int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE);
+  int ix;
+  art_u16 *dest_16 = (art_u16 *)dest;
+  art_u8 color[ART_MAX_CHAN + 1];
+
+  for (j = 0; j < n_ch; j++)
+    {
+      int color_16 = render->clear_color[j];
+      color[j] = color_16;
+    }
+
+  ix = 0;
+  for (i = 0; i < width; i++)
+    for (j = 0; j < n_ch; j++)
+      dest_16[ix++] = color[j];
+}
+
+const ArtRenderCallback art_render_clear_16_obj =
+{
+  art_render_clear_render_16,
+  art_render_nop_done
+};
+
+#endif /* ART_MAX_DEPTH >= 16 */
+
+/* todo: inline */
+static ArtRenderCallback *
+art_render_choose_clear_callback (ArtRender *render)
+{
+  ArtRenderCallback *clear_callback;
+
+  if (render->depth == 8)
+    {
+      if (render->n_chan == 3 &&
+         render->alpha_type == ART_ALPHA_NONE)
+       clear_callback = (ArtRenderCallback *)&art_render_clear_rgb8_obj;
+      else
+       clear_callback = (ArtRenderCallback *)&art_render_clear_8_obj;
+    }
+#if ART_MAX_DEPTH >= 16
+  else if (render->depth == 16)
+    clear_callback = (ArtRenderCallback *)&art_render_clear_16_obj;
+#endif
+  else
+    {
+      art_die ("art_render_choose_clear_callback: inconsistent render->depth = %d\n",
+              render->depth);
+    }
+  return clear_callback;
+}
+
+#if 0
+/* todo: get around to writing this */
+static void
+art_render_composite_render_noa_8_norm (ArtRenderCallback *self, ArtRender *render,
+                                       art_u8 *dest, int y)
+{
+  int width = render->x1 - render->x0;
+
+}
+#endif
+
+/* This is the most general form of the function. It is slow but
+   (hopefully) correct. Actually, I'm still worried about roundoff
+   errors in the premul case - it seems to me that an off-by-one could
+   lead to overflow. */
+static void
+art_render_composite (ArtRenderCallback *self, ArtRender *render,
+                                       art_u8 *dest, int y)
+{
+  ArtRenderMaskRun *run = render->run;
+  art_u32 depth = render->depth;
+  int n_run = render->n_run;
+  int x0 = render->x0;
+  int x;
+  int run_x0, run_x1;
+  art_u8 *alpha_buf = render->alpha_buf;
+  art_u8 *image_buf = render->image_buf;
+  int i, j;
+  art_u32 tmp;
+  art_u32 run_alpha;
+  art_u32 alpha;
+  int image_ix;
+  art_u16 src[ART_MAX_CHAN + 1];
+  art_u16 dst[ART_MAX_CHAN + 1];
+  int n_chan = render->n_chan;
+  ArtAlphaType alpha_type = render->alpha_type;
+  int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE);
+  int dst_pixstride = n_ch * (depth >> 3);
+  int buf_depth = render->buf_depth;
+  ArtAlphaType buf_alpha = render->buf_alpha;
+  int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE);
+  int buf_pixstride = buf_n_ch * (buf_depth >> 3);
+  art_u8 *bufptr;
+  art_u32 src_alpha;
+  art_u32 src_mul;
+  art_u8 *dstptr;
+  art_u32 dst_alpha;
+  art_u32 dst_mul;
+
+  image_ix = 0;
+  for (i = 0; i < n_run - 1; i++)
+    {
+      run_x0 = run[i].x;
+      run_x1 = run[i + 1].x;
+      tmp = run[i].alpha;
+      if (tmp < 0x8100)
+       continue;
+
+      run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */
+      bufptr = image_buf + (run_x0 - x0) * buf_pixstride;
+      dstptr = dest + (run_x0 - x0) * dst_pixstride;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         if (alpha_buf)
+           {
+             if (depth == 8)
+               {
+                 tmp = run_alpha * alpha_buf[x - x0] + 0x80;
+                 /* range 0x80 .. 0xff0080 */
+                 alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8;
+               }
+             else /* (depth == 16) */
+               {
+                 tmp = ((art_u16 *)alpha_buf)[x - x0];
+                 tmp = (run_alpha * tmp + 0x8000) >> 8;
+                 /* range 0x80 .. 0xffff80 */
+                 alpha = (tmp + (tmp >> 16)) >> 8;
+               }
+           }
+         else
+           alpha = run_alpha;
+         /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */
+
+         /* convert (src pixel * alpha) to premul alpha form,
+            store in src as 0..0xffff range */
+         if (buf_alpha == ART_ALPHA_NONE)
+           {
+             src_alpha = alpha;
+             src_mul = src_alpha;
+           }
+         else
+           {
+             if (buf_depth == 8)
+               {
+                 tmp = alpha * bufptr[n_chan] + 0x80;
+                 /* range 0x80 .. 0xff0080 */
+                 src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8;
+               }
+             else /* (depth == 16) */
+               {
+                 tmp = ((art_u16 *)bufptr)[n_chan];
+                 tmp = (alpha * tmp + 0x8000) >> 8;
+                 /* range 0x80 .. 0xffff80 */
+                 src_alpha = (tmp + (tmp >> 16)) >> 8;
+               }
+             if (buf_alpha == ART_ALPHA_SEPARATE)
+               src_mul = src_alpha;
+             else /* buf_alpha == (ART_ALPHA_PREMUL) */
+               src_mul = alpha;
+           }
+         /* src_alpha is the (alpha of the source pixel * alpha),
+            range 0..0x10000 */
+
+         if (buf_depth == 8)
+           {
+             src_mul *= 0x101;
+             for (j = 0; j < n_chan; j++)
+               src[j] = (bufptr[j] * src_mul + 0x8000) >> 16;
+           }
+         else if (buf_depth == 16)
+           {
+             for (j = 0; j < n_chan; j++)
+               src[j] = (((art_u16 *)bufptr)[j] * src_mul + 0x8000) >> 16;
+           }
+         bufptr += buf_pixstride;
+
+         /* src[0..n_chan - 1] (range 0..0xffff) and src_alpha (range
+             0..0x10000) now contain the source pixel with
+             premultiplied alpha */
+
+         /* convert dst pixel to premul alpha form,
+            store in dst as 0..0xffff range */
+         if (alpha_type == ART_ALPHA_NONE)
+           {
+             dst_alpha = 0x10000;
+             dst_mul = dst_alpha;
+           }
+         else
+           {
+             if (depth == 8)
+               {
+                 tmp = dstptr[n_chan];
+                 /* range 0..0xff */
+                 dst_alpha = (tmp << 8) + tmp + (tmp >> 7);
+               }
+             else /* (depth == 16) */
+               {
+                 tmp = ((art_u16 *)dstptr)[n_chan];
+                 dst_alpha = (tmp + (tmp >> 15));
+               }
+             if (alpha_type == ART_ALPHA_SEPARATE)
+               dst_mul = dst_alpha;
+             else /* (alpha_type == ART_ALPHA_PREMUL) */
+               dst_mul = 0x10000;
+           }
+         /* dst_alpha is the alpha of the dest pixel,
+            range 0..0x10000 */
+
+         if (depth == 8)
+           {
+             dst_mul *= 0x101;
+             for (j = 0; j < n_chan; j++)
+               dst[j] = (dstptr[j] * dst_mul + 0x8000) >> 16;
+           }
+         else if (buf_depth == 16)
+           {
+             for (j = 0; j < n_chan; j++)
+               dst[j] = (((art_u16 *)dstptr)[j] * dst_mul + 0x8000) >> 16;
+           }
+
+         /* do the compositing, dst = (src over dst) */
+         for (j = 0; j < n_chan; j++)
+           {
+             art_u32 srcv, dstv;
+             art_u32 tmp;
+
+             srcv = src[j];
+             dstv = dst[j];
+             tmp = ((dstv * (0x10000 - src_alpha) + 0x8000) >> 16) + srcv;
+             tmp -= tmp >> 16;
+             dst[j] = tmp;
+           }
+
+         if (alpha_type == ART_ALPHA_NONE)
+           {
+             if (depth == 8)
+               dst_mul = 0xff;
+             else /* (depth == 16) */
+               dst_mul = 0xffff;
+           }
+         else
+           {
+             if (src_alpha >= 0x10000)
+               dst_alpha = 0x10000;
+             else
+               dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8;
+             if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0)
+               {
+                 if (depth == 8)
+                   dst_mul = 0xff;
+                 else /* (depth == 16) */
+                   dst_mul = 0xffff;
+               }
+             else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */
+               {
+                 if (depth == 8)
+                   dst_mul = 0xff0000 / dst_alpha;
+                 else /* (depth == 16) */
+                   dst_mul = 0xffff0000 / dst_alpha;
+               }
+           }
+         if (depth == 8)
+           {
+             for (j = 0; j < n_chan; j++)
+               dstptr[j] = (dst[j] * dst_mul + 0x8000) >> 16;
+             if (alpha_type != ART_ALPHA_NONE)
+               dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16;
+           }
+         else if (depth == 16)
+           {
+             for (j = 0; j < n_chan; j++)
+               ((art_u16 *)dstptr)[j] = (dst[j] * dst_mul + 0x8000) >> 16;
+             if (alpha_type != ART_ALPHA_NONE)
+               ((art_u16 *)dstptr)[n_chan] = (dst_alpha * 0xffff + 0x8000) >> 16;
+           }
+         dstptr += dst_pixstride;
+       }
+    }
+}
+
+const ArtRenderCallback art_render_composite_obj =
+{
+  art_render_composite,
+  art_render_nop_done
+};
+
+static void
+art_render_composite_8 (ArtRenderCallback *self, ArtRender *render,
+                       art_u8 *dest, int y)
+{
+  ArtRenderMaskRun *run = render->run;
+  int n_run = render->n_run;
+  int x0 = render->x0;
+  int x;
+  int run_x0, run_x1;
+  art_u8 *alpha_buf = render->alpha_buf;
+  art_u8 *image_buf = render->image_buf;
+  int i, j;
+  art_u32 tmp;
+  art_u32 run_alpha;
+  art_u32 alpha;
+  int image_ix;
+  int n_chan = render->n_chan;
+  ArtAlphaType alpha_type = render->alpha_type;
+  int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE);
+  int dst_pixstride = n_ch;
+  ArtAlphaType buf_alpha = render->buf_alpha;
+  int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE);
+  int buf_pixstride = buf_n_ch;
+  art_u8 *bufptr;
+  art_u32 src_alpha;
+  art_u32 src_mul;
+  art_u8 *dstptr;
+  art_u32 dst_alpha;
+  art_u32 dst_mul, dst_save_mul;
+
+  image_ix = 0;
+  for (i = 0; i < n_run - 1; i++)
+    {
+      run_x0 = run[i].x;
+      run_x1 = run[i + 1].x;
+      tmp = run[i].alpha;
+      if (tmp < 0x10000)
+       continue;
+
+      run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */
+      bufptr = image_buf + (run_x0 - x0) * buf_pixstride;
+      dstptr = dest + (run_x0 - x0) * dst_pixstride;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         if (alpha_buf)
+           {
+             tmp = run_alpha * alpha_buf[x - x0] + 0x80;
+             /* range 0x80 .. 0xff0080 */
+             alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8;
+           }
+         else
+           alpha = run_alpha;
+         /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */
+
+         /* convert (src pixel * alpha) to premul alpha form,
+            store in src as 0..0xffff range */
+         if (buf_alpha == ART_ALPHA_NONE)
+           {
+             src_alpha = alpha;
+             src_mul = src_alpha;
+           }
+         else
+           {
+             tmp = alpha * bufptr[n_chan] + 0x80;
+             /* range 0x80 .. 0xff0080 */
+             src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8;
+
+             if (buf_alpha == ART_ALPHA_SEPARATE)
+               src_mul = src_alpha;
+             else /* buf_alpha == (ART_ALPHA_PREMUL) */
+               src_mul = alpha;
+           }
+         /* src_alpha is the (alpha of the source pixel * alpha),
+            range 0..0x10000 */
+
+         src_mul *= 0x101;
+
+         if (alpha_type == ART_ALPHA_NONE)
+           {
+             dst_alpha = 0x10000;
+             dst_mul = dst_alpha;
+           }
+         else
+           {
+             tmp = dstptr[n_chan];
+             /* range 0..0xff */
+             dst_alpha = (tmp << 8) + tmp + (tmp >> 7);
+             if (alpha_type == ART_ALPHA_SEPARATE)
+               dst_mul = dst_alpha;
+             else /* (alpha_type == ART_ALPHA_PREMUL) */
+               dst_mul = 0x10000;
+           }
+         /* dst_alpha is the alpha of the dest pixel,
+            range 0..0x10000 */
+
+         dst_mul *= 0x101;
+
+         if (alpha_type == ART_ALPHA_NONE)
+           {
+             dst_save_mul = 0xff;
+           }
+         else
+           {
+             if (src_alpha >= 0x10000)
+               dst_alpha = 0x10000;
+             else
+               dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8;
+             if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0)
+               {
+                 dst_save_mul = 0xff;
+               }
+             else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */
+               {
+                 dst_save_mul = 0xff0000 / dst_alpha;
+               }
+           }
+
+         for (j = 0; j < n_chan; j++)
+           {
+             art_u32 src, dst;
+             art_u32 tmp;
+
+             src = (bufptr[j] * src_mul + 0x8000) >> 16;
+             dst = (dstptr[j] * dst_mul + 0x8000) >> 16;
+             tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src;
+             tmp -= tmp >> 16;
+             dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16;
+           }
+         if (alpha_type != ART_ALPHA_NONE)
+           dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16;
+
+         bufptr += buf_pixstride;
+         dstptr += dst_pixstride;
+       }
+    }
+}
+
+const ArtRenderCallback art_render_composite_8_obj =
+{
+  art_render_composite_8,
+  art_render_nop_done
+};
+
+
+/* Assumes:
+ * alpha_buf is NULL
+ * buf_alpha = ART_ALPHA_NONE  (source)
+ * alpha_type = ART_ALPHA_SEPARATE (dest)
+ * n_chan = 3;
+ */
+static void
+art_render_composite_8_opt1 (ArtRenderCallback *self, ArtRender *render,
+                            art_u8 *dest, int y)
+{
+  ArtRenderMaskRun *run = render->run;
+  int n_run = render->n_run;
+  int x0 = render->x0;
+  int x;
+  int run_x0, run_x1;
+  art_u8 *image_buf = render->image_buf;
+  int i, j;
+  art_u32 tmp;
+  art_u32 run_alpha;
+  int image_ix;
+  art_u8 *bufptr;
+  art_u32 src_mul;
+  art_u8 *dstptr;
+  art_u32 dst_alpha;
+  art_u32 dst_mul, dst_save_mul;
+
+  image_ix = 0;
+  for (i = 0; i < n_run - 1; i++)
+    {
+      run_x0 = run[i].x;
+      run_x1 = run[i + 1].x;
+      tmp = run[i].alpha;
+      if (tmp < 0x10000)
+       continue;
+
+      run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */
+      bufptr = image_buf + (run_x0 - x0) * 3;
+      dstptr = dest + (run_x0 - x0) * 4;
+      if (run_alpha == 0x10000)
+       {
+         for (x = run_x0; x < run_x1; x++)
+           {
+             *dstptr++ = *bufptr++;
+             *dstptr++ = *bufptr++;
+             *dstptr++ = *bufptr++;
+             *dstptr++ = 0xff;
+           }
+       }
+      else
+       {
+         for (x = run_x0; x < run_x1; x++)
+           {
+             src_mul = run_alpha * 0x101;
+             
+             tmp = dstptr[3];
+             /* range 0..0xff */
+             dst_alpha = (tmp << 8) + tmp + (tmp >> 7);
+             dst_mul = dst_alpha;
+             /* dst_alpha is the alpha of the dest pixel,
+                range 0..0x10000 */
+             
+             dst_mul *= 0x101;
+             
+             dst_alpha += ((((0x10000 - dst_alpha) * run_alpha) >> 8) + 0x80) >> 8;
+             if (dst_alpha == 0)
+                 dst_save_mul = 0xff;
+             else /* (dst_alpha != 0) */
+                 dst_save_mul = 0xff0000 / dst_alpha;
+             
+             for (j = 0; j < 3; j++)
+               {
+                 art_u32 src, dst;
+                 art_u32 tmp;
+                 
+                 src = (bufptr[j] * src_mul + 0x8000) >> 16;
+                 dst = (dstptr[j] * dst_mul + 0x8000) >> 16;
+                 tmp = ((dst * (0x10000 - run_alpha) + 0x8000) >> 16) + src;
+                 tmp -= tmp >> 16;
+                 dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16;
+               }
+             dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16;
+             
+             bufptr += 3;
+             dstptr += 4;
+           }
+       }
+    }
+}
+
+
+const ArtRenderCallback art_render_composite_8_opt1_obj =
+{
+  art_render_composite_8_opt1,
+  art_render_nop_done
+};
+
+/* Assumes:
+ * alpha_buf is NULL
+ * buf_alpha = ART_ALPHA_PREMUL  (source)
+ * alpha_type = ART_ALPHA_SEPARATE (dest)
+ * n_chan = 3;
+ */
+static void
+art_render_composite_8_opt2 (ArtRenderCallback *self, ArtRender *render,
+                            art_u8 *dest, int y)
+{
+  ArtRenderMaskRun *run = render->run;
+  int n_run = render->n_run;
+  int x0 = render->x0;
+  int x;
+  int run_x0, run_x1;
+  art_u8 *image_buf = render->image_buf;
+  int i, j;
+  art_u32 tmp;
+  art_u32 run_alpha;
+  int image_ix;
+  art_u8 *bufptr;
+  art_u32 src_alpha;
+  art_u32 src_mul;
+  art_u8 *dstptr;
+  art_u32 dst_alpha;
+  art_u32 dst_mul, dst_save_mul;
+
+  image_ix = 0;
+  for (i = 0; i < n_run - 1; i++)
+    {
+      run_x0 = run[i].x;
+      run_x1 = run[i + 1].x;
+      tmp = run[i].alpha;
+      if (tmp < 0x10000)
+       continue;
+
+      run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */
+      bufptr = image_buf + (run_x0 - x0) * 4;
+      dstptr = dest + (run_x0 - x0) * 4;
+      if (run_alpha == 0x10000)
+       {
+         for (x = run_x0; x < run_x1; x++)
+           {
+             src_alpha = (bufptr[3] << 8) + bufptr[3] + (bufptr[3] >> 7);
+             /* src_alpha is the (alpha of the source pixel),
+                range 0..0x10000 */
+             
+             dst_alpha = (dstptr[3] << 8) + dstptr[3] + (dstptr[3] >> 7);
+             /* dst_alpha is the alpha of the dest pixel,
+                range 0..0x10000 */
+             
+             dst_mul = dst_alpha*0x101;
+             
+             if (src_alpha >= 0x10000)
+               dst_alpha = 0x10000;
+             else
+               dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8;
+             
+             if (dst_alpha == 0)
+                 dst_save_mul = 0xff;
+             else /* dst_alpha != 0) */
+                 dst_save_mul = 0xff0000 / dst_alpha;
+             
+             for (j = 0; j < 3; j++)
+               {
+                 art_u32 src, dst;
+                 art_u32 tmp;
+                 
+                 src = (bufptr[j] << 8) |  bufptr[j];
+                 dst = (dstptr[j] * dst_mul + 0x8000) >> 16;
+                 tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src;
+                 tmp -= tmp >> 16;
+                 dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16;
+               }
+             dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16;
+             
+             bufptr += 4;
+             dstptr += 4;
+           }
+       }
+      else
+       {
+         for (x = run_x0; x < run_x1; x++)
+           {
+             tmp = run_alpha * bufptr[3] + 0x80;
+             /* range 0x80 .. 0xff0080 */
+             src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8;
+             /* src_alpha is the (alpha of the source pixel * alpha),
+                range 0..0x10000 */
+             
+             src_mul = run_alpha * 0x101;
+             
+             tmp = dstptr[3];
+             /* range 0..0xff */
+             dst_alpha = (tmp << 8) + tmp + (tmp >> 7);
+             dst_mul = dst_alpha;
+             /* dst_alpha is the alpha of the dest pixel,
+                range 0..0x10000 */
+             
+             dst_mul *= 0x101;
+             
+             if (src_alpha >= 0x10000)
+               dst_alpha = 0x10000;
+             else
+               dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8;
+             
+             if (dst_alpha == 0)
+               {
+                 dst_save_mul = 0xff;
+               }
+             else /* dst_alpha != 0) */
+               {
+                 dst_save_mul = 0xff0000 / dst_alpha;
+               }
+             
+             for (j = 0; j < 3; j++)
+               {
+                 art_u32 src, dst;
+                 art_u32 tmp;
+                 
+                 src = (bufptr[j] * src_mul + 0x8000) >> 16;
+                 dst = (dstptr[j] * dst_mul + 0x8000) >> 16;
+                 tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src;
+                 tmp -= tmp >> 16;
+                 dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16;
+               }
+             dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16;
+             
+             bufptr += 4;
+             dstptr += 4;
+           }
+       }
+    }
+}
+
+const ArtRenderCallback art_render_composite_8_opt2_obj =
+{
+  art_render_composite_8_opt2,
+  art_render_nop_done
+};
+
+
+/* todo: inline */
+static ArtRenderCallback *
+art_render_choose_compositing_callback (ArtRender *render)
+{
+  if (render->depth == 8 && render->buf_depth == 8)
+    {
+      if (render->n_chan == 3 &&
+         render->alpha_buf == NULL &&
+         render->alpha_type == ART_ALPHA_SEPARATE)
+       {
+         if (render->buf_alpha == ART_ALPHA_NONE)
+           return (ArtRenderCallback *)&art_render_composite_8_opt1_obj;
+         else if (render->buf_alpha == ART_ALPHA_PREMUL)
+           return (ArtRenderCallback *)&art_render_composite_8_opt2_obj;
+       }
+         
+      return (ArtRenderCallback *)&art_render_composite_8_obj;
+    }
+  return (ArtRenderCallback *)&art_render_composite_obj;
+}
+
+/**
+ * art_render_invoke_callbacks: Invoke the callbacks in the render object.
+ * @render: The render object.
+ * @y: The current Y coordinate value.
+ *
+ * Invokes the callbacks of the render object in the appropriate
+ * order.  Drivers should call this routine once per scanline.
+ *
+ * todo: should management of dest devolve to this routine? very
+ * plausibly yes.
+ **/
+void
+art_render_invoke_callbacks (ArtRender *render, art_u8 *dest, int y)
+{
+  ArtRenderPriv *priv = (ArtRenderPriv *)render;
+  int i;
+
+  for (i = 0; i < priv->n_callbacks; i++)
+    {
+      ArtRenderCallback *callback;
+
+      callback = priv->callbacks[i];
+      callback->render (callback, render, dest, y);
+    }
+}
+
+/**
+ * art_render_invoke: Perform the requested rendering task.
+ * @render: The render object.
+ *
+ * Invokes the renderer and all sources associated with it, to perform
+ * the requested rendering task.
+ **/
+void
+art_render_invoke (ArtRender *render)
+{
+  ArtRenderPriv *priv = (ArtRenderPriv *)render;
+  int width;
+  int best_driver, best_score;
+  int i;
+  int n_callbacks, n_callbacks_max;
+  ArtImageSource *image_source;
+  ArtImageSourceFlags image_flags;
+  int buf_depth;
+  ArtAlphaType buf_alpha;
+  art_boolean first = ART_TRUE;
+
+  if (render == NULL)
+    {
+      art_warn ("art_render_invoke: called with render == NULL\n");
+      return;
+    }
+  if (priv->image_source == NULL)
+    {
+      art_warn ("art_render_invoke: no image source given\n");
+      return;
+    }
+
+  width = render->x1 - render->x0;
+
+  render->run = art_new (ArtRenderMaskRun, width + 1);
+
+  /* Elect a mask source as driver. */
+  best_driver = -1;
+  best_score = 0;
+  for (i = 0; i < priv->n_mask_source; i++)
+    {
+      int score;
+      ArtMaskSource *mask_source;
+
+      mask_source = priv->mask_source[i];
+      score = mask_source->can_drive (mask_source, render);
+      if (score > best_score)
+       {
+         best_score = score;
+         best_driver = i;
+       }
+    }
+
+  /* Allocate alpha buffer if needed. */
+  if (priv->n_mask_source > 1 ||
+      (priv->n_mask_source == 1 && best_driver < 0))
+    {
+      render->alpha_buf = art_new (art_u8, (width * render->depth) >> 3);
+    }
+
+  /* Negotiate image rendering and compositing. */
+  image_source = priv->image_source;
+  image_source->negotiate (image_source, render, &image_flags, &buf_depth,
+                          &buf_alpha);
+
+  /* Build callback list. */
+  n_callbacks_max = priv->n_mask_source + 3;
+  priv->callbacks = art_new (ArtRenderCallback *, n_callbacks_max);
+  n_callbacks = 0;
+  for (i = 0; i < priv->n_mask_source; i++)
+    if (i != best_driver)
+      {
+       ArtMaskSource *mask_source = priv->mask_source[i];
+
+       mask_source->prepare (mask_source, render, first);
+       first = ART_FALSE;
+       priv->callbacks[n_callbacks++] = &mask_source->super;
+      }
+
+  if (render->clear && !(image_flags & ART_IMAGE_SOURCE_CAN_CLEAR))
+    priv->callbacks[n_callbacks++] =
+      art_render_choose_clear_callback (render);
+
+  priv->callbacks[n_callbacks++] = &image_source->super;
+
+  /* Allocate image buffer and add compositing callback if needed. */
+  if (!(image_flags & ART_IMAGE_SOURCE_CAN_COMPOSITE))
+    {
+      int bytespp = ((render->n_chan + (buf_alpha != ART_ALPHA_NONE)) *
+                    buf_depth) >> 3;
+      render->buf_depth = buf_depth;
+      render->buf_alpha = buf_alpha;
+      render->image_buf = art_new (art_u8, width * bytespp);
+      priv->callbacks[n_callbacks++] =
+       art_render_choose_compositing_callback (render);
+    }
+
+  priv->n_callbacks = n_callbacks;
+
+  if (render->need_span)
+    render->span_x = art_new (int, width + 1);
+
+  /* Invoke the driver */
+  if (best_driver >= 0)
+    {
+      ArtMaskSource *driver;
+
+      driver = priv->mask_source[best_driver];
+      driver->invoke_driver (driver, render);
+    }
+  else
+    {
+      art_u8 *dest_ptr = render->pixels;
+      int y;
+
+      /* Dummy driver */
+      render->n_run = 2;
+      render->run[0].x = render->x0;
+      render->run[0].alpha = 0x8000 + 0xff * render->opacity;
+      render->run[1].x = render->x1;
+      render->run[1].alpha = 0x8000;
+      if (render->need_span)
+       {
+         render->n_span = 2;
+         render->span_x[0] = render->x0;
+         render->span_x[1] = render->x1;
+       }
+      for (y = render->y0; y < render->y1; y++)
+       {
+         art_render_invoke_callbacks (render, dest_ptr, y);
+         dest_ptr += render->rowstride;
+       }
+    }
+
+  if (priv->mask_source != NULL)
+    art_free (priv->mask_source);
+
+  /* clean up callbacks */
+  for (i = 0; i < priv->n_callbacks; i++)
+    {
+      ArtRenderCallback *callback;
+
+      callback = priv->callbacks[i];
+      callback->done (callback, render);
+    }
+
+  /* Tear down object */
+  if (render->alpha_buf != NULL)
+    art_free (render->alpha_buf);
+  if (render->image_buf != NULL)
+    art_free (render->image_buf);
+  art_free (render->run);
+  if (render->span_x != NULL)
+    art_free (render->span_x);
+  art_free (priv->callbacks);
+  art_free (render);
+}
+
+/**
+ * art_render_mask_solid: Add a solid translucent mask.
+ * @render: The render object.
+ * @opacity: Opacity in [0..0x10000] form.
+ *
+ * Adds a translucent mask to the rendering object.
+ **/
+void
+art_render_mask_solid (ArtRender *render, int opacity)
+{
+  art_u32 old_opacity = render->opacity;
+  art_u32 new_opacity_tmp;
+
+  if (opacity == 0x10000)
+    /* avoid potential overflow */
+    return;
+  new_opacity_tmp = old_opacity * (art_u32)opacity + 0x8000;
+  render->opacity = new_opacity_tmp >> 16;
+}
+
+/**
+ * art_render_add_mask_source: Add a mask source to the render object.
+ * @render: Render object.
+ * @mask_source: Mask source to add.
+ *
+ * This routine adds a mask source to the render object. In general,
+ * client api's for adding mask sources should just take a render object,
+ * then the mask source creation function should call this function.
+ * Clients should never have to call this function directly, unless of
+ * course they're creating custom mask sources.
+ **/
+void
+art_render_add_mask_source (ArtRender *render, ArtMaskSource *mask_source)
+{
+  ArtRenderPriv *priv = (ArtRenderPriv *)render;
+  int n_mask_source = priv->n_mask_source++;
+
+  if (n_mask_source == 0)
+    priv->mask_source = art_new (ArtMaskSource *, 1);
+  /* This predicate is true iff n_mask_source is a power of two */
+  else if (!(n_mask_source & (n_mask_source - 1)))
+    priv->mask_source = art_renew (priv->mask_source, ArtMaskSource *,
+                                  n_mask_source << 1);
+
+  priv->mask_source[n_mask_source] = mask_source;
+}
+
+/**
+ * art_render_add_image_source: Add a mask source to the render object.
+ * @render: Render object.
+ * @image_source: Image source to add.
+ *
+ * This routine adds an image source to the render object. In general,
+ * client api's for adding image sources should just take a render
+ * object, then the mask source creation function should call this
+ * function.  Clients should never have to call this function
+ * directly, unless of course they're creating custom image sources.
+ **/
+void
+art_render_add_image_source (ArtRender *render, ArtImageSource *image_source)
+{
+  ArtRenderPriv *priv = (ArtRenderPriv *)render;
+
+  if (priv->image_source != NULL)
+    {
+      art_warn ("art_render_add_image_source: image source already present.\n");
+      return;
+    }
+  priv->image_source = image_source;
+}
+
+/* Solid image source object and methods. Perhaps this should go into a
+   separate file. */
+
+typedef struct _ArtImageSourceSolid ArtImageSourceSolid;
+
+struct _ArtImageSourceSolid {
+  ArtImageSource super;
+  ArtPixMaxDepth color[ART_MAX_CHAN];
+  art_u32 *rgbtab;
+  art_boolean init;
+};
+
+static void
+art_render_image_solid_done (ArtRenderCallback *self, ArtRender *render)
+{
+  ArtImageSourceSolid *z = (ArtImageSourceSolid *)self;
+
+  if (z->rgbtab != NULL)
+    art_free (z->rgbtab);
+  art_free (self);
+}
+
+static void
+art_render_image_solid_rgb8_opaq_init (ArtImageSourceSolid *self, ArtRender *render)
+{
+  ArtImageSourceSolid *z = (ArtImageSourceSolid *)self;
+  ArtPixMaxDepth color_max;
+  int r_fg, g_fg, b_fg;
+  int r_bg, g_bg, b_bg;
+  int r, g, b;
+  int dr, dg, db;
+  int i;
+  int tmp;
+  art_u32 *rgbtab;
+
+  rgbtab = art_new (art_u32, 256);
+  z->rgbtab = rgbtab;
+
+  color_max = self->color[0];
+  r_fg = ART_PIX_8_FROM_MAX (color_max);
+  color_max = self->color[1];
+  g_fg = ART_PIX_8_FROM_MAX (color_max);
+  color_max = self->color[2];
+  b_fg = ART_PIX_8_FROM_MAX (color_max);
+
+  color_max = render->clear_color[0];
+  r_bg = ART_PIX_8_FROM_MAX (color_max);
+  color_max = render->clear_color[1];
+  g_bg = ART_PIX_8_FROM_MAX (color_max);
+  color_max = render->clear_color[2];
+  b_bg = ART_PIX_8_FROM_MAX (color_max);
+
+  r = (r_bg << 16) + 0x8000;
+  g = (g_bg << 16) + 0x8000;
+  b = (b_bg << 16) + 0x8000;
+  tmp = ((r_fg - r_bg) << 16) + 0x80;
+  dr = (tmp + (tmp >> 8)) >> 8;
+  tmp = ((g_fg - g_bg) << 16) + 0x80;
+  dg = (tmp + (tmp >> 8)) >> 8;
+  tmp = ((b_fg - b_bg) << 16) + 0x80;
+  db = (tmp + (tmp >> 8)) >> 8;
+
+  for (i = 0; i < 256; i++)
+    {
+      rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16);
+      r += dr;
+      g += dg;
+      b += db;
+    }
+}
+
+static void
+art_render_image_solid_rgb8_opaq (ArtRenderCallback *self, ArtRender *render,
+                                 art_u8 *dest, int y)
+{
+  ArtImageSourceSolid *z = (ArtImageSourceSolid *)self;
+  ArtRenderMaskRun *run = render->run;
+  int n_run = render->n_run;
+  art_u32 *rgbtab = z->rgbtab;
+  art_u32 rgb;
+  int x0 = render->x0;
+  int x1 = render->x1;
+  int run_x0, run_x1;
+  int i;
+  int ix;
+
+  if (n_run > 0)
+    {
+      run_x1 = run[0].x;
+      if (run_x1 > x0)
+       {
+         rgb = rgbtab[0];
+         art_rgb_fill_run (dest,
+                           rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                           run_x1 - x0);
+       }
+      for (i = 0; i < n_run - 1; i++)
+       {
+         run_x0 = run_x1;
+         run_x1 = run[i + 1].x;
+         rgb = rgbtab[(run[i].alpha >> 16) & 0xff];
+         ix = (run_x0 - x0) * 3;
+#define OPTIMIZE_LEN_1
+#ifdef OPTIMIZE_LEN_1
+         if (run_x1 - run_x0 == 1)
+           {
+             dest[ix] = rgb >> 16;
+             dest[ix + 1] = (rgb >> 8) & 0xff;
+             dest[ix + 2] = rgb & 0xff;
+           }
+         else
+           {
+             art_rgb_fill_run (dest + ix,
+                               rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                               run_x1 - run_x0);
+           }
+#else
+         art_rgb_fill_run (dest + ix,
+                           rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                           run_x1 - run_x0);
+#endif
+       }
+    }
+  else
+    {
+      run_x1 = x0;
+    }
+  if (run_x1 < x1)
+    {
+      rgb = rgbtab[0];
+      art_rgb_fill_run (dest + (run_x1 - x0) * 3,
+                       rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                       x1 - run_x1);
+    }
+}
+
+static void
+art_render_image_solid_rgb8 (ArtRenderCallback *self, ArtRender *render,
+                            art_u8 *dest, int y)
+{
+  ArtImageSourceSolid *z = (ArtImageSourceSolid *)self;
+  int width = render->x1 - render->x0;
+  art_u8 r, g, b;
+  ArtPixMaxDepth color_max;
+
+  /* todo: replace this simple test with real sparseness */
+  if (z->init)
+    return;
+  z->init = ART_TRUE;
+
+  color_max = z->color[0];
+  r = ART_PIX_8_FROM_MAX (color_max);
+  color_max = z->color[1];
+  g = ART_PIX_8_FROM_MAX (color_max);
+  color_max = z->color[2];
+  b = ART_PIX_8_FROM_MAX (color_max);
+
+  art_rgb_fill_run (render->image_buf, r, g, b, width);
+}
+
+static void
+art_render_image_solid_negotiate (ArtImageSource *self, ArtRender *render,
+                                 ArtImageSourceFlags *p_flags,
+                                 int *p_buf_depth, ArtAlphaType *p_alpha)
+{
+  ArtImageSourceSolid *z = (ArtImageSourceSolid *)self;
+  ArtImageSourceFlags flags = 0;
+  static void (*render_cbk) (ArtRenderCallback *self, ArtRender *render,
+                            art_u8 *dest, int y);
+
+  render_cbk = NULL;
+
+  if (render->depth == 8 && render->n_chan == 3 &&
+      render->alpha_type == ART_ALPHA_NONE)
+    {
+      if (render->clear)
+       {
+         render_cbk = art_render_image_solid_rgb8_opaq;
+         flags |= ART_IMAGE_SOURCE_CAN_CLEAR | ART_IMAGE_SOURCE_CAN_COMPOSITE;
+         art_render_image_solid_rgb8_opaq_init (z, render);
+       }
+    }
+  if (render_cbk == NULL)
+    {
+      if (render->depth == 8)
+       {
+         render_cbk = art_render_image_solid_rgb8;
+         *p_buf_depth = 8;
+         *p_alpha = ART_ALPHA_NONE; /* todo */
+       }
+    }
+  /* todo: general case */
+  self->super.render = render_cbk;
+  *p_flags = flags;
+}
+
+/**
+ * art_render_image_solid: Add a solid color image source.
+ * @render: The render object.
+ * @color: Color.
+ *
+ * Adds an image source with the solid color given by @color. The
+ * color need not be retained in memory after this call.
+ **/
+void
+art_render_image_solid (ArtRender *render, ArtPixMaxDepth *color)
+{
+  ArtImageSourceSolid *image_source;
+  int i;
+
+  image_source = art_new (ArtImageSourceSolid, 1);
+  image_source->super.super.render = NULL;
+  image_source->super.super.done = art_render_image_solid_done;
+  image_source->super.negotiate = art_render_image_solid_negotiate;
+
+  for (i = 0; i < render->n_chan; i++)
+    image_source->color[i] = color[i];
+
+  image_source->rgbtab = NULL;
+  image_source->init = ART_FALSE;
+
+  art_render_add_image_source (render, &image_source->super);
+}
diff --git a/lib/art/art_render_gradient.c b/lib/art/art_render_gradient.c
new file mode 100644 (file)
index 0000000..e58fff0
--- /dev/null
@@ -0,0 +1,716 @@
+/*
+ * art_render_gradient.c: Gradient image source for modular rendering.
+ *
+ * Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2000 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.
+ *
+ * Authors: Raph Levien <raph@acm.org>
+ *          Alexander Larsson <alla@lysator.liu.se>
+ */
+
+#include "config.h"
+#include "art_render_gradient.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+/* Hack to find out how to define alloca on different platforms.
+ * Modified version of glib/galloca.h.
+ */
+
+#ifdef  __GNUC__
+/* GCC does the right thing */
+# undef alloca
+# define alloca(size)   __builtin_alloca (size)
+#elif defined (HAVE_ALLOCA_H)
+/* a native and working alloca.h is there */ 
+# include <alloca.h>
+#else /* !__GNUC__ && !HAVE_ALLOCA_H */
+# ifdef _MSC_VER
+#  include <malloc.h>
+#  define alloca _alloca
+# else /* !_MSC_VER */
+#  ifdef _AIX
+ #pragma alloca
+#  else /* !_AIX */
+#   ifndef alloca /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#   endif /* !alloca */
+#  endif /* !_AIX */
+# endif /* !_MSC_VER */
+#endif /* !__GNUC__ && !HAVE_ALLOCA_H */
+
+#undef DEBUG_SPEW
+
+typedef struct _ArtImageSourceGradLin ArtImageSourceGradLin;
+typedef struct _ArtImageSourceGradRad ArtImageSourceGradRad;
+
+/* The stops will be copied right after this structure */
+struct _ArtImageSourceGradLin {
+  ArtImageSource super;
+  ArtGradientLinear gradient;
+  ArtGradientStop stops[1];
+};
+
+/* The stops will be copied right after this structure */
+struct _ArtImageSourceGradRad {
+  ArtImageSource super;
+  ArtGradientRadial gradient;
+  double a;
+  ArtGradientStop stops[1];
+};
+
+#define EPSILON 1e-6
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+
+static void
+art_rgba_gradient_run (art_u8 *buf,
+                      art_u8 *color1,
+                      art_u8 *color2,
+                      int len)
+{
+  int i;
+  int r, g, b, a;
+  int dr, dg, db, da;
+
+#ifdef DEBUG_SPEW
+  printf ("gradient run from %3d %3d %3d %3d to %3d %3d %3d %3d in %d pixels\n",
+         color1[0], color1[1], color1[2], color1[3], 
+         color2[0], color2[1], color2[2], color2[3],
+         len);
+#endif
+  
+  r = (color1[0] << 16) + 0x8000;
+  g = (color1[1] << 16) + 0x8000;
+  b = (color1[2] << 16) + 0x8000;
+  a = (color1[3] << 16) + 0x8000;
+  dr = ((color2[0] - color1[0]) << 16) / len;
+  dg = ((color2[1] - color1[1]) << 16) / len;
+  db = ((color2[2] - color1[2]) << 16) / len;
+  da = ((color2[3] - color1[3]) << 16) / len;
+
+  for (i = 0; i < len; i++)
+    {
+      *buf++ = (r>>16);
+      *buf++ = (g>>16);
+      *buf++ = (b>>16);
+      *buf++ = (a>>16);
+
+      r += dr;
+      g += dg;
+      b += db;
+      a += da;
+    }
+}
+
+static void
+calc_color_at (ArtGradientStop *stops,
+              int n_stops,
+              ArtGradientSpread spread,
+              double offset,
+              double offset_fraction,
+              int favor_start,
+              int ix,
+              art_u8 *color)
+{
+  double off0, off1;
+  int j;
+  
+  if (spread == ART_GRADIENT_PAD)
+    {
+      if (offset < 0.0)
+       {
+         color[0] = ART_PIX_8_FROM_MAX (stops[0].color[0]);
+         color[1] = ART_PIX_8_FROM_MAX (stops[0].color[1]);
+         color[2] = ART_PIX_8_FROM_MAX (stops[0].color[2]);
+         color[3] = ART_PIX_8_FROM_MAX (stops[0].color[3]);
+         return;
+       }
+      if (offset >= 1.0)
+       {
+         color[0] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[0]);
+         color[1] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[1]);
+         color[2] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[2]);
+         color[3] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[3]);
+         return;
+       }
+    }
+
+  if (ix > 0 && ix < n_stops)
+    {
+      off0 = stops[ix - 1].offset;
+      off1 = stops[ix].offset;
+      if (fabs (off1 - off0) > EPSILON)
+       {
+         double interp;
+         double o;
+         o = offset_fraction;
+         
+         if ((fabs (o) < EPSILON) && (!favor_start))
+           o = 1.0;
+         else if ((fabs (o-1.0) < EPSILON) && (favor_start))
+           o = 0.0;
+         
+         /*
+         if (offset_fraction == 0.0  && !favor_start)
+           offset_fraction = 1.0;
+         */
+         
+         interp = (o - off0) / (off1 - off0);
+         for (j = 0; j < 4; j++)
+           {
+             int z0, z1;
+             int z;
+             z0 = stops[ix - 1].color[j];
+             z1 = stops[ix].color[j];
+             z = floor (z0 + (z1 - z0) * interp + 0.5);
+             color[j] = ART_PIX_8_FROM_MAX (z);
+           }
+         return;
+       }
+      /* If offsets are too close to safely do the division, just
+        pick the ix color. */
+      color[0] = ART_PIX_8_FROM_MAX (stops[ix].color[0]);
+      color[1] = ART_PIX_8_FROM_MAX (stops[ix].color[1]);
+      color[2] = ART_PIX_8_FROM_MAX (stops[ix].color[2]);
+      color[3] = ART_PIX_8_FROM_MAX (stops[ix].color[3]);
+      return;
+    }
+
+  printf ("WARNING! bad ix %d in calc_color_at() [internal error]\n", ix);
+  assert (0);
+}
+
+static void
+art_render_gradient_linear_render_8 (ArtRenderCallback *self,
+                                    ArtRender *render,
+                                    art_u8 *dest, int y)
+{
+  ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self;
+  const ArtGradientLinear *gradient = &(z->gradient);
+  int i;
+  int width = render->x1 - render->x0;
+  int len;
+  double offset, d_offset;
+  double offset_fraction;
+  int next_stop;
+  int ix;
+  art_u8 color1[4], color2[4];
+  int n_stops = gradient->n_stops;
+  int extra_stops;
+  ArtGradientStop *stops = gradient->stops;
+  ArtGradientStop *tmp_stops;
+  art_u8 *bufp = render->image_buf;
+  ArtGradientSpread spread = gradient->spread;
+
+#ifdef DEBUG_SPEW
+  printf ("x1: %d, x2: %d, y: %d\n", render->x0, render->x1, y);
+  printf ("spread: %d, stops:", gradient->spread);
+  for (i=0;i<n_stops;i++)
+    {
+      printf ("%f, ", gradient->stops[i].offset);
+    }
+  printf ("\n");
+  printf ("a: %f, b: %f, c: %f\n", gradient->a, gradient->b, gradient->c);
+#endif
+  
+  offset = render->x0 * gradient->a + y * gradient->b + gradient->c;
+  d_offset = gradient->a;
+
+  /* We need to force the gradient to extend the whole 0..1 segment,
+     because the rest of the code doesn't handle partial gradients
+     correctly */
+  if ((gradient->stops[0].offset > EPSILON /* == 0.0 */) ||
+      (gradient->stops[n_stops-1].offset < (1.0 - EPSILON)))
+  {
+    extra_stops = 0;
+    tmp_stops = stops = alloca (sizeof (ArtGradientStop) * (n_stops + 2));
+    if (gradient->stops[0].offset > EPSILON /* 0.0 */)
+      {
+       memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop));
+       tmp_stops[0].offset = 0.0;
+       tmp_stops += 1;
+       extra_stops++;
+      }
+    memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop) * n_stops);
+    if (gradient->stops[n_stops-1].offset < (1.0 - EPSILON))
+      {
+       tmp_stops += n_stops;
+       memcpy (tmp_stops, &gradient->stops[n_stops-1], sizeof (ArtGradientStop));
+       tmp_stops[0].offset = 1.0;
+       extra_stops++;
+      }
+    n_stops += extra_stops;
+
+
+#ifdef DEBUG_SPEW
+    printf ("start/stop modified stops:");
+    for (i=0;i<n_stops;i++)
+      {
+       printf ("%f, ", stops[i].offset);
+      }
+    printf ("\n");
+#endif
+
+  }
+  
+  if (spread == ART_GRADIENT_REFLECT)
+    {
+      tmp_stops = stops;
+      stops = alloca (sizeof (ArtGradientStop) * n_stops * 2);
+      memcpy (stops, tmp_stops, sizeof (ArtGradientStop) * n_stops);
+
+      for (i = 0; i< n_stops; i++)
+       {
+         stops[n_stops * 2 - 1 - i].offset = (1.0 - stops[i].offset / 2.0);
+         memcpy (stops[n_stops * 2 - 1 - i].color, stops[i].color, sizeof (stops[i].color));
+         stops[i].offset = stops[i].offset / 2.0;
+       }
+      
+      spread = ART_GRADIENT_REPEAT;
+      offset = offset / 2.0;
+      d_offset = d_offset / 2.0;
+      
+      n_stops = 2 * n_stops;
+
+#ifdef DEBUG_SPEW
+      printf ("reflect modified stops:");
+      for (i=0;i<n_stops;i++)
+       {
+         printf ("%f, ", stops[i].offset);
+       }
+      printf ("\n");
+#endif
+    }
+
+  offset_fraction = offset - floor (offset);
+#ifdef DEBUG_SPEW
+  printf ("inital offset: %f, fraction: %f d_offset: %f\n", offset, offset_fraction, d_offset);
+#endif
+  /* ix is selected so that offset_fraction is
+     stops[ix-1] <= offset_fraction <= stops[ix]
+     If offset_fraction is equal to one of the edges, ix
+     is selected so the the section of the line extending
+     in the same direction as d_offset is between ix-1 and ix.
+  */
+  for (ix = 0; ix < n_stops; ix++)
+    if (stops[ix].offset > offset_fraction ||
+       (d_offset < 0.0 && fabs (stops[ix].offset - offset_fraction) < EPSILON))
+      break;
+  if (ix == 0)
+    ix = n_stops - 1;
+  else if (ix == n_stops)
+    ix = n_stops - 1;
+
+#ifdef DEBUG_SPEW
+  printf ("Initial ix: %d\n", ix);
+#endif
+  
+  assert (ix > 0);
+  assert (ix < n_stops);
+  assert ((stops[ix-1].offset <= offset_fraction + EPSILON) ||
+         ((stops[ix].offset > (1.0 - EPSILON)) && (offset_fraction < EPSILON /* == 0.0*/)));
+  assert (offset_fraction <= stops[ix].offset);
+  /* FIXME: These asserts may be broken, it is for now
+     safer to not use them.  Should be fixed!
+     See bug #121850
+  assert ((offset_fraction != stops[ix-1].offset) ||
+         (d_offset >= 0.0));
+  assert ((offset_fraction != stops[ix].offset) ||
+         (d_offset <= 0.0));
+  */
+  
+  while (width > 0)
+    {
+#ifdef DEBUG_SPEW
+      printf ("ix: %d\n", ix);
+      printf ("start offset: %f\n", offset);
+#endif
+      calc_color_at (stops, n_stops,
+                    spread,
+                    offset,
+                    offset_fraction,
+                    (d_offset > -EPSILON),
+                    ix,
+                    color1);
+
+      if (d_offset > 0)
+       next_stop = ix;
+      else
+       next_stop = ix-1;
+
+#ifdef DEBUG_SPEW
+      printf ("next_stop: %d\n", next_stop);
+#endif
+      if (fabs (d_offset) > EPSILON)
+       {
+         double o;
+         o = offset_fraction;
+         
+         if ((fabs (o) <= EPSILON) && (ix == n_stops - 1))
+           o = 1.0;
+         else if ((fabs (o-1.0) <= EPSILON) && (ix == 1))
+           o = 0.0;
+
+#ifdef DEBUG_SPEW
+         printf ("o: %f\n", o);
+#endif
+         len = (int)floor (fabs ((stops[next_stop].offset - o) / d_offset)) + 1;
+         len = MAX (len, 0);
+         len = MIN (len, width);
+       }
+      else
+       {
+         len = width;
+       }
+#ifdef DEBUG_SPEW
+      printf ("len: %d\n", len);
+#endif
+      if (len > 0)
+       {
+         offset = offset + (len-1) * d_offset;
+         offset_fraction = offset - floor (offset);
+#ifdef DEBUG_SPEW
+         printf ("end offset: %f, fraction: %f\n", offset, offset_fraction);
+#endif   
+         calc_color_at (stops, n_stops,
+                        spread,
+                        offset,
+                        offset_fraction,
+                        (d_offset < EPSILON),
+                        ix,
+                        color2);
+         
+         art_rgba_gradient_run (bufp,
+                                color1,
+                                color2,
+                                len);
+         offset += d_offset;
+         offset_fraction = offset - floor (offset);
+       }
+
+      if (d_offset > 0)
+       {
+         do
+           {
+             ix++;
+             if (ix == n_stops)
+               ix = 1;
+             /* Note: offset_fraction can actually be one here on x86 machines that
+                does calculations with extended precision, but later rounds to 64bit.
+                This happens if the 80bit offset_fraction is larger than the
+                largest 64bit double that is less than one.
+             */
+           }
+         while (!((stops[ix-1].offset <= offset_fraction &&
+                  offset_fraction < stops[ix].offset) ||
+                  (ix == 1 && offset_fraction > (1.0 - EPSILON)))); 
+       }
+      else
+       {
+         do
+           {
+             ix--;
+             if (ix == 0)
+               ix = n_stops - 1;
+           }
+         while (!((stops[ix-1].offset < offset_fraction &&
+                   offset_fraction <= stops[ix].offset) ||
+                  (ix == n_stops - 1 && offset_fraction < EPSILON /* == 0.0*/)));
+       }
+      
+      bufp += 4*len;
+      width -= len;
+    }
+}
+
+
+/**
+ * art_render_gradient_setpix: Set a gradient pixel.
+ * @render: The render object.
+ * @dst: Pointer to destination (where to store pixel).
+ * @n_stops: Number of stops in @stops.
+ * @stops: The stops for the gradient.
+ * @offset: The offset.
+ *
+ * @n_stops must be > 0.
+ *
+ * Sets a gradient pixel, storing it at @dst.
+ **/
+static void
+art_render_gradient_setpix (ArtRender *render,
+                           art_u8 *dst,
+                           int n_stops, ArtGradientStop *stops,
+                           double offset)
+{
+  int ix;
+  int j;
+  double off0, off1;
+  int n_ch = render->n_chan + 1;
+
+  for (ix = 0; ix < n_stops; ix++)
+    if (stops[ix].offset > offset)
+      break;
+  /* stops[ix - 1].offset < offset < stops[ix].offset */
+  if (ix > 0 && ix < n_stops)
+    {
+      off0 = stops[ix - 1].offset;
+      off1 = stops[ix].offset;
+      if (fabs (off1 - off0) > EPSILON)
+       {
+         double interp;
+
+         interp = (offset - off0) / (off1 - off0);
+         for (j = 0; j < n_ch; j++)
+           {
+             int z0, z1;
+             int z;
+             z0 = stops[ix - 1].color[j];
+             z1 = stops[ix].color[j];
+             z = floor (z0 + (z1 - z0) * interp + 0.5);
+             if (render->buf_depth == 8)
+               dst[j] = ART_PIX_8_FROM_MAX (z);
+             else /* (render->buf_depth == 16) */
+               ((art_u16 *)dst)[j] = z;
+           }
+         return;
+       }
+    }
+  else if (ix == n_stops)
+    ix--;
+
+  for (j = 0; j < n_ch; j++)
+    {
+      int z;
+      z = stops[ix].color[j];
+      if (render->buf_depth == 8)
+       dst[j] = ART_PIX_8_FROM_MAX (z);
+      else /* (render->buf_depth == 16) */
+       ((art_u16 *)dst)[j] = z;
+    }
+}
+
+static void
+art_render_gradient_linear_done (ArtRenderCallback *self, ArtRender *render)
+{
+  art_free (self);
+}
+
+static void
+art_render_gradient_linear_render (ArtRenderCallback *self, ArtRender *render,
+                                  art_u8 *dest, int y)
+{
+  ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self;
+  const ArtGradientLinear *gradient = &(z->gradient);
+  int pixstride = (render->n_chan + 1) * (render->depth >> 3);
+  int x;
+  int width = render->x1 - render->x0;
+  double offset, d_offset;
+  double actual_offset;
+  int n_stops = gradient->n_stops;
+  ArtGradientStop *stops = gradient->stops;
+  art_u8 *bufp = render->image_buf;
+  ArtGradientSpread spread = gradient->spread;
+
+  offset = render->x0 * gradient->a + y * gradient->b + gradient->c;
+  d_offset = gradient->a;
+
+  for (x = 0; x < width; x++)
+    {
+      if (spread == ART_GRADIENT_PAD)
+       actual_offset = offset;
+      else if (spread == ART_GRADIENT_REPEAT)
+       actual_offset = offset - floor (offset);
+      else /* (spread == ART_GRADIENT_REFLECT) */
+       {
+         double tmp;
+
+         tmp = offset - 2 * floor (0.5 * offset);
+         actual_offset = tmp > 1 ? 2 - tmp : tmp;
+       }
+      art_render_gradient_setpix (render, bufp, n_stops, stops, actual_offset);
+      offset += d_offset;
+      bufp += pixstride;
+    }
+}
+
+static void
+art_render_gradient_linear_negotiate (ArtImageSource *self, ArtRender *render,
+                                     ArtImageSourceFlags *p_flags,
+                                     int *p_buf_depth, ArtAlphaType *p_alpha)
+{
+  if (render->depth == 8 &&
+      render->n_chan == 3)
+    {
+      self->super.render = art_render_gradient_linear_render_8;
+      *p_flags = 0;
+      *p_buf_depth = 8;
+      *p_alpha = ART_ALPHA_PREMUL;
+      return;
+    }
+  
+  self->super.render = art_render_gradient_linear_render;
+  *p_flags = 0;
+  *p_buf_depth = render->depth;
+  *p_alpha = ART_ALPHA_PREMUL;
+}
+
+/**
+ * art_render_gradient_linear: Add a linear gradient image source.
+ * @render: The render object.
+ * @gradient: The linear gradient.
+ *
+ * Adds the linear gradient @gradient as the image source for rendering
+ * in the render object @render.
+ **/
+void
+art_render_gradient_linear (ArtRender *render,
+                           const ArtGradientLinear *gradient,
+                           ArtFilterLevel level)
+{
+  ArtImageSourceGradLin *image_source = art_alloc (sizeof (ArtImageSourceGradLin) +
+                                                  sizeof (ArtGradientStop) * (gradient->n_stops - 1));
+
+  image_source->super.super.render = NULL;
+  image_source->super.super.done = art_render_gradient_linear_done;
+  image_source->super.negotiate = art_render_gradient_linear_negotiate;
+
+  /* copy the gradient into the structure */
+  image_source->gradient = *gradient;
+  image_source->gradient.stops = image_source->stops;
+  memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops);
+
+  art_render_add_image_source (render, &image_source->super);
+}
+
+static void
+art_render_gradient_radial_done (ArtRenderCallback *self, ArtRender *render)
+{
+  art_free (self);
+}
+
+static void
+art_render_gradient_radial_render (ArtRenderCallback *self, ArtRender *render,
+                                  art_u8 *dest, int y)
+{
+  ArtImageSourceGradRad *z = (ArtImageSourceGradRad *)self;
+  const ArtGradientRadial *gradient = &(z->gradient);
+  int pixstride = (render->n_chan + 1) * (render->depth >> 3);
+  int x;
+  int x0 = render->x0;
+  int width = render->x1 - x0;
+  int n_stops = gradient->n_stops;
+  ArtGradientStop *stops = gradient->stops;
+  art_u8 *bufp = render->image_buf;
+  double fx = gradient->fx;
+  double fy = gradient->fy;
+  double dx, dy;
+  double *affine = gradient->affine;
+  double aff0 = affine[0];
+  double aff1 = affine[1];
+  const double a = z->a;
+  const double arecip = 1.0 / a;
+  double b, db;
+  double c, dc, ddc;
+  double b_a, db_a;
+  double rad, drad, ddrad;
+
+  dx = x0 * aff0 + y * affine[2] + affine[4] - fx;
+  dy = x0 * aff1 + y * affine[3] + affine[5] - fy;
+  b = dx * fx + dy * fy;
+  db = aff0 * fx + aff1 * fy;
+  c = dx * dx + dy * dy;
+  dc = 2 * aff0 * dx + aff0 * aff0 + 2 * aff1 * dy + aff1 * aff1;
+  ddc = 2 * aff0 * aff0 + 2 * aff1 * aff1;
+
+  b_a = b * arecip;
+  db_a = db * arecip;
+
+  rad = b_a * b_a + c * arecip;
+  drad = 2 * b_a * db_a + db_a * db_a + dc * arecip;
+  ddrad = 2 * db_a * db_a + ddc * arecip;
+
+  for (x = 0; x < width; x++)
+    {
+      double z;
+
+      if (rad > 0)
+       z = b_a + sqrt (rad);
+      else
+       z = b_a;
+      art_render_gradient_setpix (render, bufp, n_stops, stops, z);
+      bufp += pixstride;
+      b_a += db_a;
+      rad += drad;
+      drad += ddrad;
+    }
+}
+
+static void
+art_render_gradient_radial_negotiate (ArtImageSource *self, ArtRender *render,
+                                     ArtImageSourceFlags *p_flags,
+                                     int *p_buf_depth, ArtAlphaType *p_alpha)
+{
+  self->super.render = art_render_gradient_radial_render;
+  *p_flags = 0;
+  *p_buf_depth = render->depth;
+  *p_alpha = ART_ALPHA_PREMUL;
+}
+
+/**
+ * art_render_gradient_radial: Add a radial gradient image source.
+ * @render: The render object.
+ * @gradient: The radial gradient.
+ *
+ * Adds the radial gradient @gradient as the image source for rendering
+ * in the render object @render.
+ **/
+void
+art_render_gradient_radial (ArtRender *render,
+                           const ArtGradientRadial *gradient,
+                           ArtFilterLevel level)
+{
+  ArtImageSourceGradRad *image_source = art_alloc (sizeof (ArtImageSourceGradRad) +
+                                                  sizeof (ArtGradientStop) * (gradient->n_stops - 1));
+  double fx = gradient->fx;
+  double fy = gradient->fy;
+
+  image_source->super.super.render = NULL;
+  image_source->super.super.done = art_render_gradient_radial_done;
+  image_source->super.negotiate = art_render_gradient_radial_negotiate;
+
+  /* copy the gradient into the structure */
+  image_source->gradient = *gradient;
+  image_source->gradient.stops = image_source->stops;
+  memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops);
+
+  /* todo: sanitycheck fx, fy? */
+  image_source->a = 1 - fx * fx - fy * fy;
+
+  art_render_add_image_source (render, &image_source->super);
+}
diff --git a/lib/art/art_render_mask.c b/lib/art/art_render_mask.c
new file mode 100644 (file)
index 0000000..ce82608
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * art_render_mask.c: Alpha mask source for modular rendering.
+ *
+ * Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2000 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.
+ *
+ * Authors: Raph Levien <raph@acm.org>
+ */
+
+#include "config.h"
+#include "art_render_mask.h"
+
+#include <string.h>
+
+
+typedef struct _ArtMaskSourceMask ArtMaskSourceMask;
+
+struct _ArtMaskSourceMask {
+  ArtMaskSource super;
+  ArtRender *render;
+  art_boolean first;
+  int x0;
+  int y0;
+  int x1;
+  int y1;
+  const art_u8 *mask_buf;
+  int rowstride;
+};
+
+static void
+art_render_mask_done (ArtRenderCallback *self, ArtRender *render)
+{
+  art_free (self);
+}
+
+static int
+art_render_mask_can_drive (ArtMaskSource *self, ArtRender *render)
+{
+  return 0;
+}
+
+static void
+art_render_mask_render (ArtRenderCallback *self, ArtRender *render,
+                       art_u8 *dest, int y)
+{
+  ArtMaskSourceMask *z = (ArtMaskSourceMask *)self;
+  int x0 = render->x0, x1 = render->x1;
+  int z_x0 = z->x0, z_x1 = z->x1;
+  int width = x1 - x0;
+  int z_width = z_x1 - z_x0;
+  art_u8 *alpha_buf = render->alpha_buf;
+
+  if (y < z->y0 || y >= z->y1 || z_width <= 0)
+    memset (alpha_buf, 0, width);
+  else
+    {
+      const art_u8 *src_line = z->mask_buf + (y - z->y0) * z->rowstride;
+      art_u8 *dst_line = alpha_buf + z_x0 - x0;
+
+      if (z_x0 > x0)
+       memset (alpha_buf, 0, z_x0 - x0);
+      
+      if (z->first)
+       memcpy (dst_line, src_line, z_width);
+      else
+       {
+         int x;
+         
+         for (x = 0; x < z_width; x++)
+           {
+             int v;
+             v = src_line[x];
+             if (v)
+               {
+                 v = v * dst_line[x] + 0x80;
+                 v = (v + (v >> 8)) >> 8;
+                 dst_line[x] = v;
+               }
+             else
+               {
+                 dst_line[x] = 0;
+               }
+           }
+       }
+      
+      if (z_x1 < x1)
+       memset (alpha_buf + z_x1 - x0, 0, x1 - z_x1);
+    }
+}
+
+static void
+art_render_mask_prepare (ArtMaskSource *self, ArtRender *render,
+                        art_boolean first)
+{
+  ArtMaskSourceMask *z = (ArtMaskSourceMask *)self;
+  self->super.render = art_render_mask_render;
+  z->first = first;
+}
+
+/**
+ * art_render_mask: Use an alpha buffer as a render mask source.
+ * @render: Render object.
+ * @x0: Left coordinate of mask rect.
+ * @y0: Top coordinate of mask rect.
+ * @x1: Right coordinate of mask rect.
+ * @y1: Bottom coordinate of mask rect.
+ * @mask_buf: Buffer containing 8bpp alpha mask data.
+ * @rowstride: Rowstride of @mask_buf.
+ *
+ * Adds @mask_buf to the render object as a mask. Note: @mask_buf must
+ * remain allocated until art_render_invoke() is called on @render.
+ **/
+void
+art_render_mask (ArtRender *render,
+                int x0, int y0, int x1, int y1,
+                const art_u8 *mask_buf, int rowstride)
+{
+  ArtMaskSourceMask *mask_source;
+
+  if (x0 < render->x0)
+    {
+      mask_buf += render->x0 - x0;
+      x0 = render->x0;
+    }
+  if (x1 > render->x1)
+    x1 = render->x1;
+
+  if (y0 < render->y0)
+    {
+      mask_buf += (render->y0 - y0) * rowstride;
+      y0 = render->y0;
+    }
+  if (y1 > render->y1)
+    y1 = render->y1;
+
+  mask_source = art_new (ArtMaskSourceMask, 1);
+
+  mask_source->super.super.render = NULL;
+  mask_source->super.super.done = art_render_mask_done;
+  mask_source->super.can_drive = art_render_mask_can_drive;
+  mask_source->super.invoke_driver = NULL;
+  mask_source->super.prepare = art_render_mask_prepare;
+  mask_source->render = render;
+  mask_source->x0 = x0;
+  mask_source->y0 = y0;
+  mask_source->x1 = x1;
+  mask_source->y1 = y1;
+  mask_source->mask_buf = mask_buf;
+  mask_source->rowstride = rowstride;
+
+  art_render_add_mask_source (render, &mask_source->super);
+
+}
diff --git a/lib/art/art_render_svp.c b/lib/art/art_render_svp.c
new file mode 100644 (file)
index 0000000..895e3cc
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * art_render_gradient.c: SVP mask source for modular rendering.
+ *
+ * Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2000 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.
+ *
+ * Authors: Raph Levien <raph@acm.org>
+ */
+
+#include "art_render_svp.h"
+#include "art_svp_render_aa.h"
+
+typedef struct _ArtMaskSourceSVP ArtMaskSourceSVP;
+
+struct _ArtMaskSourceSVP {
+  ArtMaskSource super;
+  ArtRender *render;
+  const ArtSVP *svp;
+  art_u8 *dest_ptr;
+};
+
+static void
+art_render_svp_done (ArtRenderCallback *self, ArtRender *render)
+{
+  art_free (self);
+}
+
+static int
+art_render_svp_can_drive (ArtMaskSource *self, ArtRender *render)
+{
+  return 10;
+}
+
+/* The basic art_render_svp_callback function is repeated four times,
+   for all combinations of non-unit opacity and generating spans. In
+   general, I'd consider this bad style, but in this case I plead
+   a measurable performance improvement. */
+
+static void
+art_render_svp_callback (void *callback_data, int y,
+                        int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data;
+  ArtRender *render = z->render;
+  int n_run = 0;
+  int i;
+  int running_sum = start;
+  int x0 = render->x0;
+  int x1 = render->x1;
+  int run_x0, run_x1;
+  ArtRenderMaskRun *run = render->run;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0 && running_sum > 0x80ff)
+       {
+         run[0].x = x0;
+         run[0].alpha = running_sum;
+         n_run++;
+       }
+
+      for (i = 0; i < n_steps - 1; i++)
+       {
+          running_sum += steps[i].delta;
+          run_x0 = run_x1;
+          run_x1 = steps[i + 1].x;
+         if (run_x1 > run_x0)
+           {
+             run[n_run].x = run_x0;
+             run[n_run].alpha = running_sum;
+             n_run++;
+           }
+       }
+      if (x1 > run_x1)
+       {
+         running_sum += steps[n_steps - 1].delta;
+         run[n_run].x = run_x1;
+         run[n_run].alpha = running_sum;
+         n_run++;
+       }
+      if (running_sum > 0x80ff)
+       {
+         run[n_run].x = x1;
+         run[n_run].alpha = 0x8000;
+         n_run++;
+       }
+    }
+  else if ((running_sum >> 16) > 0)
+    {
+      run[0].x = x0;
+      run[0].alpha = running_sum;
+      run[1].x = x1;
+      run[1].alpha = running_sum;
+      n_run = 2;
+    }
+
+  render->n_run = n_run;
+
+  art_render_invoke_callbacks (render, z->dest_ptr, y);
+
+  z->dest_ptr += render->rowstride;
+}
+
+static void
+art_render_svp_callback_span (void *callback_data, int y,
+                             int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data;
+  ArtRender *render = z->render;
+  int n_run = 0;
+  int n_span = 0;
+  int i;
+  int running_sum = start;
+  int x0 = render->x0;
+  int x1 = render->x1;
+  int run_x0, run_x1;
+  ArtRenderMaskRun *run = render->run;
+  int *span_x = render->span_x;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0 && running_sum > 0x80ff)
+       {
+         run[0].x = x0;
+         run[0].alpha = running_sum;
+         n_run++;
+         span_x[0] = x0;
+         n_span++;
+       }
+
+      for (i = 0; i < n_steps - 1; i++)
+       {
+          running_sum += steps[i].delta;
+          run_x0 = run_x1;
+          run_x1 = steps[i + 1].x;
+         if (run_x1 > run_x0)
+           {
+             run[n_run].x = run_x0;
+             run[n_run].alpha = running_sum;
+             n_run++;
+             if ((n_span & 1) != (running_sum > 0x80ff))
+               span_x[n_span++] = run_x0;
+           }
+       }
+      if (x1 > run_x1)
+       {
+         running_sum += steps[n_steps - 1].delta;
+         run[n_run].x = run_x1;
+         run[n_run].alpha = running_sum;
+         n_run++;
+         if ((n_span & 1) != (running_sum > 0x80ff))
+           span_x[n_span++] = run_x1;
+       }
+      if (running_sum > 0x80ff)
+       {
+         run[n_run].x = x1;
+         run[n_run].alpha = 0x8000;
+         n_run++;
+         span_x[n_span++] = x1;
+       }
+    }
+  else if ((running_sum >> 16) > 0)
+    {
+      run[0].x = x0;
+      run[0].alpha = running_sum;
+      run[1].x = x1;
+      run[1].alpha = running_sum;
+      n_run = 2;
+      span_x[0] = x0;
+      span_x[1] = x1;
+      n_span = 2;
+    }
+
+  render->n_run = n_run;
+  render->n_span = n_span;
+
+  art_render_invoke_callbacks (render, z->dest_ptr, y);
+
+  z->dest_ptr += render->rowstride;
+}
+
+static void
+art_render_svp_callback_opacity (void *callback_data, int y,
+                                int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data;
+  ArtRender *render = z->render;
+  int n_run = 0;
+  int i;
+  art_u32 running_sum;
+  int x0 = render->x0;
+  int x1 = render->x1;
+  int run_x0, run_x1;
+  ArtRenderMaskRun *run = render->run;
+  art_u32 opacity = render->opacity;
+  art_u32 alpha;
+
+  running_sum = start - 0x7f80;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8;
+      if (run_x1 > x0 && alpha > 0x80ff)
+       {
+         run[0].x = x0;
+         run[0].alpha = alpha;
+         n_run++;
+       }
+
+      for (i = 0; i < n_steps - 1; i++)
+       {
+          running_sum += steps[i].delta;
+          run_x0 = run_x1;
+          run_x1 = steps[i + 1].x;
+         if (run_x1 > run_x0)
+           {
+             run[n_run].x = run_x0;
+             alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8;
+             run[n_run].alpha = alpha;
+             n_run++;
+           }
+       }
+      if (x1 > run_x1)
+       {
+         running_sum += steps[n_steps - 1].delta;
+         run[n_run].x = run_x1;
+         alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8;
+         run[n_run].alpha = alpha;
+         n_run++;
+       }
+      if (alpha > 0x80ff)
+       {
+         run[n_run].x = x1;
+         run[n_run].alpha = 0x8000;
+         n_run++;
+       }
+    }
+  else if ((running_sum >> 16) > 0)
+    {
+      run[0].x = x0;
+      run[0].alpha = running_sum;
+      run[1].x = x1;
+      run[1].alpha = running_sum;
+      n_run = 2;
+    }
+
+  render->n_run = n_run;
+
+  art_render_invoke_callbacks (render, z->dest_ptr, y);
+
+  z->dest_ptr += render->rowstride;
+}
+
+static void
+art_render_svp_callback_opacity_span (void *callback_data, int y,
+                                     int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data;
+  ArtRender *render = z->render;
+  int n_run = 0;
+  int n_span = 0;
+  int i;
+  art_u32 running_sum;
+  int x0 = render->x0;
+  int x1 = render->x1;
+  int run_x0, run_x1;
+  ArtRenderMaskRun *run = render->run;
+  int *span_x = render->span_x;
+  art_u32 opacity = render->opacity;
+  art_u32 alpha;
+
+  running_sum = start - 0x7f80;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8;
+      if (run_x1 > x0 && alpha > 0x80ff)
+       {
+         run[0].x = x0;
+         run[0].alpha = alpha;
+         n_run++;
+         span_x[0] = x0;
+         n_span++;
+       }
+
+      for (i = 0; i < n_steps - 1; i++)
+       {
+          running_sum += steps[i].delta;
+          run_x0 = run_x1;
+          run_x1 = steps[i + 1].x;
+         if (run_x1 > run_x0)
+           {
+             run[n_run].x = run_x0;
+             alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8;
+             run[n_run].alpha = alpha;
+             n_run++;
+             if ((n_span & 1) != (alpha > 0x80ff))
+               span_x[n_span++] = run_x0;
+           }
+       }
+      if (x1 > run_x1)
+       {
+         running_sum += steps[n_steps - 1].delta;
+         run[n_run].x = run_x1;
+         alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8;
+         run[n_run].alpha = alpha;
+         n_run++;
+         if ((n_span & 1) != (alpha > 0x80ff))
+           span_x[n_span++] = run_x1;
+       }
+      if (alpha > 0x80ff)
+       {
+         run[n_run].x = x1;
+         run[n_run].alpha = 0x8000;
+         n_run++;
+         span_x[n_span++] = x1;
+       }
+    }
+  else if ((running_sum >> 16) > 0)
+    {
+      run[0].x = x0;
+      run[0].alpha = running_sum;
+      run[1].x = x1;
+      run[1].alpha = running_sum;
+      n_run = 2;
+      span_x[0] = x0;
+      span_x[1] = x1;
+      n_span = 2;
+    }
+
+  render->n_run = n_run;
+  render->n_span = n_span;
+
+  art_render_invoke_callbacks (render, z->dest_ptr, y);
+
+  z->dest_ptr += render->rowstride;
+}
+
+static void
+art_render_svp_invoke_driver (ArtMaskSource *self, ArtRender *render)
+{
+  ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)self;
+  void (*callback) (void *callback_data,
+                   int y,
+                   int start,
+                   ArtSVPRenderAAStep *steps, int n_steps);
+
+  z->dest_ptr = render->pixels;
+  if (render->opacity == 0x10000)
+    {
+      if (render->need_span)
+       callback = art_render_svp_callback_span;
+      else
+       callback = art_render_svp_callback;
+    }
+  else
+    {
+      if (render->need_span)
+       callback = art_render_svp_callback_opacity_span;
+      else
+       callback = art_render_svp_callback_opacity;
+    }
+
+  art_svp_render_aa (z->svp,
+                    render->x0, render->y0,
+                    render->x1, render->y1, callback,
+                    self);
+  art_render_svp_done (&self->super, render);
+}
+
+static void
+art_render_svp_prepare (ArtMaskSource *self, ArtRender *render,
+                       art_boolean first)
+{
+  /* todo */
+  art_die ("art_render_svp non-driver mode not yet implemented.\n");
+}
+
+/**
+ * art_render_svp: Use an SVP as a render mask source.
+ * @render: Render object.
+ * @svp: SVP.
+ *
+ * Adds @svp to the render object as a mask. Note: @svp must remain
+ * allocated until art_render_invoke() is called on @render.
+ **/
+void
+art_render_svp (ArtRender *render, const ArtSVP *svp)
+{
+  ArtMaskSourceSVP *mask_source;
+  mask_source = art_new (ArtMaskSourceSVP, 1);
+
+  mask_source->super.super.render = NULL;
+  mask_source->super.super.done = art_render_svp_done;
+  mask_source->super.can_drive = art_render_svp_can_drive;
+  mask_source->super.invoke_driver = art_render_svp_invoke_driver;
+  mask_source->super.prepare = art_render_svp_prepare;
+  mask_source->render = render;
+  mask_source->svp = svp;
+
+  art_render_add_mask_source (render, &mask_source->super);
+}
diff --git a/lib/art/art_rgb.c b/lib/art/art_rgb.c
new file mode 100644 (file)
index 0000000..05bfa02
--- /dev/null
@@ -0,0 +1,175 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb.h"
+
+#include <string.h>    /* for memset */
+
+/* Basic operators for manipulating 24-bit packed RGB buffers. */
+
+#define COLOR_RUN_COMPLEX
+
+#ifdef COLOR_RUN_SIMPLE
+/* This is really slow. Is there any way we might speed it up?
+   Two ideas:
+
+   First, maybe we should be working at 32-bit alignment. Then,
+   this can be a simple loop over word stores.
+
+   Second, we can keep working at 24-bit alignment, but have some
+   intelligence about storing. For example, we can iterate over
+   4-pixel chunks (aligned at 4 pixels), with an inner loop
+   something like:
+
+   *buf++ = v1;
+   *buf++ = v2;
+   *buf++ = v3;
+
+   One source of extra complexity is the need to make sure linebuf is
+   aligned to a 32-bit boundary.
+
+   This second alternative has some complexity to it, but is
+   appealing because it really minimizes the memory bandwidth. */
+void
+art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, gint n)
+{
+  int i;
+
+  if (r == g && g == b)
+    {
+      memset (buf, g, n + n + n);
+    }
+  else
+    {
+      for (i = 0; i < n; i++)
+       {
+         *buf++ = r;
+         *buf++ = g;
+         *buf++ = b;
+       }
+    }
+}
+#endif
+
+#ifdef COLOR_RUN_COMPLEX
+/* This implements the second of the two ideas above. The test results
+   are _very_ encouraging - it seems the speed is within 10% of
+   memset, which is quite good! */
+/**
+ * art_rgb_fill_run: fill a buffer a solid RGB color.
+ * @buf: Buffer to fill.
+ * @r: Red, range 0..255.
+ * @g: Green, range 0..255.
+ * @b: Blue, range 0..255.
+ * @n: Number of RGB triples to fill.
+ *
+ * Fills a buffer with @n copies of the (@r, @g, @b) triple. Thus,
+ * locations @buf (inclusive) through @buf + 3 * @n (exclusive) are
+ * written.
+ *
+ * The implementation of this routine is very highly optimized.
+ **/
+void
+art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n)
+{
+  int i;
+  unsigned int v1, v2, v3;
+
+  if (r == g && g == b)
+    {
+      memset (buf, g, n + n + n);
+    }
+  else
+    {
+      if (n < 8)
+       {
+         for (i = 0; i < n; i++)
+           {
+             *buf++ = r;
+             *buf++ = g;
+             *buf++ = b;
+           }
+       } else {
+         /* handle prefix up to byte alignment */
+         /* I'm worried about this cast on sizeof(long) != sizeof(uchar *)
+            architectures, but it _should_ work. */
+         for (i = 0; ((unsigned long)buf) & 3; i++)
+           {
+             *buf++ = r;
+             *buf++ = g;
+             *buf++ = b;
+           }
+#ifndef WORDS_BIGENDIAN
+         v1 = r | (g << 8) | (b << 16) | (r << 24);
+         v3 = (v1 << 8) | b;
+         v2 = (v3 << 8) | g;
+#else
+         v1 = (r << 24) | (g << 16) | (b << 8) | r;
+         v2 = (v1 << 8) | g;
+         v3 = (v2 << 8) | b;
+#endif
+         for (; i < n - 3; i += 4)
+           {
+             ((art_u32 *)buf)[0] = v1;
+             ((art_u32 *)buf)[1] = v2;
+             ((art_u32 *)buf)[2] = v3;
+             buf += 12;
+           }
+         /* handle postfix */
+         for (; i < n; i++)
+           {
+             *buf++ = r;
+             *buf++ = g;
+             *buf++ = b;
+           }
+       }
+    }
+}
+#endif
+
+/**
+ * art_rgb_run_alpha: Render semitransparent color over RGB buffer.
+ * @buf: Buffer for rendering.
+ * @r: Red, range 0..255.
+ * @g: Green, range 0..255.
+ * @b: Blue, range 0..255.
+ * @alpha: Alpha, range 0..256.
+ * @n: Number of RGB triples to render.
+ *
+ * Renders a sequential run of solid (@r, @g, @b) color over @buf with
+ * opacity @alpha.
+ **/
+void
+art_rgb_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n)
+{
+  int i;
+  int v;
+
+  for (i = 0; i < n; i++)
+    {
+      v = *buf;
+      *buf++ = v + (((r - v) * alpha + 0x80) >> 8);
+      v = *buf;
+      *buf++ = v + (((g - v) * alpha + 0x80) >> 8);
+      v = *buf;
+      *buf++ = v + (((b - v) * alpha + 0x80) >> 8);
+    }
+}
+
diff --git a/lib/art/art_rgb_a_affine.c b/lib/art/art_rgb_a_affine.c
new file mode 100644 (file)
index 0000000..ff38e2a
--- /dev/null
@@ -0,0 +1,149 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_a_affine.h"
+
+#include <math.h>
+
+#include "art_affine.h"
+#include "art_point.h"
+#include "art_rgb_affine_private.h"
+
+/* This module handles compositing of affine-transformed alpha only images
+   over rgb pixel buffers. */
+
+/* Composite the source image over the destination image, applying the
+   affine transform. */
+
+/**
+ * art_rgb_a_affine: Affine transform source Alpha image and composite.
+ * @dst: Destination image RGB buffer.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @dst_rowstride: Rowstride of @dst buffer.
+ * @src: Source image alpha buffer.
+ * @src_width: Width of source image.
+ * @src_height: Height of source image.
+ * @src_rowstride: Rowstride of @src buffer.
+ * @rgb: RGB foreground color, in 0xRRGGBB.
+ * @affine: Affine transform.
+ * @level: Filter level.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Affine transform the solid color rgb with alpha specified by the
+ * source image stored in @src, compositing over the area of destination
+ * image @dst specified by the rectangle (@x0, @y0) - (@x1, @y1).
+ * As usual in libart, the left and top edges of this rectangle are
+ * included, and the right and bottom edges are excluded.
+ *
+ * The @alphagamma parameter specifies that the alpha compositing be
+ * done in a gamma-corrected color space. In the current
+ * implementation, it is ignored.
+ *
+ * The @level parameter specifies the speed/quality tradeoff of the
+ * image interpolation. Currently, only ART_FILTER_NEAREST is
+ * implemented.
+ **/
+void
+art_rgb_a_affine (art_u8 *dst,
+                 int x0, int y0, int x1, int y1, int dst_rowstride,
+                 const art_u8 *src,
+                 int src_width, int src_height, int src_rowstride,
+                 art_u32 rgb,
+                 const double affine[6],
+                 ArtFilterLevel level,
+                 ArtAlphaGamma *alphagamma)
+{
+  /* Note: this is a slow implementation, and is missing all filter
+     levels other than NEAREST. It is here for clarity of presentation
+     and to establish the interface. */
+  int x, y;
+  double inv[6];
+  art_u8 *dst_p, *dst_linestart;
+  const art_u8 *src_p;
+  ArtPoint pt, src_pt;
+  int src_x, src_y;
+  int alpha;
+  art_u8 bg_r, bg_g, bg_b;
+  art_u8 fg_r, fg_g, fg_b;
+  int tmp;
+  int run_x0, run_x1;
+  art_u8 r, g, b;
+
+  r = (rgb>>16)&0xff;
+  g = (rgb>>8)&0xff;
+  b = (rgb)&0xff;
+
+  dst_linestart = dst;
+  art_affine_invert (inv, affine);
+  for (y = y0; y < y1; y++)
+    {
+      pt.y = y + 0.5;
+      run_x0 = x0;
+      run_x1 = x1;
+      art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height,
+                         inv);
+      dst_p = dst_linestart + (run_x0 - x0) * 3;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         pt.x = x + 0.5;
+         art_affine_point (&src_pt, &pt, inv);
+         src_x = floor (src_pt.x);
+         src_y = floor (src_pt.y);
+         src_p = src + (src_y * src_rowstride) + src_x;
+         if (src_x >= 0 && src_x < src_width &&
+             src_y >= 0 && src_y < src_height)
+           {
+
+         alpha = *src_p;
+         if (alpha)
+           {
+             if (alpha == 255)
+               {
+                 dst_p[0] = r;
+                 dst_p[1] = g;
+                 dst_p[2] = b;
+               }
+             else
+               {
+                 bg_r = dst_p[0];
+                 bg_g = dst_p[1];
+                 bg_b = dst_p[2];
+                 
+                 tmp = (r - bg_r) * alpha;
+                 fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 tmp = (g - bg_g) * alpha;
+                 fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 tmp = (b - bg_b) * alpha;
+                 fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 
+                 dst_p[0] = fg_r;
+                 dst_p[1] = fg_g;
+                 dst_p[2] = fg_b;
+               }
+           }
+           } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; }
+         dst_p += 3;
+       }
+      dst_linestart += dst_rowstride;
+    }
+}
diff --git a/lib/art/art_rgb_affine.c b/lib/art/art_rgb_affine.c
new file mode 100644 (file)
index 0000000..1d82667
--- /dev/null
@@ -0,0 +1,106 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_affine.h"
+
+#include <math.h>
+#include "art_misc.h"
+#include "art_point.h"
+#include "art_affine.h"
+#include "art_rgb_affine_private.h"
+
+/* This module handles compositing of affine-transformed rgb images
+   over rgb pixel buffers. */
+
+/**
+ * art_rgb_affine: Affine transform source RGB image and composite.
+ * @dst: Destination image RGB buffer.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @dst_rowstride: Rowstride of @dst buffer.
+ * @src: Source image RGB buffer.
+ * @src_width: Width of source image.
+ * @src_height: Height of source image.
+ * @src_rowstride: Rowstride of @src buffer.
+ * @affine: Affine transform.
+ * @level: Filter level.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Affine transform the source image stored in @src, compositing over
+ * the area of destination image @dst specified by the rectangle
+ * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges
+ * of this rectangle are included, and the right and bottom edges are
+ * excluded.
+ *
+ * The @alphagamma parameter specifies that the alpha compositing be done
+ * in a gamma-corrected color space. Since the source image is opaque RGB,
+ * this argument only affects the edges. In the current implementation,
+ * it is ignored.
+ *
+ * The @level parameter specifies the speed/quality tradeoff of the
+ * image interpolation. Currently, only ART_FILTER_NEAREST is
+ * implemented.
+ **/
+void
+art_rgb_affine (art_u8 *dst, int x0, int y0, int x1, int y1, int dst_rowstride,
+               const art_u8 *src,
+               int src_width, int src_height, int src_rowstride,
+               const double affine[6],
+               ArtFilterLevel level,
+               ArtAlphaGamma *alphagamma)
+{
+  /* Note: this is a slow implementation, and is missing all filter
+     levels other than NEAREST. It is here for clarity of presentation
+     and to establish the interface. */
+  int x, y;
+  double inv[6];
+  art_u8 *dst_p, *dst_linestart;
+  const art_u8 *src_p;
+  ArtPoint pt, src_pt;
+  int src_x, src_y;
+  int run_x0, run_x1;
+
+  dst_linestart = dst;
+  art_affine_invert (inv, affine);
+  for (y = y0; y < y1; y++)
+    {
+      pt.y = y + 0.5;
+      run_x0 = x0;
+      run_x1 = x1;
+      art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height,
+                         inv);
+      dst_p = dst_linestart + (run_x0 - x0) * 3;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         pt.x = x + 0.5;
+         art_affine_point (&src_pt, &pt, inv);
+         src_x = floor (src_pt.x);
+         src_y = floor (src_pt.y);
+         src_p = src + (src_y * src_rowstride) + src_x * 3;
+         dst_p[0] = src_p[0];
+         dst_p[1] = src_p[1];
+         dst_p[2] = src_p[2];
+         dst_p += 3;
+       }
+      dst_linestart += dst_rowstride;
+    }
+}
diff --git a/lib/art/art_rgb_affine_private.c b/lib/art/art_rgb_affine_private.c
new file mode 100644 (file)
index 0000000..679e114
--- /dev/null
@@ -0,0 +1,127 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_affine_private.h"
+
+#include <math.h>
+#include "art_misc.h"
+#include "art_point.h"
+#include "art_affine.h"
+
+/* Private functions for the rgb affine image compositors - primarily,
+   the determination of runs, eliminating the need for source image
+   bbox calculation in the inner loop. */
+
+/* Determine a "run", such that the inverse affine of all pixels from
+   (x0, y) inclusive to (x1, y) exclusive fit within the bounds
+   of the source image.
+
+   Initial values of x0, x1, and result values stored in first two
+   pointer arguments.
+*/
+
+#define EPSILON 1e-6
+
+void
+art_rgb_affine_run (int *p_x0, int *p_x1, int y,
+                   int src_width, int src_height,
+                   const double affine[6])
+{
+  int x0, x1;
+  double z;
+  double x_intercept;
+  int xi;
+
+  x0 = *p_x0;
+  x1 = *p_x1;
+
+  /* do left and right edges */
+  if (affine[0] > EPSILON)
+    {
+      z = affine[2] * (y + 0.5) + affine[4];
+      x_intercept = -z / affine[0];
+      xi = ceil (x_intercept + EPSILON - 0.5);
+      if (xi > x0)
+       x0 = xi;
+      x_intercept = (-z + src_width) / affine[0];
+      xi = ceil (x_intercept - EPSILON - 0.5);
+      if (xi < x1)
+       x1 = xi;
+    }
+  else if (affine[0] < -EPSILON)
+    {
+      z = affine[2] * (y + 0.5) + affine[4];
+      x_intercept = (-z + src_width) / affine[0];
+      xi = ceil (x_intercept + EPSILON - 0.5);
+      if (xi > x0)
+       x0 = xi;
+      x_intercept = -z / affine[0];
+      xi = ceil (x_intercept - EPSILON - 0.5);
+      if (xi < x1)
+       x1 = xi;
+    }
+  else
+    {
+      z = affine[2] * (y + 0.5) + affine[4];
+      if (z < 0 || z >= src_width)
+       {
+         *p_x1 = *p_x0;
+         return;
+       }
+    }
+
+  /* do top and bottom edges */
+  if (affine[1] > EPSILON)
+    {
+      z = affine[3] * (y + 0.5) + affine[5];
+      x_intercept = -z / affine[1];
+      xi = ceil (x_intercept + EPSILON - 0.5);
+      if (xi > x0)
+       x0 = xi;
+      x_intercept = (-z + src_height) / affine[1];
+      xi = ceil (x_intercept - EPSILON - 0.5);
+      if (xi < x1)
+       x1 = xi;
+    }
+  else if (affine[1] < -EPSILON)
+    {
+      z = affine[3] * (y + 0.5) + affine[5];
+      x_intercept = (-z + src_height) / affine[1];
+      xi = ceil (x_intercept + EPSILON - 0.5);
+      if (xi > x0)
+       x0 = xi;
+      x_intercept = -z / affine[1];
+      xi = ceil (x_intercept - EPSILON - 0.5);
+      if (xi < x1)
+       x1 = xi;
+    }
+  else
+    {
+      z = affine[3] * (y + 0.5) + affine[5];
+      if (z < 0 || z >= src_height)
+       {
+         *p_x1 = *p_x0;
+         return;
+       }
+    }
+
+  *p_x0 = x0;
+  *p_x1 = x1;
+}
diff --git a/lib/art/art_rgb_bitmap_affine.c b/lib/art/art_rgb_bitmap_affine.c
new file mode 100644 (file)
index 0000000..825f8b5
--- /dev/null
@@ -0,0 +1,198 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_bitmap_affine.h"
+
+#include <math.h>
+#include "art_misc.h"
+#include "art_point.h"
+#include "art_affine.h"
+#include "art_rgb_affine_private.h"
+
+/* This module handles compositing of affine-transformed bitmap images
+   over rgb pixel buffers. */
+
+/* Composite the source image over the destination image, applying the
+   affine transform. Foreground color is given and assumed to be
+   opaque, background color is assumed to be fully transparent. */
+
+static void
+art_rgb_bitmap_affine_opaque (art_u8 *dst,
+                             int x0, int y0, int x1, int y1,
+                             int dst_rowstride,
+                             const art_u8 *src,
+                             int src_width, int src_height, int src_rowstride,
+                             art_u32 rgb,
+                             const double affine[6],
+                             ArtFilterLevel level,
+                             ArtAlphaGamma *alphagamma)
+{
+  /* Note: this is a slow implementation, and is missing all filter
+     levels other than NEAREST. It is here for clarity of presentation
+     and to establish the interface. */
+  int x, y;
+  double inv[6];
+  art_u8 *dst_p, *dst_linestart;
+  const art_u8 *src_p;
+  ArtPoint pt, src_pt;
+  int src_x, src_y;
+  art_u8 r, g, b;
+  int run_x0, run_x1;
+
+  r = rgb >> 16;
+  g = (rgb >> 8) & 0xff;
+  b = rgb & 0xff;
+  dst_linestart = dst;
+  art_affine_invert (inv, affine);
+  for (y = y0; y < y1; y++)
+    {
+      pt.y = y + 0.5;
+      run_x0 = x0;
+      run_x1 = x1;
+      art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height,
+                         inv);
+      dst_p = dst_linestart + (run_x0 - x0) * 3;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         pt.x = x + 0.5;
+         art_affine_point (&src_pt, &pt, inv);
+         src_x = floor (src_pt.x);
+         src_y = floor (src_pt.y);
+         src_p = src + (src_y * src_rowstride) + (src_x >> 3);
+         if (*src_p & (128 >> (src_x & 7)))
+           {
+             dst_p[0] = r;
+             dst_p[1] = g;
+             dst_p[2] = b;
+           }
+         dst_p += 3;
+       }
+      dst_linestart += dst_rowstride;
+    }
+}
+/* Composite the source image over the destination image, applying the
+   affine transform. Foreground color is given, background color is
+   assumed to be fully transparent. */
+
+/**
+ * art_rgb_bitmap_affine: Affine transform source bitmap image and composite.
+ * @dst: Destination image RGB buffer.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @dst_rowstride: Rowstride of @dst buffer.
+ * @src: Source image bitmap buffer.
+ * @src_width: Width of source image.
+ * @src_height: Height of source image.
+ * @src_rowstride: Rowstride of @src buffer.
+ * @rgba: RGBA foreground color, in 0xRRGGBBAA.
+ * @affine: Affine transform.
+ * @level: Filter level.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Affine transform the source image stored in @src, compositing over
+ * the area of destination image @dst specified by the rectangle
+ * (@x0, @y0) - (@x1, @y1).
+ *
+ * The source bitmap stored with MSB as the leftmost pixel. Source 1
+ * bits correspond to the semitransparent color @rgba, while source 0
+ * bits are transparent.
+ *
+ * See art_rgb_affine() for a description of additional parameters.
+ **/
+void
+art_rgb_bitmap_affine (art_u8 *dst,
+                      int x0, int y0, int x1, int y1, int dst_rowstride,
+                      const art_u8 *src,
+                      int src_width, int src_height, int src_rowstride,
+                      art_u32 rgba,
+                      const double affine[6],
+                      ArtFilterLevel level,
+                      ArtAlphaGamma *alphagamma)
+{
+  /* Note: this is a slow implementation, and is missing all filter
+     levels other than NEAREST. It is here for clarity of presentation
+     and to establish the interface. */
+  int x, y;
+  double inv[6];
+  art_u8 *dst_p, *dst_linestart;
+  const art_u8 *src_p;
+  ArtPoint pt, src_pt;
+  int src_x, src_y;
+  int alpha;
+  art_u8 bg_r, bg_g, bg_b;
+  art_u8 fg_r, fg_g, fg_b;
+  art_u8 r, g, b;
+  int run_x0, run_x1;
+
+  alpha = rgba & 0xff;
+  if (alpha == 0xff)
+    {
+      art_rgb_bitmap_affine_opaque (dst, x0, y0, x1, y1, dst_rowstride,
+                                   src,
+                                   src_width, src_height, src_rowstride,
+                                   rgba >> 8,
+                                   affine,
+                                   level,
+                                   alphagamma);
+      return;
+    }
+  /* alpha = (65536 * alpha) / 255; */
+  alpha = (alpha << 8) + alpha + (alpha >> 7);
+  r = rgba >> 24;
+  g = (rgba >> 16) & 0xff;
+  b = (rgba >> 8) & 0xff;
+  dst_linestart = dst;
+  art_affine_invert (inv, affine);
+  for (y = y0; y < y1; y++)
+    {
+      pt.y = y + 0.5;
+      run_x0 = x0;
+      run_x1 = x1;
+      art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height,
+                         inv);
+      dst_p = dst_linestart + (run_x0 - x0) * 3;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         pt.x = x + 0.5;
+         art_affine_point (&src_pt, &pt, inv);
+         src_x = floor (src_pt.x);
+         src_y = floor (src_pt.y);
+         src_p = src + (src_y * src_rowstride) + (src_x >> 3);
+         if (*src_p & (128 >> (src_x & 7)))
+           {
+             bg_r = dst_p[0];
+             bg_g = dst_p[1];
+             bg_b = dst_p[2];
+
+             fg_r = bg_r + (((r - bg_r) * alpha + 0x8000) >> 16);
+             fg_g = bg_g + (((g - bg_g) * alpha + 0x8000) >> 16);
+             fg_b = bg_b + (((b - bg_b) * alpha + 0x8000) >> 16);
+
+             dst_p[0] = fg_r;
+             dst_p[1] = fg_g;
+             dst_p[2] = fg_b;
+           }
+         dst_p += 3;
+       }
+      dst_linestart += dst_rowstride;
+    }
+}
diff --git a/lib/art/art_rgb_pixbuf_affine.c b/lib/art/art_rgb_pixbuf_affine.c
new file mode 100644 (file)
index 0000000..0a25b57
--- /dev/null
@@ -0,0 +1,104 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_pixbuf_affine.h"
+
+#include <math.h>
+#include "art_misc.h"
+#include "art_point.h"
+#include "art_affine.h"
+#include "art_pixbuf.h"
+#include "art_rgb_affine.h"
+#include "art_rgb_affine.h"
+#include "art_rgb_rgba_affine.h"
+
+/* This module handles compositing of affine-transformed generic
+   pixbuf images over rgb pixel buffers. */
+
+/* Composite the source image over the destination image, applying the
+   affine transform. */
+/**
+ * art_rgb_pixbuf_affine: Affine transform source RGB pixbuf and composite.
+ * @dst: Destination image RGB buffer.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @dst_rowstride: Rowstride of @dst buffer.
+ * @pixbuf: source image pixbuf.
+ * @affine: Affine transform.
+ * @level: Filter level.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Affine transform the source image stored in @src, compositing over
+ * the area of destination image @dst specified by the rectangle
+ * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges
+ * of this rectangle are included, and the right and bottom edges are
+ * excluded.
+ *
+ * The @alphagamma parameter specifies that the alpha compositing be
+ * done in a gamma-corrected color space. In the current
+ * implementation, it is ignored.
+ *
+ * The @level parameter specifies the speed/quality tradeoff of the
+ * image interpolation. Currently, only ART_FILTER_NEAREST is
+ * implemented.
+ **/
+void
+art_rgb_pixbuf_affine (art_u8 *dst,
+                      int x0, int y0, int x1, int y1, int dst_rowstride,
+                      const ArtPixBuf *pixbuf,
+                      const double affine[6],
+                      ArtFilterLevel level,
+                      ArtAlphaGamma *alphagamma)
+{
+  if (pixbuf->format != ART_PIX_RGB)
+    {
+      art_warn ("art_rgb_pixbuf_affine: need RGB format image\n");
+      return;
+    }
+
+  if (pixbuf->bits_per_sample != 8)
+    {
+      art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n");
+      return;
+    }
+
+  if (pixbuf->n_channels != 3 + (pixbuf->has_alpha != 0))
+    {
+      art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n");
+      return;
+    }
+
+  if (pixbuf->has_alpha)
+    art_rgb_rgba_affine (dst, x0, y0, x1, y1, dst_rowstride,
+                        pixbuf->pixels,
+                        pixbuf->width, pixbuf->height, pixbuf->rowstride,
+                        affine,
+                        level,
+                        alphagamma);
+  else
+    art_rgb_affine (dst, x0, y0, x1, y1, dst_rowstride,
+                   pixbuf->pixels,
+                   pixbuf->width, pixbuf->height, pixbuf->rowstride,
+                   affine,
+                   level,
+                   alphagamma);
+}
diff --git a/lib/art/art_rgb_rgba_affine.c b/lib/art/art_rgb_rgba_affine.c
new file mode 100644 (file)
index 0000000..41c7397
--- /dev/null
@@ -0,0 +1,142 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_rgb_rgba_affine.h"
+
+#include <math.h>
+#include "art_misc.h"
+#include "art_point.h"
+#include "art_affine.h"
+#include "art_rgb_affine_private.h"
+
+/* This module handles compositing of affine-transformed rgba images
+   over rgb pixel buffers. */
+
+/* Composite the source image over the destination image, applying the
+   affine transform. */
+
+/**
+ * art_rgb_rgba_affine: Affine transform source RGBA image and composite.
+ * @dst: Destination image RGB buffer.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @dst_rowstride: Rowstride of @dst buffer.
+ * @src: Source image RGBA buffer.
+ * @src_width: Width of source image.
+ * @src_height: Height of source image.
+ * @src_rowstride: Rowstride of @src buffer.
+ * @affine: Affine transform.
+ * @level: Filter level.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Affine transform the source image stored in @src, compositing over
+ * the area of destination image @dst specified by the rectangle
+ * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges
+ * of this rectangle are included, and the right and bottom edges are
+ * excluded.
+ *
+ * The @alphagamma parameter specifies that the alpha compositing be
+ * done in a gamma-corrected color space. In the current
+ * implementation, it is ignored.
+ *
+ * The @level parameter specifies the speed/quality tradeoff of the
+ * image interpolation. Currently, only ART_FILTER_NEAREST is
+ * implemented.
+ **/
+void
+art_rgb_rgba_affine (art_u8 *dst,
+                    int x0, int y0, int x1, int y1, int dst_rowstride,
+                    const art_u8 *src,
+                    int src_width, int src_height, int src_rowstride,
+                    const double affine[6],
+                    ArtFilterLevel level,
+                    ArtAlphaGamma *alphagamma)
+{
+  /* Note: this is a slow implementation, and is missing all filter
+     levels other than NEAREST. It is here for clarity of presentation
+     and to establish the interface. */
+  int x, y;
+  double inv[6];
+  art_u8 *dst_p, *dst_linestart;
+  const art_u8 *src_p;
+  ArtPoint pt, src_pt;
+  int src_x, src_y;
+  int alpha;
+  art_u8 bg_r, bg_g, bg_b;
+  art_u8 fg_r, fg_g, fg_b;
+  int tmp;
+  int run_x0, run_x1;
+
+  dst_linestart = dst;
+  art_affine_invert (inv, affine);
+  for (y = y0; y < y1; y++)
+    {
+      pt.y = y + 0.5;
+      run_x0 = x0;
+      run_x1 = x1;
+      art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height,
+                         inv);
+      dst_p = dst_linestart + (run_x0 - x0) * 3;
+      for (x = run_x0; x < run_x1; x++)
+       {
+         pt.x = x + 0.5;
+         art_affine_point (&src_pt, &pt, inv);
+         src_x = floor (src_pt.x);
+         src_y = floor (src_pt.y);
+         src_p = src + (src_y * src_rowstride) + src_x * 4;
+         if (src_x >= 0 && src_x < src_width &&
+             src_y >= 0 && src_y < src_height)
+           {
+
+         alpha = src_p[3];
+         if (alpha)
+           {
+             if (alpha == 255)
+               {
+                 dst_p[0] = src_p[0];
+                 dst_p[1] = src_p[1];
+                 dst_p[2] = src_p[2];
+               }
+             else
+               {
+                 bg_r = dst_p[0];
+                 bg_g = dst_p[1];
+                 bg_b = dst_p[2];
+                 
+                 tmp = (src_p[0] - bg_r) * alpha;
+                 fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 tmp = (src_p[1] - bg_g) * alpha;
+                 fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 tmp = (src_p[2] - bg_b) * alpha;
+                 fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8);
+                 
+                 dst_p[0] = fg_r;
+                 dst_p[1] = fg_g;
+                 dst_p[2] = fg_b;
+               }
+           }
+           } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; }
+         dst_p += 3;
+       }
+      dst_linestart += dst_rowstride;
+    }
+}
diff --git a/lib/art/art_rgb_svp.c b/lib/art/art_rgb_svp.c
new file mode 100644 (file)
index 0000000..d031016
--- /dev/null
@@ -0,0 +1,457 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Render a sorted vector path into an RGB buffer. */
+
+#include "config.h"
+#include "art_rgb_svp.h"
+
+#include "art_svp.h"
+#include "art_svp_render_aa.h"
+#include "art_rgb.h"
+
+typedef struct _ArtRgbSVPData ArtRgbSVPData;
+typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData;
+
+struct _ArtRgbSVPData {
+  art_u32 rgbtab[256];
+  art_u8 *buf;
+  int rowstride;
+  int x0, x1;
+};
+
+struct _ArtRgbSVPAlphaData {
+  int alphatab[256];
+  art_u8 r, g, b, alpha;
+  art_u8 *buf;
+  int rowstride;
+  int x0, x1;
+};
+
+static void
+art_rgb_svp_callback (void *callback_data, int y,
+                     int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtRgbSVPData *data = (ArtRgbSVPData *)callback_data;
+  art_u8 *linebuf;
+  int run_x0, run_x1;
+  art_u32 running_sum = start;
+  art_u32 rgb;
+  int x0, x1;
+  int k;
+
+  linebuf = data->buf;
+  x0 = data->x0;
+  x1 = data->x1;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0)
+       {
+         rgb = data->rgbtab[(running_sum >> 16) & 0xff];
+         art_rgb_fill_run (linebuf,
+                           rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                           run_x1 - x0);
+       }
+
+      for (k = 0; k < n_steps - 1; k++)
+       {
+         running_sum += steps[k].delta;
+         run_x0 = run_x1;
+         run_x1 = steps[k + 1].x;
+         if (run_x1 > run_x0)
+           {
+             rgb = data->rgbtab[(running_sum >> 16) & 0xff];
+             art_rgb_fill_run (linebuf + (run_x0 - x0) * 3,
+                               rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                               run_x1 - run_x0);
+           }
+       }
+      running_sum += steps[k].delta;
+      if (x1 > run_x1)
+       {
+         rgb = data->rgbtab[(running_sum >> 16) & 0xff];
+         art_rgb_fill_run (linebuf + (run_x1 - x0) * 3,
+                           rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                           x1 - run_x1);
+       }
+    }
+  else
+    {
+      rgb = data->rgbtab[(running_sum >> 16) & 0xff];
+      art_rgb_fill_run (linebuf,
+                       rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff,
+                       x1 - x0);
+    }
+
+  data->buf += data->rowstride;
+}
+
+/* Render the vector path into the RGB buffer. */
+
+/**
+ * art_rgb_svp_aa: Render sorted vector path into RGB buffer.
+ * @svp: The source sorted vector path.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @fg_color: Foreground color in 0xRRGGBB format.
+ * @bg_color: Background color in 0xRRGGBB format.
+ * @buf: Destination RGB buffer.
+ * @rowstride: Rowstride of @buf buffer.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the rendering.
+ *
+ * Renders the shape specified with @svp into the @buf RGB buffer.
+ * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height,
+ * of the rectangle rendered. The new pixels are stored starting at
+ * the first byte of @buf. Thus, the @x0 and @y0 parameters specify
+ * an offset within @svp, and may be tweaked as a way of doing
+ * integer-pixel translations without fiddling with @svp itself.
+ *
+ * The @fg_color and @bg_color arguments specify the opaque colors to
+ * be used for rendering. For pixels of entirely 0 winding-number,
+ * @bg_color is used. For pixels of entirely 1 winding number,
+ * @fg_color is used. In between, the color is interpolated based on
+ * the fraction of the pixel with a winding number of 1. If
+ * @alphagamma is NULL, then linear interpolation (in pixel counts) is
+ * the default. Otherwise, the interpolation is as specified by
+ * @alphagamma.
+ **/
+void
+art_rgb_svp_aa (const ArtSVP *svp,
+               int x0, int y0, int x1, int y1,
+               art_u32 fg_color, art_u32 bg_color,
+               art_u8 *buf, int rowstride,
+               ArtAlphaGamma *alphagamma)
+{
+  ArtRgbSVPData data;
+
+  int r_fg, g_fg, b_fg;
+  int r_bg, g_bg, b_bg;
+  int r, g, b;
+  int dr, dg, db;
+  int i;
+
+  if (alphagamma == NULL)
+    {
+      r_fg = fg_color >> 16;
+      g_fg = (fg_color >> 8) & 0xff;
+      b_fg = fg_color & 0xff;
+
+      r_bg = bg_color >> 16;
+      g_bg = (bg_color >> 8) & 0xff;
+      b_bg = bg_color & 0xff;
+
+      r = (r_bg << 16) + 0x8000;
+      g = (g_bg << 16) + 0x8000;
+      b = (b_bg << 16) + 0x8000;
+      dr = ((r_fg - r_bg) << 16) / 255;
+      dg = ((g_fg - g_bg) << 16) / 255;
+      db = ((b_fg - b_bg) << 16) / 255;
+
+      for (i = 0; i < 256; i++)
+       {
+         data.rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16);
+         r += dr;
+         g += dg;
+         b += db;
+       }
+    }
+  else
+    {
+      int *table;
+      art_u8 *invtab;
+
+      table = alphagamma->table;
+
+      r_fg = table[fg_color >> 16];
+      g_fg = table[(fg_color >> 8) & 0xff];
+      b_fg = table[fg_color & 0xff];
+
+      r_bg = table[bg_color >> 16];
+      g_bg = table[(bg_color >> 8) & 0xff];
+      b_bg = table[bg_color & 0xff];
+
+      r = (r_bg << 16) + 0x8000;
+      g = (g_bg << 16) + 0x8000;
+      b = (b_bg << 16) + 0x8000;
+      dr = ((r_fg - r_bg) << 16) / 255;
+      dg = ((g_fg - g_bg) << 16) / 255;
+      db = ((b_fg - b_bg) << 16) / 255;
+
+      invtab = alphagamma->invtable;
+      for (i = 0; i < 256; i++)
+       {
+         data.rgbtab[i] = (invtab[r >> 16] << 16) |
+           (invtab[g >> 16] << 8) |
+           invtab[b >> 16];
+         r += dr;
+         g += dg;
+         b += db;
+       }
+    }
+  data.buf = buf;
+  data.rowstride = rowstride;
+  data.x0 = x0;
+  data.x1 = x1;
+  art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_callback, &data);
+}
+
+static void
+art_rgb_svp_alpha_callback (void *callback_data, int y,
+                           int start, ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data;
+  art_u8 *linebuf;
+  int run_x0, run_x1;
+  art_u32 running_sum = start;
+  int x0, x1;
+  int k;
+  art_u8 r, g, b;
+  int *alphatab;
+  int alpha;
+
+  linebuf = data->buf;
+  x0 = data->x0;
+  x1 = data->x1;
+
+  r = data->r;
+  g = data->g;
+  b = data->b;
+  alphatab = data->alphatab;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0)
+       {
+         alpha = (running_sum >> 16) & 0xff;
+         if (alpha)
+           art_rgb_run_alpha (linebuf,
+                              r, g, b, alphatab[alpha],
+                              run_x1 - x0);
+       }
+
+      for (k = 0; k < n_steps - 1; k++)
+       {
+         running_sum += steps[k].delta;
+         run_x0 = run_x1;
+         run_x1 = steps[k + 1].x;
+         if (run_x1 > run_x0)
+           {
+             alpha = (running_sum >> 16) & 0xff;
+             if (alpha)
+               art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3,
+                                  r, g, b, alphatab[alpha],
+                                  run_x1 - run_x0);
+           }
+       }
+      running_sum += steps[k].delta;
+      if (x1 > run_x1)
+       {
+         alpha = (running_sum >> 16) & 0xff;
+         if (alpha)
+           art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3,
+                              r, g, b, alphatab[alpha],
+                              x1 - run_x1);
+       }
+    }
+  else
+    {
+      alpha = (running_sum >> 16) & 0xff;
+      if (alpha)
+       art_rgb_run_alpha (linebuf,
+                          r, g, b, alphatab[alpha],
+                          x1 - x0);
+    }
+
+  data->buf += data->rowstride;
+}
+
+static void
+art_rgb_svp_alpha_opaque_callback (void *callback_data, int y,
+                                  int start,
+                                  ArtSVPRenderAAStep *steps, int n_steps)
+{
+  ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data;
+  art_u8 *linebuf;
+  int run_x0, run_x1;
+  art_u32 running_sum = start;
+  int x0, x1;
+  int k;
+  art_u8 r, g, b;
+  int *alphatab;
+  int alpha;
+
+  linebuf = data->buf;
+  x0 = data->x0;
+  x1 = data->x1;
+
+  r = data->r;
+  g = data->g;
+  b = data->b;
+  alphatab = data->alphatab;
+
+  if (n_steps > 0)
+    {
+      run_x1 = steps[0].x;
+      if (run_x1 > x0)
+       {
+         alpha = running_sum >> 16;
+         if (alpha)
+           {
+             if (alpha >= 255)
+               art_rgb_fill_run (linebuf,
+                                 r, g, b,
+                                 run_x1 - x0);
+             else
+               art_rgb_run_alpha (linebuf,
+                                  r, g, b, alphatab[alpha],
+                                  run_x1 - x0);
+           }
+       }
+
+      for (k = 0; k < n_steps - 1; k++)
+       {
+         running_sum += steps[k].delta;
+         run_x0 = run_x1;
+         run_x1 = steps[k + 1].x;
+         if (run_x1 > run_x0)
+           {
+             alpha = running_sum >> 16;
+             if (alpha)
+               {
+                 if (alpha >= 255)
+                   art_rgb_fill_run (linebuf + (run_x0 - x0) * 3,
+                                     r, g, b,
+                                     run_x1 - run_x0);
+                 else
+                   art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3,
+                                      r, g, b, alphatab[alpha],
+                                      run_x1 - run_x0);
+               }
+           }
+       }
+      running_sum += steps[k].delta;
+      if (x1 > run_x1)
+       {
+         alpha = running_sum >> 16;
+         if (alpha)
+           {
+             if (alpha >= 255)
+               art_rgb_fill_run (linebuf + (run_x1 - x0) * 3,
+                                 r, g, b,
+                                 x1 - run_x1);
+             else
+               art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3,
+                                  r, g, b, alphatab[alpha],
+                                  x1 - run_x1);
+           }
+       }
+    }
+  else
+    {
+      alpha = running_sum >> 16;
+      if (alpha)
+       {
+         if (alpha >= 255)
+           art_rgb_fill_run (linebuf,
+                             r, g, b,
+                             x1 - x0);
+         else
+           art_rgb_run_alpha (linebuf,
+                              r, g, b, alphatab[alpha],
+                              x1 - x0);
+       }
+    }
+
+  data->buf += data->rowstride;
+}
+
+/**
+ * art_rgb_svp_alpha: Alpha-composite sorted vector path over RGB buffer.
+ * @svp: The source sorted vector path.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @rgba: Color in 0xRRGGBBAA format.
+ * @buf: Destination RGB buffer.
+ * @rowstride: Rowstride of @buf buffer.
+ * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing.
+ *
+ * Renders the shape specified with @svp over the @buf RGB buffer.
+ * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height,
+ * of the rectangle rendered. The new pixels are stored starting at
+ * the first byte of @buf. Thus, the @x0 and @y0 parameters specify
+ * an offset within @svp, and may be tweaked as a way of doing
+ * integer-pixel translations without fiddling with @svp itself.
+ *
+ * The @rgba argument specifies the color for the rendering. Pixels of
+ * entirely 0 winding number are left untouched. Pixels of entirely
+ * 1 winding number have the color @rgba composited over them (ie,
+ * are replaced by the red, green, blue components of @rgba if the alpha
+ * component is 0xff). Pixels of intermediate coverage are interpolated
+ * according to the rule in @alphagamma, or default to linear if
+ * @alphagamma is NULL.
+ **/
+void
+art_rgb_svp_alpha (const ArtSVP *svp,
+                  int x0, int y0, int x1, int y1,
+                  art_u32 rgba,
+                  art_u8 *buf, int rowstride,
+                  ArtAlphaGamma *alphagamma)
+{
+  ArtRgbSVPAlphaData data;
+  int r, g, b, alpha;
+  int i;
+  int a, da;
+
+  r = rgba >> 24;
+  g = (rgba >> 16) & 0xff;
+  b = (rgba >> 8) & 0xff;
+  alpha = rgba & 0xff;
+
+  data.r = r;
+  data.g = g;
+  data.b = b;
+  data.alpha = alpha;
+
+  a = 0x8000;
+  da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */
+
+  for (i = 0; i < 256; i++)
+    {
+      data.alphatab[i] = a >> 16;
+      a += da;
+    }
+
+  data.buf = buf;
+  data.rowstride = rowstride;
+  data.x0 = x0;
+  data.x1 = x1;
+  if (alpha == 255)
+    art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback,
+                      &data);
+  else
+    art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback, &data);
+}
+
diff --git a/lib/art/art_rgba.c b/lib/art/art_rgba.c
new file mode 100644 (file)
index 0000000..cde183c
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * art_rgba.c: Functions for manipulating RGBA pixel data.
+ *
+ * Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2000 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 "config.h"
+#include "art_rgba.h"
+
+#define ART_OPTIMIZE_SPACE
+
+#ifndef ART_OPTIMIZE_SPACE
+#include "art_rgba_table.c"
+#endif
+
+/**
+ * art_rgba_rgba_composite: Composite RGBA image over RGBA buffer.
+ * @dst: Destination RGBA buffer.
+ * @src: Source RGBA buffer.
+ * @n: Number of RGBA pixels to composite.
+ *
+ * Composites the RGBA pixels in @dst over the @src buffer.
+ **/
+void
+art_rgba_rgba_composite (art_u8 *dst, const art_u8 *src, int n)
+{
+  int i;
+#ifdef WORDS_BIGENDIAN
+  art_u32 src_rgba, dst_rgba;
+#else
+  art_u32 src_abgr, dst_abgr;
+#endif
+  art_u8 src_alpha, dst_alpha;
+
+  for (i = 0; i < n; i++)
+    {
+#ifdef WORDS_BIGENDIAN
+      src_rgba = ((art_u32 *)src)[i];
+      src_alpha = src_rgba & 0xff;
+#else
+      src_abgr = ((art_u32 *)src)[i];
+      src_alpha = (src_abgr >> 24) & 0xff;
+#endif
+      if (src_alpha)
+       {
+         if (src_alpha == 0xff ||
+             (
+#ifdef WORDS_BIGENDIAN
+              dst_rgba = ((art_u32 *)dst)[i],
+              dst_alpha = dst_rgba & 0xff,
+#else
+              dst_abgr = ((art_u32 *)dst)[i],
+              dst_alpha = (dst_abgr >> 24),
+#endif
+              dst_alpha == 0))
+#ifdef WORDS_BIGENDIAN
+           ((art_u32 *)dst)[i] = src_rgba;
+#else
+           ((art_u32 *)dst)[i] = src_abgr;
+#endif
+         else
+           {
+             int r, g, b, a;
+             int src_r, src_g, src_b;
+             int dst_r, dst_g, dst_b;
+             int tmp;
+             int c;
+
+#ifdef ART_OPTIMIZE_SPACE
+             tmp = (255 - src_alpha) * (255 - dst_alpha) + 0x80;
+             a = 255 - ((tmp + (tmp >> 8)) >> 8);
+             c = ((src_alpha << 16) + (a >> 1)) / a;
+#else
+             tmp = art_rgba_composite_table[(src_alpha << 8) + dst_alpha];
+             c = tmp & 0x1ffff;
+             a = tmp >> 24;
+#endif
+#ifdef WORDS_BIGENDIAN
+             src_r = (src_rgba >> 24) & 0xff;
+             src_g = (src_rgba >> 16) & 0xff;
+             src_b = (src_rgba >> 8) & 0xff;
+             dst_r = (dst_rgba >> 24) & 0xff;
+             dst_g = (dst_rgba >> 16) & 0xff;
+             dst_b = (dst_rgba >> 8) & 0xff;
+#else
+             src_r = src_abgr & 0xff;
+             src_g = (src_abgr >> 8) & 0xff;
+             src_b = (src_abgr >> 16) & 0xff;
+             dst_r = dst_abgr & 0xff;
+             dst_g = (dst_abgr >> 8) & 0xff;
+             dst_b = (dst_abgr >> 16) & 0xff;
+#endif
+             r = dst_r + (((src_r - dst_r) * c + 0x8000) >> 16);
+             g = dst_g + (((src_g - dst_g) * c + 0x8000) >> 16);
+             b = dst_b + (((src_b - dst_b) * c + 0x8000) >> 16);
+#ifdef WORDS_BIGENDIAN
+           ((art_u32 *)dst)[i] = (r << 24) | (g << 16) | (b << 8) | a;
+#else
+           ((art_u32 *)dst)[i] = (a << 24) | (b << 16) | (g << 8) | r;
+#endif       
+           }
+       }
+#if 0
+      /* it's not clear to me this optimization really wins */
+      else
+       {
+         /* skip over run of transparent pixels */
+         for (; i < n - 1; i++)
+           {
+#ifdef WORDS_BIGENDIAN
+             src_rgba = ((art_u32 *)src)[i + 1];
+             if (src_rgba & 0xff)
+               break;
+#else
+             src_abgr = ((art_u32 *)src)[i + 1];
+             if (src_abgr & 0xff000000)
+               break;
+#endif
+           }
+       }
+#endif
+    }
+}
+
+/**
+ * art_rgba_fill_run: fill an RGBA buffer a solid RGB color.
+ * @buf: Buffer to fill.
+ * @r: Red, range 0..255.
+ * @g: Green, range 0..255.
+ * @b: Blue, range 0..255.
+ * @n: Number of RGB triples to fill.
+ *
+ * Fills a buffer with @n copies of the (@r, @g, @b) triple, solid
+ * alpha. Thus, locations @buf (inclusive) through @buf + 4 * @n
+ * (exclusive) are written.
+ **/
+void
+art_rgba_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n)
+{
+  int i;
+#ifdef WORDS_BIGENDIAN
+  art_u32 src_rgba;
+#else
+  art_u32 src_abgr;
+#endif
+
+#ifdef WORDS_BIGENDIAN
+  src_rgba = (r << 24) | (g << 16) | (b << 8) | 255;
+#else
+  src_abgr = (255 << 24) | (b << 16) | (g << 8) | r;
+#endif
+  for (i = 0; i < n; i++)
+    {
+#ifdef WORDS_BIGENDIAN
+      ((art_u32 *)buf)[i] = src_rgba;
+#else
+      ((art_u32 *)buf)[i] = src_abgr;
+#endif
+    }    
+}
+
+/**
+ * art_rgba_run_alpha: Render semitransparent color over RGBA buffer.
+ * @buf: Buffer for rendering.
+ * @r: Red, range 0..255.
+ * @g: Green, range 0..255.
+ * @b: Blue, range 0..255.
+ * @alpha: Alpha, range 0..255.
+ * @n: Number of RGB triples to render.
+ *
+ * Renders a sequential run of solid (@r, @g, @b) color over @buf with
+ * opacity @alpha. Note that the range of @alpha is 0..255, in contrast
+ * to art_rgb_run_alpha, which has a range of 0..256.
+ **/
+void
+art_rgba_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n)
+{
+  int i;
+#ifdef WORDS_BIGENDIAN
+  art_u32 src_rgba, dst_rgba;
+#else
+  art_u32 src_abgr, dst_abgr;
+#endif
+  art_u8 dst_alpha;
+  int a;
+  int dst_r, dst_g, dst_b;
+  int tmp;
+  int c;
+
+#ifdef WORDS_BIGENDIAN
+  src_rgba = (r << 24) | (g << 16) | (b << 8) | alpha;
+#else
+  src_abgr = (alpha << 24) | (b << 16) | (g << 8) | r;
+#endif
+  for (i = 0; i < n; i++)
+    {
+#ifdef WORDS_BIGENDIAN
+      dst_rgba = ((art_u32 *)buf)[i];
+      dst_alpha = dst_rgba & 0xff;
+#else
+      dst_abgr = ((art_u32 *)buf)[i];
+      dst_alpha = (dst_abgr >> 24) & 0xff;
+#endif
+      if (dst_alpha)
+       {
+#ifdef ART_OPTIMIZE_SPACE
+         tmp = (255 - alpha) * (255 - dst_alpha) + 0x80;
+         a = 255 - ((tmp + (tmp >> 8)) >> 8);
+         c = ((alpha << 16) + (a >> 1)) / a;
+#else
+         tmp = art_rgba_composite_table[(alpha << 8) + dst_alpha];
+         c = tmp & 0x1ffff;
+         a = tmp >> 24;
+#endif
+#ifdef WORDS_BIGENDIAN
+         dst_r = (dst_rgba >> 24) & 0xff;
+         dst_g = (dst_rgba >> 16) & 0xff;
+         dst_b = (dst_rgba >> 8) & 0xff;
+#else
+         dst_r = dst_abgr & 0xff;
+         dst_g = (dst_abgr >> 8) & 0xff;
+         dst_b = (dst_abgr >> 16) & 0xff;
+#endif
+         dst_r += (((r - dst_r) * c + 0x8000) >> 16);
+         dst_g += (((g - dst_g) * c + 0x8000) >> 16);
+         dst_b += (((b - dst_b) * c + 0x8000) >> 16);
+#ifdef WORDS_BIGENDIAN
+         ((art_u32 *)buf)[i] = (dst_r << 24) | (dst_g << 16) | (dst_b << 8) | a;
+#else
+         ((art_u32 *)buf)[i] = (a << 24) | (dst_b << 16) | (dst_g << 8) | dst_r;
+#endif
+       }
+      else
+       {
+#ifdef WORDS_BIGENDIAN
+         ((art_u32 *)buf)[i] = src_rgba;
+#else
+         ((art_u32 *)buf)[i] = src_abgr;
+#endif
+       }
+    }
+}
diff --git a/lib/art/art_svp.c b/lib/art/art_svp.c
new file mode 100644 (file)
index 0000000..8d7f7d1
--- /dev/null
@@ -0,0 +1,152 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Basic constructors and operations for sorted vector paths */
+
+#include "config.h"
+#include "art_svp.h"
+
+#include "art_misc.h"
+
+/* Add a new segment. The arguments can be zero and NULL if the caller
+   would rather fill them in later.
+
+   We also realloc one auxiliary array of ints of size n_segs if
+   desired.
+*/
+/**
+ * art_svp_add_segment: Add a segment to an #ArtSVP structure.
+ * @p_vp: Pointer to where the #ArtSVP structure is stored.
+ * @pn_segs_max: Pointer to the allocated size of *@p_vp.
+ * @pn_points_max: Pointer to where auxiliary array is stored.
+ * @n_points: Number of points for new segment.
+ * @dir: Direction for new segment; 0 is up, 1 is down.
+ * @points: Points for new segment.
+ * @bbox: Bounding box for new segment.
+ *
+ * Adds a new segment to an ArtSVP structure. This routine reallocates
+ * the structure if necessary, updating *@p_vp and *@pn_segs_max as
+ * necessary.
+ *
+ * The new segment is simply added after all other segments. Thus,
+ * this routine should be called in order consistent with the #ArtSVP
+ * sorting rules.
+ *
+ * If the @bbox argument is given, it is simply stored in the new
+ * segment. Otherwise (if it is NULL), the bounding box is computed
+ * from the @points given.
+ **/
+int
+art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max,
+                    int **pn_points_max,
+                    int n_points, int dir, ArtPoint *points,
+                    ArtDRect *bbox)
+{
+  int seg_num;
+  ArtSVP *svp;
+  ArtSVPSeg *seg;
+
+  svp = *p_vp;
+  seg_num = svp->n_segs++;
+  if (*pn_segs_max == seg_num)
+    {
+      *pn_segs_max <<= 1;
+      svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
+                                  (*pn_segs_max - 1) * sizeof(ArtSVPSeg));
+      *p_vp = svp;
+      if (pn_points_max != NULL)
+       *pn_points_max = art_renew (*pn_points_max, int, *pn_segs_max);
+    }
+  seg = &svp->segs[seg_num];
+  seg->n_points = n_points;
+  seg->dir = dir;
+  seg->points = points;
+  if (bbox)
+    seg->bbox = *bbox;
+  else if (points)
+    {
+      double x_min, x_max;
+      int i;
+
+      x_min = x_max = points[0].x;
+      for (i = 1; i < n_points; i++)
+       {
+         if (x_min > points[i].x)
+           x_min = points[i].x;
+         if (x_max < points[i].x)
+           x_max = points[i].x;
+       }
+      seg->bbox.x0 = x_min;
+      seg->bbox.y0 = points[0].y;
+      
+      seg->bbox.x1 = x_max;
+      seg->bbox.y1 = points[n_points - 1].y;
+    }
+  return seg_num;
+}
+
+
+/**
+ * art_svp_free: Free an #ArtSVP structure.
+ * @svp: #ArtSVP to free.
+ * 
+ * Frees an #ArtSVP structure and all the segments in it.
+ **/
+void
+art_svp_free (ArtSVP *svp)
+{
+  int n_segs = svp->n_segs;
+  int i;
+
+  for (i = 0; i < n_segs; i++)
+    art_free (svp->segs[i].points);
+  art_free (svp);
+}
+
+#ifdef ART_USE_NEW_INTERSECTOR
+#define EPSILON 0
+#else
+#define EPSILON 1e-6
+#endif
+
+/**
+ * art_svp_seg_compare: Compare two segments of an svp.
+ * @seg1: First segment to compare.
+ * @seg2: Second segment to compare.
+ * 
+ * Compares two segments of an svp. Return 1 if @seg2 is below or to the
+ * right of @seg1, -1 otherwise.
+ **/
+int
+art_svp_seg_compare (const void *s1, const void *s2)
+{
+  const ArtSVPSeg *seg1 = s1;
+  const ArtSVPSeg *seg2 = s2;
+
+  if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1;
+  else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1;
+  else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1;
+  else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1;
+  else if ((seg1->points[1].x - seg1->points[0].x) *
+          (seg2->points[1].y - seg2->points[0].y) -
+          (seg1->points[1].y - seg1->points[0].y) *
+          (seg2->points[1].x - seg2->points[0].x) > 0) return 1;
+  else return -1;
+}
+
diff --git a/lib/art/art_svp_intersect.c b/lib/art/art_svp_intersect.c
new file mode 100644 (file)
index 0000000..4ece5f4
--- /dev/null
@@ -0,0 +1,1803 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 2001 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.
+ */
+
+/* This file contains a testbed implementation of the new intersection
+   code.
+*/
+
+#include "config.h"
+#include "art_svp_intersect.h"
+
+#include <math.h> /* for sqrt */
+
+/* Sanitychecking verifies the main invariant on every priority queue
+   point. Do not use in production, as it slows things down way too
+   much. */
+#define noSANITYCHECK
+
+/* This can be used in production, to prevent hangs. Eventually, it
+   should not be necessary. */
+#define CHEAP_SANITYCHECK
+
+#define noVERBOSE
+
+#include "art_misc.h"
+
+/* A priority queue - perhaps move to a separate file if it becomes
+   needed somewhere else */
+
+#define ART_PRIQ_USE_HEAP
+
+typedef struct _ArtPriQ ArtPriQ;
+typedef struct _ArtPriPoint ArtPriPoint;
+
+struct _ArtPriQ {
+  int n_items;
+  int n_items_max;
+  ArtPriPoint **items;
+};
+
+struct _ArtPriPoint {
+  double x;
+  double y;
+  void *user_data;
+};
+
+static ArtPriQ *
+art_pri_new (void)
+{
+  ArtPriQ *result = art_new (ArtPriQ, 1);
+
+  result->n_items = 0;
+  result->n_items_max = 16;
+  result->items = art_new (ArtPriPoint *, result->n_items_max);
+  return result;
+}
+
+static void
+art_pri_free (ArtPriQ *pq)
+{
+  art_free (pq->items);
+  art_free (pq);
+}
+
+static art_boolean
+art_pri_empty (ArtPriQ *pq)
+{
+  return pq->n_items == 0;
+}
+
+#ifdef ART_PRIQ_USE_HEAP
+
+/* This heap implementation is based on Vasek Chvatal's course notes:
+   http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */
+
+static void
+art_pri_bubble_up (ArtPriQ *pq, int vacant, ArtPriPoint *missing)
+{
+  ArtPriPoint **items = pq->items;
+  int parent;
+
+  parent = (vacant - 1) >> 1;
+  while (vacant > 0 && (missing->y < items[parent]->y ||
+                       (missing->y == items[parent]->y &&
+                        missing->x < items[parent]->x)))
+    {
+      items[vacant] = items[parent];
+      vacant = parent;
+      parent = (vacant - 1) >> 1;
+    }
+
+  items[vacant] = missing;
+}
+
+static void
+art_pri_insert (ArtPriQ *pq, ArtPriPoint *point)
+{
+  if (pq->n_items == pq->n_items_max)
+    art_expand (pq->items, ArtPriPoint *, pq->n_items_max);
+
+  art_pri_bubble_up (pq, pq->n_items++, point);
+}
+
+static void
+art_pri_sift_down_from_root (ArtPriQ *pq, ArtPriPoint *missing)
+{
+  ArtPriPoint **items = pq->items;
+  int vacant = 0, child = 2;
+  int n = pq->n_items;
+
+  while (child < n)
+    {
+      if (items[child - 1]->y < items[child]->y ||
+         (items[child - 1]->y == items[child]->y &&
+          items[child - 1]->x < items[child]->x))
+       child--;
+      items[vacant] = items[child];
+      vacant = child;
+      child = (vacant + 1) << 1;
+    }
+  if (child == n)
+    {
+      items[vacant] = items[n - 1];
+      vacant = n - 1;
+    }
+
+  art_pri_bubble_up (pq, vacant, missing);
+}
+
+static ArtPriPoint *
+art_pri_choose (ArtPriQ *pq)
+{
+  ArtPriPoint *result = pq->items[0];
+
+  art_pri_sift_down_from_root (pq, pq->items[--pq->n_items]);
+  return result;
+}
+
+#else
+
+/* Choose least point in queue */
+static ArtPriPoint *
+art_pri_choose (ArtPriQ *pq)
+{
+  int i;
+  int best = 0;
+  double best_x, best_y;
+  double y;
+  ArtPriPoint *result;
+
+  if (pq->n_items == 0)
+    return NULL;
+
+  best_x = pq->items[best]->x;
+  best_y = pq->items[best]->y;
+
+  for (i = 1; i < pq->n_items; i++)
+    {
+      y = pq->items[i]->y;
+      if (y < best_y || (y == best_y && pq->items[i]->x < best_x))
+       {
+         best = i;
+         best_x = pq->items[best]->x;
+         best_y = y;
+       }
+    }
+  result = pq->items[best];
+  pq->items[best] = pq->items[--pq->n_items];
+  return result;
+}
+
+static void
+art_pri_insert (ArtPriQ *pq, ArtPriPoint *point)
+{
+  if (pq->n_items == pq->n_items_max)
+    art_expand (pq->items, ArtPriPoint *, pq->n_items_max);
+
+  pq->items[pq->n_items++] = point;
+}
+
+#endif
+
+#ifdef TEST_PRIQ
+
+#include <stdlib.h> /* for rand() */
+#include <stdio.h>
+
+static double
+double_rand (double lo, double hi, int quant)
+{
+  int tmp = rand () / (RAND_MAX * (1.0 / quant)) + 0.5;
+  return lo + tmp * ((hi - lo) / quant);
+}
+
+/*
+ * This custom allocator for priority queue points is here so I can
+ * test speed. It doesn't look like it will be that significant, but
+ * if I want a small improvement later, it's something.
+ */
+
+typedef ArtPriPoint *ArtPriPtPool;
+
+static ArtPriPtPool *
+art_pri_pt_pool_new (void)
+{
+  ArtPriPtPool *result = art_new (ArtPriPtPool, 1);
+  *result = NULL;
+  return result;
+}
+
+static ArtPriPoint *
+art_pri_pt_alloc (ArtPriPtPool *pool)
+{
+  ArtPriPoint *result = *pool;
+  if (result == NULL)
+    return art_new (ArtPriPoint, 1);
+  else
+    {
+      *pool = result->user_data;
+      return result;
+    }
+}
+
+static void
+art_pri_pt_free (ArtPriPtPool *pool, ArtPriPoint *pt)
+{
+  pt->user_data = *pool;
+  *pool = pt;
+}
+
+static void
+art_pri_pt_pool_free (ArtPriPtPool *pool)
+{
+  ArtPriPoint *pt = *pool;
+  while (pt != NULL)
+    {
+      ArtPriPoint *next = pt->user_data;
+      art_free (pt);
+      pt = next;
+    }
+  art_free (pool);
+}
+
+int
+main (int argc, char **argv)
+{
+  ArtPriPtPool *pool = art_pri_pt_pool_new ();
+  ArtPriQ *pq;
+  int i, j;
+  const int n_iter = 1;
+  const int pq_size = 100;
+
+  for (j = 0; j < n_iter; j++)
+    {
+      pq = art_pri_new ();
+
+      for (i = 0; i < pq_size; i++)
+       {
+         ArtPriPoint *pt = art_pri_pt_alloc (pool);
+         pt->x = double_rand (0, 1, 100);
+         pt->y = double_rand (0, 1, 100);
+         pt->user_data = (void *)i;
+         art_pri_insert (pq, pt);
+       }
+
+      while (!art_pri_empty (pq))
+       {
+         ArtPriPoint *pt = art_pri_choose (pq);
+         if (n_iter == 1)
+           printf ("(%g, %g), %d\n", pt->x, pt->y, (int)pt->user_data);
+         art_pri_pt_free (pool, pt);
+       }
+
+      art_pri_free (pq);
+    }
+  art_pri_pt_pool_free (pool);
+  return 0;
+}
+
+#else /* TEST_PRIQ */
+
+/* A virtual class for an "svp writer". A client of this object creates an
+   SVP by repeatedly calling "add segment" and "add point" methods on it.
+*/
+
+typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind;
+
+/* An implementation of the svp writer virtual class that applies the
+   winding rule. */
+
+struct _ArtSvpWriterRewind {
+  ArtSvpWriter super;
+  ArtWindRule rule;
+  ArtSVP *svp;
+  int n_segs_max;
+  int *n_points_max;
+};
+
+static int
+art_svp_writer_rewind_add_segment (ArtSvpWriter *self, int wind_left,
+                                  int delta_wind, double x, double y)
+{
+  ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self;
+  ArtSVP *svp;
+  ArtSVPSeg *seg;
+  art_boolean left_filled, right_filled;
+  int wind_right = wind_left + delta_wind;
+  int seg_num;
+  const int init_n_points_max = 4;
+
+  switch (swr->rule)
+    {
+    case ART_WIND_RULE_NONZERO:
+      left_filled = (wind_left != 0);
+      right_filled = (wind_right != 0);
+      break;
+    case ART_WIND_RULE_INTERSECT:
+      left_filled = (wind_left > 1);
+      right_filled = (wind_right > 1);
+      break;
+    case ART_WIND_RULE_ODDEVEN:
+      left_filled = (wind_left & 1);
+      right_filled = (wind_right & 1);
+      break;
+    case ART_WIND_RULE_POSITIVE:
+      left_filled = (wind_left > 0);
+      right_filled = (wind_right > 0);
+      break;
+    default:
+      art_die ("Unknown wind rule %d\n", swr->rule);
+    }
+  if (left_filled == right_filled)
+    {
+      /* discard segment now */
+#ifdef VERBOSE
+      art_dprint ("swr add_segment: %d += %d (%g, %g) --> -1\n",
+             wind_left, delta_wind, x, y);
+#endif
+      return -1;
+   }
+
+  svp = swr->svp;
+  seg_num = svp->n_segs++;
+  if (swr->n_segs_max == seg_num)
+    {
+      swr->n_segs_max <<= 1;
+      svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
+                                  (swr->n_segs_max - 1) *
+                                  sizeof(ArtSVPSeg));
+      swr->svp = svp;
+      swr->n_points_max = art_renew (swr->n_points_max, int,
+                                    swr->n_segs_max);
+    }
+  seg = &svp->segs[seg_num];
+  seg->n_points = 1;
+  seg->dir = right_filled;
+  swr->n_points_max[seg_num] = init_n_points_max;
+  seg->bbox.x0 = x;
+  seg->bbox.y0 = y;
+  seg->bbox.x1 = x;
+  seg->bbox.y1 = y;
+  seg->points = art_new (ArtPoint, init_n_points_max);
+  seg->points[0].x = x;
+  seg->points[0].y = y;
+#ifdef VERBOSE
+    art_dprint ("swr add_segment: %d += %d (%g, %g) --> %d(%s)\n",
+           wind_left, delta_wind, x, y, seg_num,
+           seg->dir ? "v" : "^");
+#endif
+  return seg_num;
+}
+
+static void
+art_svp_writer_rewind_add_point (ArtSvpWriter *self, int seg_id,
+                                double x, double y)
+{
+  ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self;
+  ArtSVPSeg *seg;
+  int n_points;
+
+#ifdef VERBOSE
+  art_dprint ("swr add_point: %d (%g, %g)\n", seg_id, x, y);
+#endif
+  if (seg_id < 0)
+    /* omitted segment */
+    return;
+
+  seg = &swr->svp->segs[seg_id];
+  n_points = seg->n_points++;
+  if (swr->n_points_max[seg_id] == n_points)
+    art_expand (seg->points, ArtPoint, swr->n_points_max[seg_id]);
+  seg->points[n_points].x = x;
+  seg->points[n_points].y = y;
+  if (x < seg->bbox.x0)
+    seg->bbox.x0 = x;
+  if (x > seg->bbox.x1)
+    seg->bbox.x1 = x;
+  seg->bbox.y1 = y;
+}
+
+static void
+art_svp_writer_rewind_close_segment (ArtSvpWriter *self, int seg_id)
+{
+  /* Not needed for this simple implementation. A potential future
+     optimization is to merge segments that can be merged safely. */
+#ifdef SANITYCHECK
+  ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self;
+  ArtSVPSeg *seg;
+
+  if (seg_id >= 0)
+    {
+      seg = &swr->svp->segs[seg_id];
+      if (seg->n_points < 2)
+       art_warn ("*** closing segment %d with only %d point%s\n",
+                 seg_id, seg->n_points, seg->n_points == 1 ? "" : "s");
+    }
+#endif
+
+#ifdef VERBOSE
+  art_dprint ("swr close_segment: %d\n", seg_id);
+#endif
+}
+
+ArtSVP *
+art_svp_writer_rewind_reap (ArtSvpWriter *self)
+{
+  ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self;
+  ArtSVP *result = swr->svp;
+
+  art_free (swr->n_points_max);
+  art_free (swr);
+  return result;
+}
+
+ArtSvpWriter *
+art_svp_writer_rewind_new (ArtWindRule rule)
+{
+  ArtSvpWriterRewind *result = art_new (ArtSvpWriterRewind, 1);
+
+  result->super.add_segment = art_svp_writer_rewind_add_segment;
+  result->super.add_point = art_svp_writer_rewind_add_point;
+  result->super.close_segment = art_svp_writer_rewind_close_segment;
+
+  result->rule = rule;
+  result->n_segs_max = 16;
+  result->svp = art_alloc (sizeof(ArtSVP) + 
+                          (result->n_segs_max - 1) * sizeof(ArtSVPSeg));
+  result->svp->n_segs = 0;
+  result->n_points_max = art_new (int, result->n_segs_max);
+
+  return &result->super;
+}
+
+/* Now, data structures for the active list */
+
+typedef struct _ArtActiveSeg ArtActiveSeg;
+
+/* Note: BNEG is 1 for \ lines, and 0 for /. Thus,
+   x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */
+#define ART_ACTIVE_FLAGS_BNEG 1
+
+/* This flag is set if the segment has been inserted into the active
+   list. */
+#define ART_ACTIVE_FLAGS_IN_ACTIVE 2
+
+/* This flag is set when the segment is to be deleted in the
+   horiz commit process. */
+#define ART_ACTIVE_FLAGS_DEL 4
+
+/* This flag is set if the seg_id is a valid output segment. */
+#define ART_ACTIVE_FLAGS_OUT 8
+
+/* This flag is set if the segment is in the horiz list. */
+#define ART_ACTIVE_FLAGS_IN_HORIZ 16
+
+struct _ArtActiveSeg {
+  int flags;
+  int wind_left, delta_wind;
+  ArtActiveSeg *left, *right; /* doubly linked list structure */
+
+  const ArtSVPSeg *in_seg;
+  int in_curs;
+
+  double x[2];
+  double y0, y1;
+  double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1,
+                    and a>0 */
+
+  /* bottom point and intersection point stack */
+  int n_stack;
+  int n_stack_max;
+  ArtPoint *stack;
+
+  /* horiz commit list */
+  ArtActiveSeg *horiz_left, *horiz_right;
+  double horiz_x;
+  int horiz_delta_wind;
+  int seg_id;
+};
+
+typedef struct _ArtIntersectCtx ArtIntersectCtx;
+
+struct _ArtIntersectCtx {
+  const ArtSVP *in;
+  ArtSvpWriter *out;
+
+  ArtPriQ *pq;
+
+  ArtActiveSeg *active_head;
+
+  double y;
+  ArtActiveSeg *horiz_first;
+  ArtActiveSeg *horiz_last;
+
+  /* segment index of next input segment to be added to pri q */
+  int in_curs;
+};
+
+#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */
+
+/**
+ * art_svp_intersect_setup_seg: Set up an active segment from input segment.
+ * @seg: Active segment.
+ * @pri_pt: Priority queue point to initialize.
+ *
+ * Sets the x[], a, b, c, flags, and stack fields according to the
+ * line from the current cursor value. Sets the priority queue point
+ * to the bottom point of this line. Also advances the input segment
+ * cursor.
+ **/
+static void
+art_svp_intersect_setup_seg (ArtActiveSeg *seg, ArtPriPoint *pri_pt)
+{
+  const ArtSVPSeg *in_seg = seg->in_seg;
+  int in_curs = seg->in_curs++;
+  double x0, y0, x1, y1;
+  double dx, dy, s;
+  double a, b, r2;
+
+  x0 = in_seg->points[in_curs].x;
+  y0 = in_seg->points[in_curs].y;
+  x1 = in_seg->points[in_curs + 1].x;
+  y1 = in_seg->points[in_curs + 1].y;
+  pri_pt->x = x1;
+  pri_pt->y = y1;
+  dx = x1 - x0;
+  dy = y1 - y0;
+  r2 = dx * dx + dy * dy;
+  s = r2 == 0 ? 1 : 1 / sqrt (r2);
+  seg->a = a = dy * s;
+  seg->b = b = -dx * s;
+  seg->c = -(a * x0 + b * y0);
+  seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0);
+  seg->x[0] = x0;
+  seg->x[1] = x1;
+  seg->y0 = y0;
+  seg->y1 = y1;
+  seg->n_stack = 1;
+  seg->stack[0].x = x1;
+  seg->stack[0].y = y1;
+}
+
+/**
+ * art_svp_intersect_add_horiz: Add point to horizontal list.
+ * @ctx: Intersector context.
+ * @seg: Segment with point to insert into horizontal list.
+ *
+ * Inserts @seg into horizontal list, keeping it in ascending horiz_x
+ * order.
+ *
+ * Note: the horiz_commit routine processes "clusters" of segs in the
+ * horiz list, all sharing the same horiz_x value. The cluster is
+ * processed in active list order, rather than horiz list order. Thus,
+ * the order of segs in the horiz list sharing the same horiz_x
+ * _should_ be irrelevant. Even so, we use b as a secondary sorting key,
+ * as a "belt and suspenders" defensive coding tactic.
+ **/
+static void
+art_svp_intersect_add_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg)
+{
+  ArtActiveSeg **pp = &ctx->horiz_last;
+  ArtActiveSeg *place;
+  ArtActiveSeg *place_right = NULL;
+
+
+#ifdef CHEAP_SANITYCHECK
+  if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ)
+    {
+      art_warn ("*** attempt to put segment in horiz list twice\n");
+      return;
+    }
+  seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ;
+#endif
+
+#ifdef VERBOSE
+  art_dprint ("add_horiz %lx, x = %g\n", (unsigned long) seg, seg->horiz_x);
+#endif
+  for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x ||
+                                     (place->horiz_x == seg->horiz_x &&
+                                      place->b < seg->b));
+       place = *pp)
+    {
+      place_right = place;
+      pp = &place->horiz_left;
+    }
+  *pp = seg;
+  seg->horiz_left = place;
+  seg->horiz_right = place_right;
+  if (place == NULL)
+    ctx->horiz_first = seg;
+  else
+    place->horiz_right = seg;
+}
+
+static void
+art_svp_intersect_push_pt (ArtIntersectCtx *ctx, ArtActiveSeg *seg,
+                          double x, double y)
+{
+  ArtPriPoint *pri_pt;
+  int n_stack = seg->n_stack;
+
+  if (n_stack == seg->n_stack_max)
+    art_expand (seg->stack, ArtPoint, seg->n_stack_max);
+  seg->stack[n_stack].x = x;
+  seg->stack[n_stack].y = y;
+  seg->n_stack++;
+
+  seg->x[1] = x;
+  seg->y1 = y;
+
+  pri_pt = art_new (ArtPriPoint, 1);
+  pri_pt->x = x;
+  pri_pt->y = y;
+  pri_pt->user_data = seg;
+  art_pri_insert (ctx->pq, pri_pt);
+}
+
+typedef enum {
+  ART_BREAK_LEFT = 1,
+  ART_BREAK_RIGHT = 2
+} ArtBreakFlags;
+
+/**
+ * art_svp_intersect_break: Break an active segment.
+ *
+ * Note: y must be greater than the top point's y, and less than
+ * the bottom's.
+ *
+ * Return value: x coordinate of break point.
+ */
+static double
+art_svp_intersect_break (ArtIntersectCtx *ctx, ArtActiveSeg *seg,
+                        double x_ref, double y, ArtBreakFlags break_flags)
+{
+  double x0, y0, x1, y1;
+  const ArtSVPSeg *in_seg = seg->in_seg;
+  int in_curs = seg->in_curs;
+  double x;
+
+  x0 = in_seg->points[in_curs - 1].x;
+  y0 = in_seg->points[in_curs - 1].y;
+  x1 = in_seg->points[in_curs].x;
+  y1 = in_seg->points[in_curs].y;
+  x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0));
+  if ((break_flags == ART_BREAK_LEFT && x > x_ref) ||
+      (break_flags == ART_BREAK_RIGHT && x < x_ref))
+    {
+#ifdef VERBOSE
+      art_dprint ("art_svp_intersect_break: limiting x to %f, was %f, %s\n",
+                 x_ref, x, break_flags == ART_BREAK_LEFT ? "left" : "right");
+      x = x_ref;
+#endif
+    }
+
+  /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane
+     arithmetic, but it might be worthwhile to check just in case. */
+
+  if (y > ctx->y)
+    art_svp_intersect_push_pt (ctx, seg, x, y);
+  else
+    {
+      seg->x[0] = x;
+      seg->y0 = y;
+      seg->horiz_x = x;
+      art_svp_intersect_add_horiz (ctx, seg);
+    }
+
+  return x;
+}
+
+/**
+ * art_svp_intersect_add_point: Add a point, breaking nearby neighbors.
+ * @ctx: Intersector context.
+ * @x: X coordinate of point to add.
+ * @y: Y coordinate of point to add.
+ * @seg: "nearby" segment, or NULL if leftmost.
+ *
+ * Return value: Segment immediately to the left of the new point, or
+ * NULL if the new point is leftmost.
+ **/
+static ArtActiveSeg *
+art_svp_intersect_add_point (ArtIntersectCtx *ctx, double x, double y,
+                            ArtActiveSeg *seg, ArtBreakFlags break_flags)
+{
+  ArtActiveSeg *left, *right;
+  double x_min = x, x_max = x;
+  art_boolean left_live, right_live;
+  double d;
+  double new_x;
+  ArtActiveSeg *test, *result = NULL;
+  double x_test;
+
+  left = seg;
+  if (left == NULL)
+    right = ctx->active_head;
+  else
+    right = left->right; 
+  left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL);
+  right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL);
+  while (left_live || right_live)
+    {
+      if (left_live)
+       {
+         if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] &&
+             /* It may be that one of these conjuncts turns out to be always
+                true. We test both anyway, to be defensive. */
+             y != left->y0 && y < left->y1)
+           {
+             d = x_min * left->a + y * left->b + left->c;
+             if (d < EPSILON_A)
+               {
+                 new_x = art_svp_intersect_break (ctx, left, x_min, y,
+                                                  ART_BREAK_LEFT);
+                 if (new_x > x_max)
+                   {
+                     x_max = new_x;
+                     right_live = (right != NULL);
+                   }
+                 else if (new_x < x_min)
+                   x_min = new_x;
+                 left = left->left;
+                 left_live = (left != NULL);
+               }
+             else
+               left_live = ART_FALSE;
+           }
+         else
+           left_live = ART_FALSE;
+       }
+      else if (right_live)
+       {
+         if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] &&
+             /* It may be that one of these conjuncts turns out to be always
+                true. We test both anyway, to be defensive. */
+             y != right->y0 && y < right->y1)
+           {
+             d = x_max * right->a + y * right->b + right->c;
+             if (d > -EPSILON_A)
+               {
+                 new_x = art_svp_intersect_break (ctx, right, x_max, y,
+                                                  ART_BREAK_RIGHT);
+                 if (new_x < x_min)
+                   {
+                     x_min = new_x;
+                     left_live = (left != NULL);
+                   }
+                 else if (new_x >= x_max)
+                   x_max = new_x;
+                 right = right->right;
+                 right_live = (right != NULL);
+               }
+             else
+               right_live = ART_FALSE;
+           }
+         else
+           right_live = ART_FALSE;
+       }
+    }
+
+  /* Ascending order is guaranteed by break_flags. Thus, we don't need
+     to actually fix up non-ascending pairs. */
+
+  /* Now, (left, right) defines an interval of segments broken. Sort
+     into ascending x order. */
+  test = left == NULL ? ctx->active_head : left->right;
+  result = left;
+  if (test != NULL && test != right)
+    {
+      if (y == test->y0)
+       x_test = test->x[0];
+      else /* assert y == test->y1, I think */
+       x_test = test->x[1];
+      for (;;)
+       {
+         if (x_test <= x)
+           result = test;
+         test = test->right;
+         if (test == right)
+           break;
+         new_x = x_test;
+         if (new_x < x_test)
+           {
+             art_warn ("art_svp_intersect_add_point: non-ascending x\n");
+           }
+         x_test = new_x;
+       }
+    }
+  return result;
+}
+
+static void
+art_svp_intersect_swap_active (ArtIntersectCtx *ctx,
+                              ArtActiveSeg *left_seg, ArtActiveSeg *right_seg)
+{
+  right_seg->left = left_seg->left;
+  if (right_seg->left != NULL)
+    right_seg->left->right = right_seg;
+  else
+    ctx->active_head = right_seg;
+  left_seg->right = right_seg->right;
+  if (left_seg->right != NULL)
+    left_seg->right->left = left_seg;
+  left_seg->left = right_seg;
+  right_seg->right = left_seg;
+}
+
+/**
+ * art_svp_intersect_test_cross: Test crossing of a pair of active segments.
+ * @ctx: Intersector context.
+ * @left_seg: Left segment of the pair.
+ * @right_seg: Right segment of the pair.
+ * @break_flags: Flags indicating whether to break neighbors.
+ *
+ * Tests crossing of @left_seg and @right_seg. If there is a crossing,
+ * inserts the intersection point into both segments.
+ *
+ * Return value: True if the intersection took place at the current
+ * scan line, indicating further iteration is needed.
+ **/
+static art_boolean
+art_svp_intersect_test_cross (ArtIntersectCtx *ctx,
+                             ArtActiveSeg *left_seg, ArtActiveSeg *right_seg,
+                             ArtBreakFlags break_flags)
+{
+  double left_x0, left_y0, left_x1;
+  double left_y1 = left_seg->y1;
+  double right_y1 = right_seg->y1;
+  double d;
+
+  const ArtSVPSeg *in_seg;
+  int in_curs;
+  double d0, d1, t;
+  double x, y; /* intersection point */
+
+#ifdef VERBOSE 
+  static int count = 0;
+
+  art_dprint ("art_svp_intersect_test_cross %lx <-> %lx: count=%d\n",
+         (unsigned long)left_seg, (unsigned long)right_seg, count++);
+#endif
+
+  if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0])
+    {
+      /* Top points of left and right segments coincide. This case
+        feels like a bit of duplication - we may want to merge it
+        with the cases below. However, this way, we're sure that this
+        logic makes only localized changes. */
+
+      if (left_y1 < right_y1)
+       {
+         /* Test left (x1, y1) against right segment */
+         double left_x1 = left_seg->x[1];
+
+         if (left_x1 <
+             right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] ||
+             left_y1 == right_seg->y0)
+           return ART_FALSE;
+         d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c;
+         if (d < -EPSILON_A)
+           return ART_FALSE;
+         else if (d < EPSILON_A)
+           {
+             /* I'm unsure about the break flags here. */
+             double right_x1 = art_svp_intersect_break (ctx, right_seg,
+                                                        left_x1, left_y1,
+                                                        ART_BREAK_RIGHT);
+             if (left_x1 <= right_x1)
+               return ART_FALSE;
+           }
+       }
+      else if (left_y1 > right_y1)
+       {
+         /* Test right (x1, y1) against left segment */
+         double right_x1 = right_seg->x[1];
+
+         if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] ||
+             right_y1 == left_seg->y0)
+           return ART_FALSE;
+         d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c;
+         if (d > EPSILON_A)
+           return ART_FALSE;
+         else if (d > -EPSILON_A)
+           {
+             /* See above regarding break flags. */
+             double left_x1 = art_svp_intersect_break (ctx, left_seg,
+                                                       right_x1, right_y1,
+                                                       ART_BREAK_LEFT);
+             if (left_x1 <= right_x1)
+               return ART_FALSE;
+           }
+       }
+      else /* left_y1 == right_y1 */
+       {
+         double left_x1 = left_seg->x[1];
+         double right_x1 = right_seg->x[1];
+
+         if (left_x1 <= right_x1)
+           return ART_FALSE;
+       }
+      art_svp_intersect_swap_active (ctx, left_seg, right_seg);
+      return ART_TRUE;
+    }
+
+  if (left_y1 < right_y1)
+    {
+      /* Test left (x1, y1) against right segment */
+      double left_x1 = left_seg->x[1];
+
+      if (left_x1 <
+         right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] ||
+         left_y1 == right_seg->y0)
+       return ART_FALSE;
+      d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c;
+      if (d < -EPSILON_A)
+       return ART_FALSE;
+      else if (d < EPSILON_A)
+       {
+         double right_x1 = art_svp_intersect_break (ctx, right_seg,
+                                                    left_x1, left_y1,
+                                                    ART_BREAK_RIGHT);
+         if (left_x1 <= right_x1)
+           return ART_FALSE;
+       }
+    }
+  else if (left_y1 > right_y1)
+    {
+      /* Test right (x1, y1) against left segment */
+      double right_x1 = right_seg->x[1];
+
+      if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] ||
+         right_y1 == left_seg->y0)
+       return ART_FALSE;
+      d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c;
+      if (d > EPSILON_A)
+       return ART_FALSE;
+      else if (d > -EPSILON_A)
+       {
+         double left_x1 = art_svp_intersect_break (ctx, left_seg,
+                                                   right_x1, right_y1,
+                                                   ART_BREAK_LEFT);
+         if (left_x1 <= right_x1)
+           return ART_FALSE;
+       }
+    }
+  else /* left_y1 == right_y1 */
+    { 
+      double left_x1 = left_seg->x[1];
+      double right_x1 = right_seg->x[1];
+
+      if (left_x1 <= right_x1)
+       return ART_FALSE;
+    }
+
+  /* The segments cross. Find the intersection point. */
+
+  in_seg = left_seg->in_seg;
+  in_curs = left_seg->in_curs;
+  left_x0 = in_seg->points[in_curs - 1].x;
+  left_y0 = in_seg->points[in_curs - 1].y;
+  left_x1 = in_seg->points[in_curs].x;
+  left_y1 = in_seg->points[in_curs].y;
+  d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c;
+  d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c;
+  if (d0 == d1)
+    {
+      x = left_x0;
+      y = left_y0;
+    }
+  else
+    {
+      /* Is this division always safe? It could possibly overflow. */
+      t = d0 / (d0 - d1);
+      if (t <= 0)
+       {
+         x = left_x0;
+         y = left_y0;
+       }
+      else if (t >= 1)
+       {
+         x = left_x1;
+         y = left_y1;
+       }
+      else
+       {
+         x = left_x0 + t * (left_x1 - left_x0);
+         y = left_y0 + t * (left_y1 - left_y0);
+       }
+    }
+
+  /* Make sure intersection point is within bounds of right seg. */
+  if (y < right_seg->y0)
+    {
+      x = right_seg->x[0];
+      y = right_seg->y0;
+    }
+  else if (y > right_seg->y1)
+    {
+      x = right_seg->x[1];
+      y = right_seg->y1;
+    }
+  else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1])
+    x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1];
+  else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG])
+    x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG];
+
+  if (y == left_seg->y0)
+    {
+      if (y != right_seg->y0)
+       {
+#ifdef VERBOSE
+         art_dprint ("art_svp_intersect_test_cross: intersection (%g, %g) matches former y0 of %lx, %lx\n",
+                   x, y, (unsigned long)left_seg, (unsigned long)right_seg);
+#endif
+         art_svp_intersect_push_pt (ctx, right_seg, x, y);
+         if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL)
+           art_svp_intersect_add_point (ctx, x, y, right_seg->right,
+                                        break_flags);
+       }
+      else
+       {
+         /* Intersection takes place at current scan line; process
+            immediately rather than queueing intersection point into
+            priq. */
+         ArtActiveSeg *winner, *loser;
+
+         /* Choose "most vertical" segement */
+         if (left_seg->a > right_seg->a)
+           {
+             winner = left_seg;
+             loser = right_seg;
+           }
+         else
+           {
+             winner = right_seg;
+             loser = left_seg;
+           }
+
+         loser->x[0] = winner->x[0];
+         loser->horiz_x = loser->x[0];
+         loser->horiz_delta_wind += loser->delta_wind;
+         winner->horiz_delta_wind -= loser->delta_wind;
+
+         art_svp_intersect_swap_active (ctx, left_seg, right_seg);
+         return ART_TRUE;
+       }
+    }
+  else if (y == right_seg->y0)
+    {
+#ifdef VERBOSE
+      art_dprint ("*** art_svp_intersect_test_cross: intersection (%g, %g) matches latter y0 of %lx, %lx\n",
+             x, y, (unsigned long)left_seg, (unsigned long)right_seg);
+#endif
+      art_svp_intersect_push_pt (ctx, left_seg, x, y);
+      if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL)
+       art_svp_intersect_add_point (ctx, x, y, left_seg->left,
+                                    break_flags);
+    }
+  else
+    {
+#ifdef VERBOSE
+      art_dprint ("Inserting (%g, %g) into %lx, %lx\n",
+             x, y, (unsigned long)left_seg, (unsigned long)right_seg);
+#endif
+      /* Insert the intersection point into both segments. */
+      art_svp_intersect_push_pt (ctx, left_seg, x, y);
+      art_svp_intersect_push_pt (ctx, right_seg, x, y);
+      if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL)
+       art_svp_intersect_add_point (ctx, x, y, left_seg->left, break_flags);
+      if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL)
+       art_svp_intersect_add_point (ctx, x, y, right_seg->right, break_flags);
+    }
+  return ART_FALSE;
+}
+
+/**
+ * art_svp_intersect_active_delete: Delete segment from active list.
+ * @ctx: Intersection context.
+ * @seg: Segment to delete.
+ *
+ * Deletes @seg from the active list.
+ **/
+static /* todo inline */ void
+art_svp_intersect_active_delete (ArtIntersectCtx *ctx, ArtActiveSeg *seg)
+{
+  ArtActiveSeg *left = seg->left, *right = seg->right;
+
+  if (left != NULL)
+    left->right = right;
+  else
+    ctx->active_head = right;
+  if (right != NULL)
+    right->left = left;
+}
+
+/**
+ * art_svp_intersect_active_free: Free an active segment.
+ * @seg: Segment to delete.
+ *
+ * Frees @seg.
+ **/
+static /* todo inline */ void
+art_svp_intersect_active_free (ArtActiveSeg *seg)
+{
+  art_free (seg->stack);
+#ifdef VERBOSE
+  art_dprint ("Freeing %lx\n", (unsigned long) seg);
+#endif
+  art_free (seg);
+}
+
+/**
+ * art_svp_intersect_insert_cross: Test crossings of newly inserted line.
+ *
+ * Tests @seg against its left and right neighbors for intersections.
+ * Precondition: the line in @seg is not purely horizontal.
+ **/
+static void
+art_svp_intersect_insert_cross (ArtIntersectCtx *ctx,
+                               ArtActiveSeg *seg)
+{
+  ArtActiveSeg *left = seg, *right = seg;
+
+  for (;;)
+    {
+      if (left != NULL)
+       {
+         ArtActiveSeg *leftc;
+
+         for (leftc = left->left; leftc != NULL; leftc = leftc->left)
+           if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL))
+             break;
+         if (leftc != NULL &&
+             art_svp_intersect_test_cross (ctx, leftc, left,
+                                           ART_BREAK_LEFT))
+           {
+             if (left == right || right == NULL)
+               right = left->right;
+           }
+         else
+           {
+             left = NULL;
+           }
+       }
+      else if (right != NULL && right->right != NULL)
+       {
+         ArtActiveSeg *rightc;
+
+         for (rightc = right->right; rightc != NULL; rightc = rightc->right)
+           if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL))
+             break;
+         if (rightc != NULL &&
+             art_svp_intersect_test_cross (ctx, right, rightc,
+                                           ART_BREAK_RIGHT))
+           {
+             if (left == right || left == NULL)
+               left = right->left;
+           }
+         else
+           {
+             right = NULL;
+           }
+       }
+      else
+       break;
+    }
+}
+
+/**
+ * art_svp_intersect_horiz: Add horizontal line segment.
+ * @ctx: Intersector context.
+ * @seg: Segment on which to add horizontal line.
+ * @x0: Old x position.
+ * @x1: New x position.
+ *
+ * Adds a horizontal line from @x0 to @x1, and updates the current
+ * location of @seg to @x1.
+ **/
+static void
+art_svp_intersect_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg,
+                        double x0, double x1)
+{
+  ArtActiveSeg *hs;
+
+  if (x0 == x1)
+    return;
+
+  hs = art_new (ArtActiveSeg, 1);
+
+  hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT);
+  if (seg->flags & ART_ACTIVE_FLAGS_OUT)
+    {
+      ArtSvpWriter *swr = ctx->out;
+
+      swr->add_point (swr, seg->seg_id, x0, ctx->y);
+    }
+  hs->seg_id = seg->seg_id;
+  hs->horiz_x = x0;
+  hs->horiz_delta_wind = seg->delta_wind;
+  hs->stack = NULL;
+
+  /* Ideally, the (a, b, c) values will never be read. However, there
+     are probably some tests remaining that don't check for _DEL
+     before evaluating the line equation. For those, these
+     initializations will at least prevent a UMR of the values, which
+     can crash on some platforms. */
+  hs->a = 0.0;
+  hs->b = 0.0;
+  hs->c = 0.0;
+
+  seg->horiz_delta_wind -= seg->delta_wind;
+
+  art_svp_intersect_add_horiz (ctx, hs);
+
+  if (x0 > x1)
+    {
+      ArtActiveSeg *left;
+      art_boolean first = ART_TRUE;
+
+      for (left = seg->left; left != NULL; left = seg->left)
+       {
+         int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG;
+
+         if (left->x[left_bneg] <= x1)
+           break;
+         if (left->x[left_bneg ^ 1] <= x1 &&
+             x1 * left->a + ctx->y * left->b + left->c >= 0)
+           break;
+         if (left->y0 != ctx->y && left->y1 != ctx->y)
+           {
+             art_svp_intersect_break (ctx, left, x1, ctx->y, ART_BREAK_LEFT);
+           }
+#ifdef VERBOSE
+         art_dprint ("x0=%g > x1=%g, swapping %lx, %lx\n",
+                 x0, x1, (unsigned long)left, (unsigned long)seg);
+#endif
+         art_svp_intersect_swap_active (ctx, left, seg);
+         if (first && left->right != NULL)
+           {
+             art_svp_intersect_test_cross (ctx, left, left->right,
+                                           ART_BREAK_RIGHT);
+             first = ART_FALSE;
+           }
+       }
+    }
+  else
+    {
+      ArtActiveSeg *right;
+      art_boolean first = ART_TRUE;
+
+      for (right = seg->right; right != NULL; right = seg->right)
+       {
+         int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG;
+
+         if (right->x[right_bneg ^ 1] >= x1)
+           break;
+         if (right->x[right_bneg] >= x1 &&
+             x1 * right->a + ctx->y * right->b + right->c <= 0)
+           break;
+         if (right->y0 != ctx->y && right->y1 != ctx->y)
+           {
+             art_svp_intersect_break (ctx, right, x1, ctx->y,
+                                      ART_BREAK_LEFT);
+           }
+#ifdef VERBOSE
+         art_dprint ("[right]x0=%g < x1=%g, swapping %lx, %lx\n",
+                 x0, x1, (unsigned long)seg, (unsigned long)right);
+#endif
+         art_svp_intersect_swap_active (ctx, seg, right);
+         if (first && right->left != NULL)
+           {
+             art_svp_intersect_test_cross (ctx, right->left, right,
+                                           ART_BREAK_RIGHT);
+             first = ART_FALSE;
+           }
+       }
+    }
+
+  seg->x[0] = x1;
+  seg->x[1] = x1;
+  seg->horiz_x = x1;
+  seg->flags &= ~ART_ACTIVE_FLAGS_OUT;
+}
+
+/**
+ * art_svp_intersect_insert_line: Insert a line into the active list.
+ * @ctx: Intersector context.
+ * @seg: Segment containing line to insert.
+ *
+ * Inserts the line into the intersector context, taking care of any
+ * intersections, and adding the appropriate horizontal points to the
+ * active list.
+ **/
+static void
+art_svp_intersect_insert_line (ArtIntersectCtx *ctx, ArtActiveSeg *seg)
+{
+  if (seg->y1 == seg->y0)
+    {
+#ifdef VERBOSE
+      art_dprint ("art_svp_intersect_insert_line: %lx is horizontal\n",
+             (unsigned long)seg);
+#endif
+      art_svp_intersect_horiz (ctx, seg, seg->x[0], seg->x[1]);
+    }
+  else
+    {
+      art_svp_intersect_insert_cross (ctx, seg);
+      art_svp_intersect_add_horiz (ctx, seg);
+    }
+}
+
+static void
+art_svp_intersect_process_intersection (ArtIntersectCtx *ctx,
+                                       ArtActiveSeg *seg)
+{
+  int n_stack = --seg->n_stack;
+  seg->x[1] = seg->stack[n_stack - 1].x;
+  seg->y1 = seg->stack[n_stack - 1].y;
+  seg->x[0] = seg->stack[n_stack].x;
+  seg->y0 = seg->stack[n_stack].y;
+  seg->horiz_x = seg->x[0];
+  art_svp_intersect_insert_line (ctx, seg);
+}
+
+static void
+art_svp_intersect_advance_cursor (ArtIntersectCtx *ctx, ArtActiveSeg *seg,
+                                 ArtPriPoint *pri_pt)
+{
+  const ArtSVPSeg *in_seg = seg->in_seg;
+  int in_curs = seg->in_curs;
+  ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL;
+
+  if (swr != NULL)
+    swr->add_point (swr, seg->seg_id, seg->x[1], seg->y1);
+  if (in_curs + 1 == in_seg->n_points)
+    {
+      ArtActiveSeg *left = seg->left, *right = seg->right;
+
+#if 0
+      if (swr != NULL)
+       swr->close_segment (swr, seg->seg_id);
+      seg->flags &= ~ART_ACTIVE_FLAGS_OUT;
+#endif
+      seg->flags |= ART_ACTIVE_FLAGS_DEL;
+      art_svp_intersect_add_horiz (ctx, seg);
+      art_svp_intersect_active_delete (ctx, seg);
+      if (left != NULL && right != NULL)
+       art_svp_intersect_test_cross (ctx, left, right,
+                                     ART_BREAK_LEFT | ART_BREAK_RIGHT);
+      art_free (pri_pt);
+    }
+  else
+    {
+      seg->horiz_x = seg->x[1];
+
+      art_svp_intersect_setup_seg (seg, pri_pt);
+      art_pri_insert (ctx->pq, pri_pt);
+      art_svp_intersect_insert_line (ctx, seg);
+    }
+}
+
+static void
+art_svp_intersect_add_seg (ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg)
+{
+  ArtActiveSeg *seg = art_new (ArtActiveSeg, 1);
+  ArtActiveSeg *test;
+  double x0, y0;
+  ArtActiveSeg *beg_range;
+  ArtActiveSeg *last = NULL;
+  ArtActiveSeg *left, *right;
+  ArtPriPoint *pri_pt = art_new (ArtPriPoint, 1);
+
+  seg->flags = 0;
+  seg->in_seg = in_seg;
+  seg->in_curs = 0;
+
+  seg->n_stack_max = 4;
+  seg->stack = art_new (ArtPoint, seg->n_stack_max);
+
+  seg->horiz_delta_wind = 0;
+  
+  seg->wind_left = 0;
+
+  pri_pt->user_data = seg;
+  art_svp_intersect_setup_seg (seg, pri_pt);
+  art_pri_insert (ctx->pq, pri_pt);
+
+  /* Find insertion place for new segment */
+  /* This is currently a left-to-right scan, but should be replaced
+     with a binary search as soon as it's validated. */
+
+  x0 = in_seg->points[0].x;
+  y0 = in_seg->points[0].y;
+  beg_range = NULL;
+  for (test = ctx->active_head; test != NULL; test = test->right)
+    {
+      double d;
+      int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG;
+
+      if (x0 < test->x[test_bneg])
+       {
+         if (x0 < test->x[test_bneg ^ 1])
+           break;
+         d = x0 * test->a + y0 * test->b + test->c;
+         if (d < 0)
+           break;
+       }
+      last = test;
+    }
+
+  left = art_svp_intersect_add_point (ctx, x0, y0, last, ART_BREAK_LEFT | ART_BREAK_RIGHT);
+  seg->left = left;
+  if (left == NULL)
+    {
+      right = ctx->active_head;
+      ctx->active_head = seg;
+    }
+  else
+    {
+      right = left->right;
+      left->right = seg;
+    }
+  seg->right = right;
+  if (right != NULL)
+    right->left = seg;
+
+  seg->delta_wind = in_seg->dir ? 1 : -1;
+  seg->horiz_x = x0;
+
+  art_svp_intersect_insert_line (ctx, seg);
+}
+
+#ifdef SANITYCHECK
+static void
+art_svp_intersect_sanitycheck_winding (ArtIntersectCtx *ctx)
+{
+#if 0
+  /* At this point, we seem to be getting false positives, so it's
+     turned off for now. */
+
+  ArtActiveSeg *seg;
+  int winding_number = 0;
+
+  for (seg = ctx->active_head; seg != NULL; seg = seg->right)
+    {
+      /* Check winding number consistency. */
+      if (seg->flags & ART_ACTIVE_FLAGS_OUT)
+       {
+         if (winding_number != seg->wind_left)
+           art_warn ("*** art_svp_intersect_sanitycheck_winding: seg %lx has wind_left of %d, expected %d\n",
+                 (unsigned long) seg, seg->wind_left, winding_number);
+         winding_number = seg->wind_left + seg->delta_wind;
+       }
+    }
+  if (winding_number != 0)
+    art_warn ("*** art_svp_intersect_sanitycheck_winding: non-balanced winding number %d\n",
+             winding_number);
+#endif
+}
+#endif
+
+/**
+ * art_svp_intersect_horiz_commit: Commit points in horiz list to output.
+ * @ctx: Intersection context.
+ *
+ * The main function of the horizontal commit is to output new
+ * points to the output writer.
+ *
+ * This "commit" pass is also where winding numbers are assigned,
+ * because doing it here provides much greater tolerance for inputs
+ * which are not in strict SVP order.
+ *
+ * Each cluster in the horiz_list contains both segments that are in
+ * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not,
+ * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We
+ * need to deal with both.
+ **/
+static void
+art_svp_intersect_horiz_commit (ArtIntersectCtx *ctx)
+{
+  ArtActiveSeg *seg;
+  int winding_number = 0; /* initialization just to avoid warning */
+  int horiz_wind = 0;
+  double last_x = 0; /* initialization just to avoid warning */
+
+#ifdef VERBOSE
+  art_dprint ("art_svp_intersect_horiz_commit: y=%g\n", ctx->y);
+  for (seg = ctx->horiz_first; seg != NULL; seg = seg->horiz_right)
+    art_dprint (" %lx: %g %+d\n",
+           (unsigned long)seg, seg->horiz_x, seg->horiz_delta_wind);
+#endif
+
+  /* Output points to svp writer. */
+  for (seg = ctx->horiz_first; seg != NULL;)
+    {
+      /* Find a cluster with common horiz_x, */
+      ArtActiveSeg *curs;
+      double x = seg->horiz_x;
+
+      /* Generate any horizontal segments. */
+      if (horiz_wind != 0)
+       {
+         ArtSvpWriter *swr = ctx->out;
+         int seg_id;
+
+         seg_id = swr->add_segment (swr, winding_number, horiz_wind,
+                                    last_x, ctx->y);
+         swr->add_point (swr, seg_id, x, ctx->y);
+         swr->close_segment (swr, seg_id);
+       }
+
+      /* Find first active segment in cluster. */
+
+      for (curs = seg; curs != NULL && curs->horiz_x == x;
+          curs = curs->horiz_right)
+       if (!(curs->flags & ART_ACTIVE_FLAGS_DEL))
+         break;
+
+      if (curs != NULL && curs->horiz_x == x)
+       {
+         /* There exists at least one active segment in this cluster. */
+
+         /* Find beginning of cluster. */
+         for (; curs->left != NULL; curs = curs->left)
+           if (curs->left->horiz_x != x)
+             break;
+
+         if (curs->left != NULL)
+           winding_number = curs->left->wind_left + curs->left->delta_wind;
+         else
+           winding_number = 0;
+
+         do
+           {
+#ifdef VERBOSE
+             art_dprint (" curs %lx: winding_number = %d += %d\n",
+                     (unsigned long)curs, winding_number, curs->delta_wind);
+#endif
+             if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) ||
+                 curs->wind_left != winding_number)
+               {
+                 ArtSvpWriter *swr = ctx->out;
+
+                 if (curs->flags & ART_ACTIVE_FLAGS_OUT)
+                   {
+                     swr->add_point (swr, curs->seg_id,
+                                     curs->horiz_x, ctx->y);
+                     swr->close_segment (swr, curs->seg_id);
+                   }
+
+                 curs->seg_id = swr->add_segment (swr, winding_number,
+                                                  curs->delta_wind,
+                                                  x, ctx->y);
+                 curs->flags |= ART_ACTIVE_FLAGS_OUT;
+               }
+             curs->wind_left = winding_number;
+             winding_number += curs->delta_wind;
+             curs = curs->right;
+           }
+         while (curs != NULL && curs->horiz_x == x);
+       }
+
+      /* Skip past cluster. */
+      do
+       {
+         ArtActiveSeg *next = seg->horiz_right;
+
+         seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ;
+         horiz_wind += seg->horiz_delta_wind;
+         seg->horiz_delta_wind = 0;
+         if (seg->flags & ART_ACTIVE_FLAGS_DEL)
+           {
+             if (seg->flags & ART_ACTIVE_FLAGS_OUT)
+               {
+                 ArtSvpWriter *swr = ctx->out;
+                 swr->close_segment (swr, seg->seg_id);
+               }
+             art_svp_intersect_active_free (seg);
+           }
+         seg = next;
+       }
+      while (seg != NULL && seg->horiz_x == x);
+
+      last_x = x;
+    }
+  ctx->horiz_first = NULL;
+  ctx->horiz_last = NULL;
+#ifdef SANITYCHECK
+  art_svp_intersect_sanitycheck_winding (ctx);
+#endif
+}
+
+#ifdef VERBOSE
+static void
+art_svp_intersect_print_active (ArtIntersectCtx *ctx)
+{
+  ArtActiveSeg *seg;
+
+  art_dprint ("Active list (y = %g):\n", ctx->y);
+  for (seg = ctx->active_head; seg != NULL; seg = seg->right)
+    {
+      art_dprint (" %lx: (%g, %g)-(%g, %g), (a, b, c) = (%g, %g, %g)\n",
+             (unsigned long)seg,
+             seg->x[0], seg->y0, seg->x[1], seg->y1,
+             seg->a, seg->b, seg->c);
+    }
+}
+#endif
+
+#ifdef SANITYCHECK
+static void
+art_svp_intersect_sanitycheck (ArtIntersectCtx *ctx)
+{
+  ArtActiveSeg *seg;
+  ArtActiveSeg *last = NULL;
+  double d;
+
+  for (seg = ctx->active_head; seg != NULL; seg = seg->right)
+    {
+      if (seg->left != last)
+       {
+         art_warn ("*** art_svp_intersect_sanitycheck: last=%lx, seg->left=%lx\n",
+                   (unsigned long)last, (unsigned long)seg->left);
+       }
+      if (last != NULL)
+       {
+         /* pairwise compare with previous seg */
+
+         /* First the top. */
+         if (last->y0 < seg->y0)
+           {
+           }
+         else
+           {
+           }
+
+         /* Then the bottom. */
+         if (last->y1 < seg->y1)
+           {
+             if (!((last->x[1] <
+                    seg->x[(seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) ||
+                   last->y1 == seg->y0))
+               {
+                 d = last->x[1] * seg->a + last->y1 * seg->b + seg->c;
+                 if (d >= -EPSILON_A)
+                   art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to right (d = %g)\n",
+                             last->x[1], last->y1, (unsigned long) last,
+                             (unsigned long) seg, d);
+               }
+           }
+         else if (last->y1 > seg->y1)
+
+           {
+             if (!((seg->x[1] >
+                    last->x[last->flags & ART_ACTIVE_FLAGS_BNEG]) ||
+                   seg->y1 == last->y0))
+             {
+               d = seg->x[1] * last->a + seg->y1 * last->b + last->c;
+               if (d <= EPSILON_A)
+                 art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to left (d = %g)\n",
+                             seg->x[1], seg->y1, (unsigned long) seg,
+                             (unsigned long) last, d);
+             }
+           }
+         else
+           {
+             if (last->x[1] > seg->x[1])
+               art_warn ("*** bottoms (%g, %g) of %lx and (%g, %g) of %lx out of order\n",
+                         last->x[1], last->y1, (unsigned long)last,
+                         seg->x[1], seg->y1, (unsigned long)seg);
+           }
+       }
+      last = seg;
+    }
+}
+#endif
+
+void
+art_svp_intersector (const ArtSVP *in, ArtSvpWriter *out)
+{
+  ArtIntersectCtx *ctx;
+  ArtPriQ *pq;
+  ArtPriPoint *first_point;
+#ifdef VERBOSE
+  int count = 0;
+#endif
+
+  if (in->n_segs == 0)
+    return;
+
+  ctx = art_new (ArtIntersectCtx, 1);
+  ctx->in = in;
+  ctx->out = out;
+  pq = art_pri_new ();
+  ctx->pq = pq;
+
+  ctx->active_head = NULL;
+
+  ctx->horiz_first = NULL;
+  ctx->horiz_last = NULL;
+
+  ctx->in_curs = 0;
+  first_point = art_new (ArtPriPoint, 1);
+  first_point->x = in->segs[0].points[0].x;
+  first_point->y = in->segs[0].points[0].y;
+  first_point->user_data = NULL;
+  ctx->y = first_point->y;
+  art_pri_insert (pq, first_point);
+
+  while (!art_pri_empty (pq))
+    {
+      ArtPriPoint *pri_point = art_pri_choose (pq);
+      ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data;
+
+#ifdef VERBOSE
+      art_dprint ("\nIntersector step %d\n", count++);
+      art_svp_intersect_print_active (ctx);
+      art_dprint ("priq choose (%g, %g) %lx\n", pri_point->x, pri_point->y,
+             (unsigned long)pri_point->user_data);
+#endif
+#ifdef SANITYCHECK
+      art_svp_intersect_sanitycheck(ctx);
+#endif
+
+      if (ctx->y != pri_point->y)
+       {
+         art_svp_intersect_horiz_commit (ctx);
+         ctx->y = pri_point->y;
+       }
+
+      if (seg == NULL)
+       {
+         /* Insert new segment from input */
+         const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++];
+         art_svp_intersect_add_seg (ctx, in_seg);
+         if (ctx->in_curs < in->n_segs)
+           {
+             const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs];
+             pri_point->x = next_seg->points[0].x;
+             pri_point->y = next_seg->points[0].y;
+             /* user_data is already NULL */
+             art_pri_insert (pq, pri_point);
+           }
+         else
+           art_free (pri_point);
+       }
+      else
+       {
+         int n_stack = seg->n_stack;
+
+         if (n_stack > 1)
+           {
+             art_svp_intersect_process_intersection (ctx, seg);
+             art_free (pri_point);
+           }
+         else
+           {
+             art_svp_intersect_advance_cursor (ctx, seg, pri_point);
+           }
+       }
+    }
+
+  art_svp_intersect_horiz_commit (ctx);
+
+  art_pri_free (pq);
+  art_free (ctx);
+}
+
+#endif /* not TEST_PRIQ */
diff --git a/lib/art/art_svp_ops.c b/lib/art/art_svp_ops.c
new file mode 100644 (file)
index 0000000..5bb837c
--- /dev/null
@@ -0,0 +1,401 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+#define noVERBOSE
+
+/* Vector path set operations, over sorted vpaths. */
+
+#include "config.h"
+#include "art_svp_ops.h"
+
+#include "art_misc.h"
+
+#include "art_svp.h"
+#include "art_vpath.h"
+#include "art_svp_vpath.h"
+#include "art_svp.h"
+#ifdef ART_USE_NEW_INTERSECTOR
+#include "art_svp_intersect.h"
+#else
+#include "art_svp_wind.h"
+#endif
+#include "art_vpath_svp.h"
+
+/* Merge the segments of the two svp's. The resulting svp will share
+   segments with args passed in, so be super-careful with the
+   allocation.  */
+/**
+ * art_svp_merge: Merge the segments of two svp's.
+ * @svp1: One svp to merge.
+ * @svp2: The other svp to merge.
+ *
+ * Merges the segments of two SVP's into a new one. The resulting
+ * #ArtSVP data structure will share the segments of the argument
+ * svp's, so it is probably a good idea to free it shallowly,
+ * especially if the arguments will be freed with art_svp_free().
+ *
+ * Return value: The merged #ArtSVP.
+ **/
+static ArtSVP *
+art_svp_merge (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+  ArtSVP *svp_new;
+  int ix;
+  int ix1, ix2;
+
+  svp_new = (ArtSVP *)art_alloc (sizeof(ArtSVP) +
+                                (svp1->n_segs + svp2->n_segs - 1) *
+                                sizeof(ArtSVPSeg));
+  ix1 = 0;
+  ix2 = 0;
+  for (ix = 0; ix < svp1->n_segs + svp2->n_segs; ix++)
+    {
+      if (ix1 < svp1->n_segs &&
+         (ix2 == svp2->n_segs ||
+          art_svp_seg_compare (&svp1->segs[ix1], &svp2->segs[ix2]) < 1))
+       svp_new->segs[ix] = svp1->segs[ix1++];
+      else
+       svp_new->segs[ix] = svp2->segs[ix2++];
+    }
+
+  svp_new->n_segs = ix;
+  return svp_new;
+}
+
+#ifdef VERBOSE
+
+#define XOFF 50
+#define YOFF 700
+
+static void
+print_ps_vpath (ArtVpath *vpath)
+{
+  int i;
+
+  printf ("gsave %d %d translate 1 -1 scale\n", XOFF, YOFF);
+  for (i = 0; vpath[i].code != ART_END; i++)
+    {
+      switch (vpath[i].code)
+       {
+       case ART_MOVETO:
+         printf ("%g %g moveto\n", vpath[i].x, vpath[i].y);
+         break;
+       case ART_LINETO:
+         printf ("%g %g lineto\n", vpath[i].x, vpath[i].y);
+         break;
+       default:
+         break;
+       }
+    }
+  printf ("stroke grestore showpage\n");
+}
+
+#define DELT 4
+
+static void
+print_ps_svp (ArtSVP *vpath)
+{
+  int i, j;
+
+  printf ("%% begin\n");
+  for (i = 0; i < vpath->n_segs; i++)
+    {
+      printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0);
+      for (j = 0; j < vpath->segs[i].n_points; j++)
+       {
+         printf ("%g %g %s\n",
+                 XOFF + vpath->segs[i].points[j].x,
+                 YOFF - vpath->segs[i].points[j].y,
+                 j ? "lineto" : "moveto");
+       }
+      printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n",
+             XOFF + vpath->segs[i].points[0].x - DELT,
+             YOFF - DELT - vpath->segs[i].points[0].y,
+             XOFF + vpath->segs[i].points[0].x - DELT,
+             YOFF - vpath->segs[i].points[0].y,
+             XOFF + vpath->segs[i].points[0].x + DELT,
+             YOFF - vpath->segs[i].points[0].y,
+             XOFF + vpath->segs[i].points[0].x + DELT,
+             YOFF - DELT - vpath->segs[i].points[0].y);
+      printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n",
+             XOFF + vpath->segs[i].points[j - 1].x - DELT,
+             YOFF + DELT - vpath->segs[i].points[j - 1].y,
+             XOFF + vpath->segs[i].points[j - 1].x - DELT,
+             YOFF - vpath->segs[i].points[j - 1].y,
+             XOFF + vpath->segs[i].points[j - 1].x + DELT,
+             YOFF - vpath->segs[i].points[j - 1].y,
+             XOFF + vpath->segs[i].points[j - 1].x + DELT,
+             YOFF + DELT - vpath->segs[i].points[j - 1].y);
+      printf ("stroke\n");
+    }
+
+  printf ("showpage\n");
+}
+#endif
+
+#ifndef ART_USE_NEW_INTERSECTOR
+static ArtSVP *
+art_svp_merge_perturbed (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+  ArtVpath *vpath1, *vpath2;
+  ArtVpath *vpath1_p, *vpath2_p;
+  ArtSVP *svp1_p, *svp2_p;
+  ArtSVP *svp_new;
+
+  vpath1 = art_vpath_from_svp (svp1);
+  vpath1_p = art_vpath_perturb (vpath1);
+  art_free (vpath1);
+  svp1_p = art_svp_from_vpath (vpath1_p);
+  art_free (vpath1_p);
+
+  vpath2 = art_vpath_from_svp (svp2);
+  vpath2_p = art_vpath_perturb (vpath2);
+  art_free (vpath2);
+  svp2_p = art_svp_from_vpath (vpath2_p);
+  art_free (vpath2_p);
+
+  svp_new = art_svp_merge (svp1_p, svp2_p);
+#ifdef VERBOSE
+  print_ps_svp (svp1_p);
+  print_ps_svp (svp2_p);
+  print_ps_svp (svp_new);
+#endif
+  art_free (svp1_p);
+  art_free (svp2_p);
+
+  return svp_new;
+}
+#endif
+
+/* Compute the union of two vector paths.
+
+   Status of this routine:
+
+   Basic correctness: Seems to work.
+
+   Numerical stability: We cheat (adding random perturbation). Thus,
+   it seems very likely that no numerical stability problems will be
+   seen in practice.
+
+   Speed: Would be better if we didn't go to unsorted vector path
+   and back to add the perturbation.
+
+   Precision: The perturbation fuzzes the coordinates slightly. In
+   cases of butting segments, razor thin long holes may appear.
+
+*/
+/**
+ * art_svp_union: Compute the union of two sorted vector paths.
+ * @svp1: One sorted vector path.
+ * @svp2: The other sorted vector path.
+ *
+ * Computes the union of the two argument svp's. Given two svp's with
+ * winding numbers of 0 and 1 everywhere, the resulting winding number
+ * will be 1 where either (or both) of the argument svp's has a
+ * winding number 1, 0 otherwise. The result is newly allocated.
+ *
+ * Currently, this routine has accuracy problems pending the
+ * implementation of the new intersector.
+ *
+ * Return value: The union of @svp1 and @svp2.
+ **/
+ArtSVP *
+art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+#ifdef ART_USE_NEW_INTERSECTOR 
+  ArtSVP *svp3, *svp_new;
+  ArtSvpWriter *swr;
+
+  svp3 = art_svp_merge (svp1, svp2);
+  swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE);
+  art_svp_intersector (svp3, swr);
+  svp_new = art_svp_writer_rewind_reap (swr);
+  art_free (svp3); /* shallow free because svp3 contains shared segments */
+
+  return svp_new;
+#else
+  ArtSVP *svp3, *svp4, *svp_new;
+
+  svp3 = art_svp_merge_perturbed (svp1, svp2);
+  svp4 = art_svp_uncross (svp3);
+  art_svp_free (svp3);
+
+  svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_POSITIVE);
+#ifdef VERBOSE
+  print_ps_svp (svp4);
+  print_ps_svp (svp_new);
+#endif
+  art_svp_free (svp4);
+  return svp_new;
+#endif
+}
+
+/* Compute the intersection of two vector paths.
+
+   Status of this routine:
+
+   Basic correctness: Seems to work.
+
+   Numerical stability: We cheat (adding random perturbation). Thus,
+   it seems very likely that no numerical stability problems will be
+   seen in practice.
+
+   Speed: Would be better if we didn't go to unsorted vector path
+   and back to add the perturbation.
+
+   Precision: The perturbation fuzzes the coordinates slightly. In
+   cases of butting segments, razor thin long isolated segments may
+   appear.
+
+*/
+
+/**
+ * art_svp_intersect: Compute the intersection of two sorted vector paths.
+ * @svp1: One sorted vector path.
+ * @svp2: The other sorted vector path.
+ *
+ * Computes the intersection of the two argument svp's. Given two
+ * svp's with winding numbers of 0 and 1 everywhere, the resulting
+ * winding number will be 1 where both of the argument svp's has a
+ * winding number 1, 0 otherwise. The result is newly allocated.
+ *
+ * Currently, this routine has accuracy problems pending the
+ * implementation of the new intersector.
+ *
+ * Return value: The intersection of @svp1 and @svp2.
+ **/
+ArtSVP *
+art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+#ifdef ART_USE_NEW_INTERSECTOR 
+  ArtSVP *svp3, *svp_new;
+  ArtSvpWriter *swr;
+
+  svp3 = art_svp_merge (svp1, svp2);
+  swr = art_svp_writer_rewind_new (ART_WIND_RULE_INTERSECT);
+  art_svp_intersector (svp3, swr);
+  svp_new = art_svp_writer_rewind_reap (swr);
+  art_free (svp3); /* shallow free because svp3 contains shared segments */
+
+  return svp_new;
+#else
+  ArtSVP *svp3, *svp4, *svp_new;
+
+  svp3 = art_svp_merge_perturbed (svp1, svp2);
+  svp4 = art_svp_uncross (svp3);
+  art_svp_free (svp3);
+
+  svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_INTERSECT);
+  art_svp_free (svp4);
+  return svp_new;
+#endif
+}
+
+/* Compute the symmetric difference of two vector paths.
+
+   Status of this routine:
+
+   Basic correctness: Seems to work.
+
+   Numerical stability: We cheat (adding random perturbation). Thus,
+   it seems very likely that no numerical stability problems will be
+   seen in practice.
+
+   Speed: We could do a lot better by scanning through the svp
+   representations and culling out any segments that are exactly
+   identical. It would also be better if we didn't go to unsorted
+   vector path and back to add the perturbation.
+
+   Precision: Awful. In the case of inputs which are similar (the
+   common case for canvas display), the entire outline is "hairy." In
+   addition, the perturbation fuzzes the coordinates slightly. It can
+   be used as a conservative approximation.
+
+*/
+
+/**
+ * art_svp_diff: Compute the symmetric difference of two sorted vector paths.
+ * @svp1: One sorted vector path.
+ * @svp2: The other sorted vector path.
+ *
+ * Computes the symmetric of the two argument svp's. Given two svp's
+ * with winding numbers of 0 and 1 everywhere, the resulting winding
+ * number will be 1 where either, but not both, of the argument svp's
+ * has a winding number 1, 0 otherwise. The result is newly allocated.
+ *
+ * Currently, this routine has accuracy problems pending the
+ * implementation of the new intersector.
+ *
+ * Return value: The symmetric difference of @svp1 and @svp2.
+ **/
+ArtSVP *
+art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+#ifdef ART_USE_NEW_INTERSECTOR 
+  ArtSVP *svp3, *svp_new;
+  ArtSvpWriter *swr;
+
+  svp3 = art_svp_merge (svp1, svp2);
+  swr = art_svp_writer_rewind_new (ART_WIND_RULE_ODDEVEN);
+  art_svp_intersector (svp3, swr);
+  svp_new = art_svp_writer_rewind_reap (swr);
+  art_free (svp3); /* shallow free because svp3 contains shared segments */
+
+  return svp_new;
+#else
+  ArtSVP *svp3, *svp4, *svp_new;
+
+  svp3 = art_svp_merge_perturbed (svp1, svp2);
+  svp4 = art_svp_uncross (svp3);
+  art_svp_free (svp3);
+
+  svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_ODDEVEN);
+  art_svp_free (svp4);
+  return svp_new;
+#endif
+}
+
+#ifdef ART_USE_NEW_INTERSECTOR 
+ArtSVP *
+art_svp_minus (const ArtSVP *svp1, const ArtSVP *svp2)
+{
+  ArtSVP *svp2_mod;
+  ArtSVP *svp3, *svp_new;
+  ArtSvpWriter *swr;
+  int i;
+
+  svp2_mod = (ArtSVP *) svp2; /* get rid of the const for a while */
+
+  /* First invert svp2 to "turn it inside out" */
+  for (i = 0; i < svp2_mod->n_segs; i++)
+    svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir;
+
+  svp3 = art_svp_merge (svp1, svp2_mod);
+  swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE);
+  art_svp_intersector (svp3, swr);
+  svp_new = art_svp_writer_rewind_reap (swr);
+  art_free (svp3); /* shallow free because svp3 contains shared segments */
+
+  /* Flip svp2 back to its original state */
+  for (i = 0; i < svp2_mod->n_segs; i++)
+    svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir;
+
+  return svp_new;
+}
+#endif /* ART_USE_NEW_INTERSECTOR */
diff --git a/lib/art/art_svp_point.c b/lib/art/art_svp_point.c
new file mode 100644 (file)
index 0000000..4b41e8c
--- /dev/null
@@ -0,0 +1,144 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1999 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 "config.h"
+#include "art_svp_point.h"
+
+#include <math.h>
+#include "art_misc.h"
+
+#include "art_svp.h"
+
+/* Determine whether a point is inside, or near, an svp. */
+
+/* return winding number of point wrt svp */
+/**
+ * art_svp_point_wind: Determine winding number of a point with respect to svp.
+ * @svp: The svp.
+ * @x: The X coordinate of the point.
+ * @y: The Y coordinate of the point.
+ *
+ * Determine the winding number of the point @x, @y with respect to @svp.
+ *
+ * Return value: the winding number.
+ **/
+int
+art_svp_point_wind (ArtSVP *svp, double x, double y)
+{
+  int i, j;
+  int wind = 0;
+
+  for (i = 0; i < svp->n_segs; i++)
+    {
+      ArtSVPSeg *seg = &svp->segs[i];
+
+      if (seg->bbox.y0 > y)
+       break;
+
+      if (seg->bbox.y1 > y)
+       {
+         if (seg->bbox.x1 < x)
+           wind += seg->dir ? 1 : -1;
+         else if (seg->bbox.x0 <= x)
+           {
+             double x0, y0, x1, y1, dx, dy;
+
+             for (j = 0; j < seg->n_points - 1; j++)
+               {
+                 if (seg->points[j + 1].y > y)
+                   break;
+               }
+             x0 = seg->points[j].x;
+             y0 = seg->points[j].y;
+             x1 = seg->points[j + 1].x;
+             y1 = seg->points[j + 1].y;
+
+             dx = x1 - x0;
+             dy = y1 - y0;
+             if ((x - x0) * dy > (y - y0) * dx)
+               wind += seg->dir ? 1 : -1;
+           }
+       }
+    }
+
+  return wind;
+}
+
+/**
+ * art_svp_point_dist: Determine distance between point and svp.
+ * @svp: The svp.
+ * @x: The X coordinate of the point.
+ * @y: The Y coordinate of the point.
+ *
+ * Determines the distance of the point @x, @y to the closest edge in
+ * @svp. A large number is returned if @svp is empty.
+ *
+ * Return value: the distance.
+ **/
+double
+art_svp_point_dist (ArtSVP *svp, double x, double y)
+{
+  int i, j;
+  double dist_sq;
+  double best_sq = -1;
+
+  for (i = 0; i < svp->n_segs; i++)
+    {
+      ArtSVPSeg *seg = &svp->segs[i];
+      for (j = 0; j < seg->n_points - 1; j++)
+       {
+         double x0 = seg->points[j].x;
+         double y0 = seg->points[j].y;
+         double x1 = seg->points[j + 1].x;
+         double y1 = seg->points[j + 1].y;
+
+         double dx = x1 - x0;
+         double dy = y1 - y0;
+
+         double dxx0 = x - x0;
+         double dyy0 = y - y0;
+
+         double dot = dxx0 * dx + dyy0 * dy;
+
+         if (dot < 0)
+           dist_sq = dxx0 * dxx0 + dyy0 * dyy0;
+         else
+           {
+             double rr = dx * dx + dy * dy;
+
+             if (dot > rr)
+               dist_sq = (x - x1) * (x - x1) + (y - y1) * (y - y1);
+             else
+               {
+                 double perp = (y - y0) * dx - (x - x0) * dy;
+
+                 dist_sq = perp * perp / rr;
+               }
+           }
+         if (best_sq < 0 || dist_sq < best_sq)
+           best_sq = dist_sq;
+       }
+    }
+
+  if (best_sq >= 0)
+    return sqrt (best_sq);
+  else
+    return 1e12;
+}
+
diff --git a/lib/art/art_svp_render_aa.c b/lib/art/art_svp_render_aa.c
new file mode 100644 (file)
index 0000000..d696a51
--- /dev/null
@@ -0,0 +1,463 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* The spiffy antialiased renderer for sorted vector paths. */
+
+#include "config.h"
+#include "art_svp_render_aa.h"
+
+#include <math.h>
+#include <string.h> /* for memmove */
+#include "art_misc.h"
+
+#include "art_rect.h"
+#include "art_svp.h"
+
+#include <stdio.h>
+
+typedef double artfloat;
+
+struct _ArtSVPRenderAAIter {
+  const ArtSVP *svp;
+  int x0, x1;
+  int y;
+  int seg_ix;
+
+  int *active_segs;
+  int n_active_segs;
+  int *cursor;
+  artfloat *seg_x;
+  artfloat *seg_dx;
+
+  ArtSVPRenderAAStep *steps;
+};
+
+static void
+art_svp_render_insert_active (int i, int *active_segs, int n_active_segs,
+                             artfloat *seg_x, artfloat *seg_dx)
+{
+  int j;
+  artfloat x;
+  int tmp1, tmp2;
+
+  /* this is a cheap hack to get ^'s sorted correctly */
+  x = seg_x[i] + 0.001 * seg_dx[i];
+  for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++);
+
+  tmp1 = i;
+  while (j < n_active_segs)
+    {
+      tmp2 = active_segs[j];
+      active_segs[j] = tmp1;
+      tmp1 = tmp2;
+      j++;
+    }
+  active_segs[j] = tmp1;
+}
+
+static void
+art_svp_render_delete_active (int *active_segs, int j, int n_active_segs)
+{
+  int k;
+
+  for (k = j; k < n_active_segs; k++)
+    active_segs[k] = active_segs[k + 1];
+}
+
+#define EPSILON 1e-6
+
+/* Render the sorted vector path in the given rectangle, antialiased.
+
+   This interface uses a callback for the actual pixel rendering. The
+   callback is called y1 - y0 times (once for each scan line). The y
+   coordinate is given as an argument for convenience (it could be
+   stored in the callback's private data and incremented on each
+   call).
+
+   The rendered polygon is represented in a semi-runlength format: a
+   start value and a sequence of "steps". Each step has an x
+   coordinate and a value delta. The resulting value at position x is
+   equal to the sum of the start value and all step delta values for
+   which the step x coordinate is less than or equal to x. An
+   efficient algorithm will traverse the steps left to right, keeping
+   a running sum.
+
+   All x coordinates in the steps are guaranteed to be x0 <= x < x1.
+   (This guarantee is a change from the gfonted vpaar renderer, and is
+   designed to simplify the callback).
+
+   There is now a further guarantee that no two steps will have the
+   same x value. This may allow for further speedup and simplification
+   of renderers.
+
+   The value 0x8000 represents 0% coverage by the polygon, while
+   0xff8000 represents 100% coverage. This format is designed so that
+   >> 16 results in a standard 0x00..0xff value range, with nice
+   rounding.
+
+   Status of this routine:
+
+   Basic correctness: OK
+
+   Numerical stability: pretty good, although probably not
+   bulletproof.
+
+   Speed: Needs more aggressive culling of bounding boxes.  Can
+   probably speed up the [x0,x1) clipping of step values.  Can do more
+   of the step calculation in fixed point.
+
+   Precision: No known problems, although it should be tested
+   thoroughly, especially for symmetry.
+
+*/
+
+ArtSVPRenderAAIter *
+art_svp_render_aa_iter (const ArtSVP *svp,
+                       int x0, int y0, int x1, int y1)
+{
+  ArtSVPRenderAAIter *iter = art_new (ArtSVPRenderAAIter, 1);
+
+  iter->svp = svp;
+  iter->y = y0;
+  iter->x0 = x0;
+  iter->x1 = x1;
+  iter->seg_ix = 0;
+
+  iter->active_segs = art_new (int, svp->n_segs);
+  iter->cursor = art_new (int, svp->n_segs);
+  iter->seg_x = art_new (artfloat, svp->n_segs);
+  iter->seg_dx = art_new (artfloat, svp->n_segs);
+  iter->steps = art_new (ArtSVPRenderAAStep, x1 - x0);
+  iter->n_active_segs = 0;
+
+  return iter;
+}
+
+#define ADD_STEP(xpos, xdelta)                          \
+  /* stereotype code fragment for adding a step */      \
+  if (n_steps == 0 || steps[n_steps - 1].x < xpos)      \
+    {                                                   \
+      sx = n_steps;                                     \
+      steps[sx].x = xpos;                               \
+      steps[sx].delta = xdelta;                         \
+      n_steps++;                                        \
+    }                                                   \
+  else                                                  \
+    {                                                   \
+      for (sx = n_steps; sx > 0; sx--)                  \
+       {                                               \
+         if (steps[sx - 1].x == xpos)                  \
+           {                                           \
+             steps[sx - 1].delta += xdelta;            \
+             sx = n_steps;                             \
+             break;                                    \
+           }                                           \
+         else if (steps[sx - 1].x < xpos)              \
+           {                                           \
+             break;                                    \
+           }                                           \
+       }                                               \
+      if (sx < n_steps)                                 \
+       {                                               \
+         memmove (&steps[sx + 1], &steps[sx],          \
+                  (n_steps - sx) * sizeof(steps[0]));  \
+         steps[sx].x = xpos;                           \
+         steps[sx].delta = xdelta;                     \
+         n_steps++;                                    \
+       }                                               \
+    }
+
+void
+art_svp_render_aa_iter_step (ArtSVPRenderAAIter *iter, int *p_start,
+                            ArtSVPRenderAAStep **p_steps, int *p_n_steps)
+{
+  const ArtSVP *svp = iter->svp;
+  int *active_segs = iter->active_segs;
+  int n_active_segs = iter->n_active_segs;
+  int *cursor = iter->cursor;
+  artfloat *seg_x = iter->seg_x;
+  artfloat *seg_dx = iter->seg_dx;
+  int i = iter->seg_ix;
+  int j;
+  int x0 = iter->x0;
+  int x1 = iter->x1;
+  int y = iter->y;
+  int seg_index;
+
+  int x;
+  ArtSVPRenderAAStep *steps = iter->steps;
+  int n_steps;
+  artfloat y_top, y_bot;
+  artfloat x_top, x_bot;
+  artfloat x_min, x_max;
+  int ix_min, ix_max;
+  artfloat delta; /* delta should be int too? */
+  int last, this;
+  int xdelta;
+  artfloat rslope, drslope;
+  int start;
+  const ArtSVPSeg *seg;
+  int curs;
+  artfloat dy;
+
+  int sx;
+  
+  /* insert new active segments */
+  for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++)
+    {
+      if (svp->segs[i].bbox.y1 > y &&
+         svp->segs[i].bbox.x0 < x1)
+       {
+         seg = &svp->segs[i];
+         /* move cursor to topmost vector which overlaps [y,y+1) */
+         for (curs = 0; seg->points[curs + 1].y < y; curs++);
+         cursor[i] = curs;
+         dy = seg->points[curs + 1].y - seg->points[curs].y;
+         if (fabs (dy) >= EPSILON)
+           seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) /
+             dy;
+         else
+           seg_dx[i] = 1e12;
+         seg_x[i] = seg->points[curs].x +
+           (y - seg->points[curs].y) * seg_dx[i];
+         art_svp_render_insert_active (i, active_segs, n_active_segs++,
+                                       seg_x, seg_dx);
+       }
+    }
+
+  n_steps = 0;
+
+  /* render the runlengths, advancing and deleting as we go */
+  start = 0x8000;
+
+  for (j = 0; j < n_active_segs; j++)
+    {
+      seg_index = active_segs[j];
+      seg = &svp->segs[seg_index];
+      curs = cursor[seg_index];
+      while (curs != seg->n_points - 1 &&
+            seg->points[curs].y < y + 1)
+       {
+         y_top = y;
+         if (y_top < seg->points[curs].y)
+           y_top = seg->points[curs].y;
+         y_bot = y + 1;
+         if (y_bot > seg->points[curs + 1].y)
+           y_bot = seg->points[curs + 1].y;
+         if (y_top != y_bot) {
+           delta = (seg->dir ? 16711680.0 : -16711680.0) *
+             (y_bot - y_top);
+           x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index];
+           x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index];
+           if (x_top < x_bot)
+             {
+               x_min = x_top;
+               x_max = x_bot;
+             }
+           else
+             {
+               x_min = x_bot;
+               x_max = x_top;
+             }
+           ix_min = floor (x_min);
+           ix_max = floor (x_max);
+           if (ix_min >= x1)
+             {
+               /* skip; it starts to the right of the render region */
+             }
+           else if (ix_max < x0)
+             /* it ends to the left of the render region */
+             start += delta;
+           else if (ix_min == ix_max)
+             {
+               /* case 1, antialias a single pixel */
+               xdelta = (ix_min + 1 - (x_min + x_max) * 0.5) * delta;
+
+               ADD_STEP(ix_min, xdelta)
+
+               if (ix_min + 1 < x1)
+                 {
+                   xdelta = delta - xdelta;
+
+                   ADD_STEP(ix_min + 1, xdelta)
+                 }
+             }
+           else
+             {
+               /* case 2, antialias a run */
+               rslope = 1.0 / fabs (seg_dx[seg_index]);
+               drslope = delta * rslope;
+               last =
+                 drslope * 0.5 *
+                 (ix_min + 1 - x_min) * (ix_min + 1 - x_min);
+               xdelta = last;
+               if (ix_min >= x0)
+                 {
+                   ADD_STEP(ix_min, xdelta)
+                   
+                   x = ix_min + 1;
+                 }
+               else
+                 {
+                   start += last;
+                   x = x0;
+                 }
+               if (ix_max > x1)
+                 ix_max = x1;
+               for (; x < ix_max; x++)
+                 {
+                   this = (seg->dir ? 16711680.0 : -16711680.0) * rslope *
+                     (x + 0.5 - x_min);
+                   xdelta = this - last;
+                   last = this;
+
+                   ADD_STEP(x, xdelta)
+                 }
+               if (x < x1)
+                 {
+                   this =
+                     delta * (1 - 0.5 *
+                              (x_max - ix_max) * (x_max - ix_max) *
+                              rslope);
+                   xdelta = this - last;
+                   last = this;
+
+                   ADD_STEP(x, xdelta)
+                   
+                   if (x + 1 < x1)
+                     {
+                       xdelta = delta - last;
+
+                       ADD_STEP(x + 1, xdelta)
+                     }
+                 }
+             }
+         }
+         curs++;
+         if (curs != seg->n_points - 1 &&
+             seg->points[curs].y < y + 1)
+           {
+             dy = seg->points[curs + 1].y - seg->points[curs].y;
+             if (fabs (dy) >= EPSILON)
+               seg_dx[seg_index] = (seg->points[curs + 1].x -
+                                    seg->points[curs].x) / dy;
+             else
+               seg_dx[seg_index] = 1e12;
+             seg_x[seg_index] = seg->points[curs].x +
+               (y - seg->points[curs].y) * seg_dx[seg_index];
+           }
+         /* break here, instead of duplicating predicate in while? */
+       }
+      if (seg->points[curs].y >= y + 1)
+       {
+         curs--;
+         cursor[seg_index] = curs;
+         seg_x[seg_index] += seg_dx[seg_index];
+       }
+      else
+       {
+         art_svp_render_delete_active (active_segs, j--,
+                                       --n_active_segs);
+       }
+    }
+
+  *p_start = start;
+  *p_steps = steps;
+  *p_n_steps = n_steps;
+
+  iter->seg_ix = i;
+  iter->n_active_segs = n_active_segs;
+  iter->y++;
+}
+
+void
+art_svp_render_aa_iter_done (ArtSVPRenderAAIter *iter)
+{
+  art_free (iter->steps);
+
+  art_free (iter->seg_dx);
+  art_free (iter->seg_x);
+  art_free (iter->cursor);
+  art_free (iter->active_segs);
+  art_free (iter);
+}
+
+/**
+ * art_svp_render_aa: Render SVP antialiased.
+ * @svp: The #ArtSVP to render.
+ * @x0: Left coordinate of destination rectangle.
+ * @y0: Top coordinate of destination rectangle.
+ * @x1: Right coordinate of destination rectangle.
+ * @y1: Bottom coordinate of destination rectangle.
+ * @callback: The callback which actually paints the pixels.
+ * @callback_data: Private data for @callback.
+ *
+ * Renders the sorted vector path in the given rectangle, antialiased.
+ *
+ * This interface uses a callback for the actual pixel rendering. The
+ * callback is called @y1 - @y0 times (once for each scan line). The y
+ * coordinate is given as an argument for convenience (it could be
+ * stored in the callback's private data and incremented on each
+ * call).
+ *
+ * The rendered polygon is represented in a semi-runlength format: a
+ * start value and a sequence of "steps". Each step has an x
+ * coordinate and a value delta. The resulting value at position x is
+ * equal to the sum of the start value and all step delta values for
+ * which the step x coordinate is less than or equal to x. An
+ * efficient algorithm will traverse the steps left to right, keeping
+ * a running sum.
+ *
+ * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1.
+ * (This guarantee is a change from the gfonted vpaar renderer from
+ * which this routine is derived, and is designed to simplify the
+ * callback).
+ *
+ * The value 0x8000 represents 0% coverage by the polygon, while
+ * 0xff8000 represents 100% coverage. This format is designed so that
+ * >> 16 results in a standard 0x00..0xff value range, with nice
+ * rounding.
+ * 
+ **/
+void
+art_svp_render_aa (const ArtSVP *svp,
+                  int x0, int y0, int x1, int y1,
+                  void (*callback) (void *callback_data,
+                                    int y,
+                                    int start,
+                                    ArtSVPRenderAAStep *steps, int n_steps),
+                  void *callback_data)
+{
+  ArtSVPRenderAAIter *iter;
+  int y;
+  int start;
+  ArtSVPRenderAAStep *steps;
+  int n_steps;
+
+  iter = art_svp_render_aa_iter (svp, x0, y0, x1, y1);
+
+
+  for (y = y0; y < y1; y++)
+    {
+      art_svp_render_aa_iter_step (iter, &start, &steps, &n_steps);
+      (*callback) (callback_data, y, start, steps, n_steps);
+    }
+
+  art_svp_render_aa_iter_done (iter);
+}
diff --git a/lib/art/art_svp_vpath.c b/lib/art/art_svp_vpath.c
new file mode 100644 (file)
index 0000000..196711a
--- /dev/null
@@ -0,0 +1,215 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* Sort vector paths into sorted vector paths */
+
+#include "config.h"
+#include "art_svp_vpath.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "art_misc.h"
+
+#include "art_vpath.h"
+#include "art_svp.h"
+
+
+/* reverse a list of points in place */
+static void
+reverse_points (ArtPoint *points, int n_points)
+{
+  int i;
+  ArtPoint tmp_p;
+
+  for (i = 0; i < (n_points >> 1); i++)
+    {
+      tmp_p = points[i];
+      points[i] = points[n_points - (i + 1)];
+      points[n_points - (i + 1)] = tmp_p;
+    }
+}
+
+/**
+ * art_svp_from_vpath: Convert a vpath to a sorted vector path.
+ * @vpath: #ArtVPath to convert.
+ *
+ * Converts a vector path into sorted vector path form. The svp form is
+ * more efficient for rendering and other vector operations.
+ *
+ * Basically, the implementation is to traverse the vector path,
+ * generating a new segment for each "run" of points in the vector
+ * path with monotonically increasing Y values. All the resulting
+ * values are then sorted.
+ *
+ * Note: I'm not sure that the sorting rule is correct with respect
+ * to numerical stability issues.
+ *
+ * Return value: Resulting sorted vector path.
+ **/
+ArtSVP *
+art_svp_from_vpath (ArtVpath *vpath)
+{
+  int n_segs, n_segs_max;
+  ArtSVP *svp;
+  int dir;
+  int new_dir;
+  int i;
+  ArtPoint *points;
+  int n_points, n_points_max;
+  double x, y;
+  double x_min, x_max;
+
+  n_segs = 0;
+  n_segs_max = 16;
+  svp = (ArtSVP *)art_alloc (sizeof(ArtSVP) +
+                            (n_segs_max - 1) * sizeof(ArtSVPSeg));
+
+  dir = 0;
+  n_points = 0;
+  n_points_max = 0;
+  points = NULL;
+  i = 0;
+
+  x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant,
+               but it makes gcc -Wall -ansi -pedantic happier */
+  x_min = x_max = 0; /* same */
+
+  while (vpath[i].code != ART_END) {
+    if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN)
+      {
+       if (points != NULL && n_points >= 2)
+         {
+           if (n_segs == n_segs_max)
+             {
+               n_segs_max <<= 1;
+               svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
+                                            (n_segs_max - 1) *
+                                            sizeof(ArtSVPSeg));
+             }
+           svp->segs[n_segs].n_points = n_points;
+           svp->segs[n_segs].dir = (dir > 0);
+           if (dir < 0)
+             reverse_points (points, n_points);
+           svp->segs[n_segs].points = points;
+           svp->segs[n_segs].bbox.x0 = x_min;
+           svp->segs[n_segs].bbox.x1 = x_max;
+           svp->segs[n_segs].bbox.y0 = points[0].y;
+           svp->segs[n_segs].bbox.y1 = points[n_points - 1].y;
+           n_segs++;
+           points = NULL;
+         }
+
+       if (points == NULL)
+         {
+           n_points_max = 4;
+           points = art_new (ArtPoint, n_points_max);
+         }
+
+       n_points = 1;
+       points[0].x = x = vpath[i].x;
+       points[0].y = y = vpath[i].y;
+       x_min = x;
+       x_max = x;
+       dir = 0;
+      }
+    else /* must be LINETO */
+      {
+       new_dir = (vpath[i].y > y ||
+                  (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1;
+       if (dir && dir != new_dir)
+         {
+           /* new segment */
+           x = points[n_points - 1].x;
+           y = points[n_points - 1].y;
+           if (n_segs == n_segs_max)
+             {
+               n_segs_max <<= 1;
+               svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
+                                            (n_segs_max - 1) *
+                                            sizeof(ArtSVPSeg));
+             }
+           svp->segs[n_segs].n_points = n_points;
+           svp->segs[n_segs].dir = (dir > 0);
+           if (dir < 0)
+             reverse_points (points, n_points);
+           svp->segs[n_segs].points = points;
+           svp->segs[n_segs].bbox.x0 = x_min;
+           svp->segs[n_segs].bbox.x1 = x_max;
+           svp->segs[n_segs].bbox.y0 = points[0].y;
+           svp->segs[n_segs].bbox.y1 = points[n_points - 1].y;
+           n_segs++;
+
+           n_points = 1;
+           n_points_max = 4;
+           points = art_new (ArtPoint, n_points_max);
+           points[0].x = x;
+           points[0].y = y;
+           x_min = x;
+           x_max = x;
+         }
+
+       if (points != NULL)
+         {
+           if (n_points == n_points_max)
+             art_expand (points, ArtPoint, n_points_max);
+           points[n_points].x = x = vpath[i].x;
+           points[n_points].y = y = vpath[i].y;
+           if (x < x_min) x_min = x;
+           else if (x > x_max) x_max = x;
+           n_points++;
+         }
+       dir = new_dir;
+      }
+    i++;
+  }
+
+  if (points != NULL)
+    {
+      if (n_points >= 2)
+       {
+         if (n_segs == n_segs_max)
+           {
+             n_segs_max <<= 1;
+             svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
+                                          (n_segs_max - 1) *
+                                          sizeof(ArtSVPSeg));
+           }
+         svp->segs[n_segs].n_points = n_points;
+         svp->segs[n_segs].dir = (dir > 0);
+         if (dir < 0)
+           reverse_points (points, n_points);
+         svp->segs[n_segs].points = points;
+         svp->segs[n_segs].bbox.x0 = x_min;
+         svp->segs[n_segs].bbox.x1 = x_max;
+         svp->segs[n_segs].bbox.y0 = points[0].y;
+         svp->segs[n_segs].bbox.y1 = points[n_points - 1].y;
+         n_segs++;
+       }
+      else
+       art_free (points);
+    }
+
+  svp->n_segs = n_segs;
+
+  qsort (&svp->segs, n_segs, sizeof (ArtSVPSeg), art_svp_seg_compare);
+
+  return svp;
+}
+
diff --git a/lib/art/art_svp_vpath_stroke.c b/lib/art/art_svp_vpath_stroke.c
new file mode 100644 (file)
index 0000000..bf3111b
--- /dev/null
@@ -0,0 +1,741 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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 "config.h"
+#include "art_svp_vpath_stroke.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "art_misc.h"
+
+#include "art_vpath.h"
+#include "art_svp.h"
+#ifdef ART_USE_NEW_INTERSECTOR
+#include "art_svp_intersect.h"
+#else
+#include "art_svp_wind.h"
+#endif
+#include "art_svp_vpath.h"
+
+#define EPSILON 1e-6
+#define EPSILON_2 1e-12
+
+#define yes_OPTIMIZE_INNER
+
+/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1,
+   yc + y1), centered at (xc, yc), and with given radius. Both x0^2 +
+   y0^2 and x1^2 + y1^2 should be equal to radius^2.
+
+   A positive value of radius means curve to the left, negative means
+   curve to the right.
+*/
+static void
+art_svp_vpath_stroke_arc (ArtVpath **p_vpath, int *pn, int *pn_max,
+                         double xc, double yc,
+                         double x0, double y0,
+                         double x1, double y1,
+                         double radius,
+                         double flatness)
+{
+  double theta;
+  double th_0, th_1;
+  int n_pts;
+  int i;
+  double aradius;
+
+  aradius = fabs (radius);
+  theta = 2 * M_SQRT2 * sqrt (flatness / aradius);
+  th_0 = atan2 (y0, x0);
+  th_1 = atan2 (y1, x1);
+  if (radius > 0)
+    {
+      /* curve to the left */
+      if (th_0 < th_1) th_0 += M_PI * 2;
+      n_pts = ceil ((th_0 - th_1) / theta);
+    }
+  else
+    {
+      /* curve to the right */
+      if (th_1 < th_0) th_1 += M_PI * 2;
+      n_pts = ceil ((th_1 - th_0) / theta);
+    }
+#ifdef VERBOSE
+  printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta);
+#endif
+  art_vpath_add_point (p_vpath, pn, pn_max,
+                      ART_LINETO, xc + x0, yc + y0);
+  for (i = 1; i < n_pts; i++)
+    {
+      theta = th_0 + (th_1 - th_0) * i / n_pts;
+      art_vpath_add_point (p_vpath, pn, pn_max,
+                          ART_LINETO, xc + cos (theta) * aradius,
+                          yc + sin (theta) * aradius);
+#ifdef VERBOSE
+      printf ("mid %f %f\n", cos (theta) * radius, sin (theta) * radius);
+#endif
+    }
+  art_vpath_add_point (p_vpath, pn, pn_max,
+                      ART_LINETO, xc + x1, yc + y1);
+#ifdef VERBOSE
+  printf ("end %f %f\n", x1, y1);
+#endif
+}
+
+/* Assume that forw and rev are at point i0. Bring them to i1,
+   joining with the vector i1 - i2.
+
+   This used to be true, but isn't now that the stroke_raw code is
+   filtering out (near)zero length vectors: {It so happens that all
+   invocations of this function maintain the precondition i1 = i0 + 1,
+   so we could decrease the number of arguments by one. We haven't
+   done that here, though.}
+
+   forw is to the line's right and rev is to its left.
+
+   Precondition: no zero-length vectors, otherwise a divide by
+   zero will happen.  */
+static void
+render_seg (ArtVpath **p_forw, int *pn_forw, int *pn_forw_max,
+           ArtVpath **p_rev, int *pn_rev, int *pn_rev_max,
+           ArtVpath *vpath, int i0, int i1, int i2,
+           ArtPathStrokeJoinType join,
+           double line_width, double miter_limit, double flatness)
+{
+  double dx0, dy0;
+  double dx1, dy1;
+  double dlx0, dly0;
+  double dlx1, dly1;
+  double dmx, dmy;
+  double dmr2;
+  double scale;
+  double cross;
+
+#ifdef VERBOSE
+  printf ("join style = %d\n", join);
+#endif
+
+  /* The vectors of the lines from i0 to i1 and i1 to i2. */
+  dx0 = vpath[i1].x - vpath[i0].x;
+  dy0 = vpath[i1].y - vpath[i0].y;
+
+  dx1 = vpath[i2].x - vpath[i1].x;
+  dy1 = vpath[i2].y - vpath[i1].y;
+
+  /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
+     90 degrees, and scaled to the length of line_width. */
+  scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
+  dlx0 = dy0 * scale;
+  dly0 = -dx0 * scale;
+
+  /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise
+     90 degrees, and scaled to the length of line_width. */
+  scale = line_width / sqrt (dx1 * dx1 + dy1 * dy1);
+  dlx1 = dy1 * scale;
+  dly1 = -dx1 * scale;
+
+#ifdef VERBOSE
+  printf ("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n",
+         vpath[i0].x, vpath[i0].y,
+         vpath[i1].x, vpath[i1].y,
+         vpath[i2].x, vpath[i2].y);
+
+  printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n",
+         dx0, dy0, dlx0, dly0);
+
+  printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n",
+         dx1, dy1, dlx1, dly1);
+#endif
+
+  /* now, forw's last point is expected to be colinear along d[xy]0
+     to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */
+
+  /* positive for positive area (i.e. left turn) */
+  cross = dx1 * dy0 - dx0 * dy1;
+
+  dmx = (dlx0 + dlx1) * 0.5;
+  dmy = (dly0 + dly1) * 0.5;
+  dmr2 = dmx * dmx + dmy * dmy;
+
+  if (join == ART_PATH_STROKE_JOIN_MITER &&
+      dmr2 * miter_limit * miter_limit < line_width * line_width)
+    join = ART_PATH_STROKE_JOIN_BEVEL;
+
+  /* the case when dmr2 is zero or very small bothers me
+     (i.e. near a 180 degree angle)
+     ALEX: So, we avoid the optimization when dmr2 is very small. This should
+     be safe since dmx/y is only used in optimization and in MITER case, and MITER
+     should be converted to BEVEL when dmr2 is very small. */
+  if (dmr2 > EPSILON_2)
+    {
+      scale = line_width * line_width / dmr2;
+      dmx *= scale;
+      dmy *= scale;
+    }
+
+  if (cross * cross < EPSILON_2 && dx0 * dx1 + dy0 * dy1 >= 0)
+    {
+      /* going straight */
+#ifdef VERBOSE
+      printf ("%% render_seg: straight\n");
+#endif
+      art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                      ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
+      art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                      ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
+    }
+  else if (cross > 0)
+    {
+      /* left turn, forw is outside and rev is inside */
+
+#ifdef VERBOSE
+      printf ("%% render_seg: left\n");
+#endif
+      if (
+#ifdef NO_OPTIMIZE_INNER
+         0 &&
+#endif
+         (dmr2 > EPSILON_2) &&
+         /* check that i1 + dm[xy] is inside i0-i1 rectangle */
+         (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 &&
+         /* and that i1 + dm[xy] is inside i1-i2 rectangle */
+         ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0)
+#ifdef PEDANTIC_INNER
+         &&
+         /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */
+         (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 &&
+         /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */
+         ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0)
+#endif
+         )
+       {
+         /* can safely add single intersection point */
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
+       }
+      else
+       {
+         /* need to loop-de-loop the inside */
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x, vpath[i1].y);
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
+       }
+
+      if (join == ART_PATH_STROKE_JOIN_BEVEL)
+       {
+         /* bevel */
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
+       }
+      else if (join == ART_PATH_STROKE_JOIN_MITER)
+       {
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
+       }
+      else if (join == ART_PATH_STROKE_JOIN_ROUND) {
+       art_svp_vpath_stroke_arc (p_forw, pn_forw, pn_forw_max,
+                                 vpath[i1].x, vpath[i1].y,
+                                 -dlx0, -dly0,
+                                 -dlx1, -dly1,
+                                 line_width,
+                                 flatness);
+      }
+    }
+  else
+    {
+      /* right turn, rev is outside and forw is inside */
+#ifdef VERBOSE
+      printf ("%% render_seg: right\n");
+#endif
+
+      if (
+#ifdef NO_OPTIMIZE_INNER
+         0 &&
+#endif
+         (dmr2 > EPSILON_2) &&
+         /* check that i1 - dm[xy] is inside i0-i1 rectangle */
+         (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 &&
+         /* and that i1 - dm[xy] is inside i1-i2 rectangle */
+         ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0)
+#ifdef PEDANTIC_INNER
+         &&
+         /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */
+         (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 &&
+         /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */
+         ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0)
+#endif
+         )
+       {
+         /* can safely add single intersection point */
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
+       }
+      else
+       {
+         /* need to loop-de-loop the inside */
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x, vpath[i1].y);
+         art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
+                          ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
+       }
+
+      if (join == ART_PATH_STROKE_JOIN_BEVEL)
+       {
+         /* bevel */
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
+       }
+      else if (join == ART_PATH_STROKE_JOIN_MITER)
+       {
+         art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
+                          ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
+       }
+      else if (join == ART_PATH_STROKE_JOIN_ROUND) {
+       art_svp_vpath_stroke_arc (p_rev, pn_rev, pn_rev_max,
+                                 vpath[i1].x, vpath[i1].y,
+                                 dlx0, dly0,
+                                 dlx1, dly1,
+                                 -line_width,
+                                 flatness);
+      }
+
+    }
+}
+
+/* caps i1, under the assumption of a vector from i0 */
+static void
+render_cap (ArtVpath **p_result, int *pn_result, int *pn_result_max,
+           ArtVpath *vpath, int i0, int i1,
+           ArtPathStrokeCapType cap, double line_width, double flatness)
+{
+  double dx0, dy0;
+  double dlx0, dly0;
+  double scale;
+  int n_pts;
+  int i;
+
+  dx0 = vpath[i1].x - vpath[i0].x;
+  dy0 = vpath[i1].y - vpath[i0].y;
+
+  /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
+     90 degrees, and scaled to the length of line_width. */
+  scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
+  dlx0 = dy0 * scale;
+  dly0 = -dx0 * scale;
+
+#ifdef VERBOSE
+  printf ("cap style = %d\n", cap);
+#endif
+
+  switch (cap)
+    {
+    case ART_PATH_STROKE_CAP_BUTT:
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
+      break;
+    case ART_PATH_STROKE_CAP_ROUND:
+      n_pts = ceil (M_PI / (2.0 * M_SQRT2 * sqrt (flatness / line_width)));
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
+      for (i = 1; i < n_pts; i++)
+       {
+         double theta, c_th, s_th;
+
+         theta = M_PI * i / n_pts;
+         c_th = cos (theta);
+         s_th = sin (theta);
+         art_vpath_add_point (p_result, pn_result, pn_result_max,
+                              ART_LINETO,
+                              vpath[i1].x - dlx0 * c_th - dly0 * s_th,
+                              vpath[i1].y - dly0 * c_th + dlx0 * s_th);
+       }
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
+      break;
+    case ART_PATH_STROKE_CAP_SQUARE:
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO,
+                          vpath[i1].x - dlx0 - dly0,
+                          vpath[i1].y - dly0 + dlx0);
+      art_vpath_add_point (p_result, pn_result, pn_result_max,
+                          ART_LINETO,
+                          vpath[i1].x + dlx0 - dly0,
+                          vpath[i1].y + dly0 + dlx0);
+      break;
+    }
+}
+
+/**
+ * art_svp_from_vpath_raw: Stroke a vector path, raw version
+ * @vpath: #ArtVPath to stroke.
+ * @join: Join style.
+ * @cap: Cap style.
+ * @line_width: Width of stroke.
+ * @miter_limit: Miter limit.
+ * @flatness: Flatness.
+ *
+ * Exactly the same as art_svp_vpath_stroke(), except that the resulting
+ * stroke outline may self-intersect and have regions of winding number
+ * greater than 1.
+ *
+ * Return value: Resulting raw stroked outline in svp format.
+ **/
+ArtVpath *
+art_svp_vpath_stroke_raw (ArtVpath *vpath,
+                         ArtPathStrokeJoinType join,
+                         ArtPathStrokeCapType cap,
+                         double line_width,
+                         double miter_limit,
+                         double flatness)
+{
+  int begin_idx, end_idx;
+  int i;
+  ArtVpath *forw, *rev;
+  int n_forw, n_rev;
+  int n_forw_max, n_rev_max;
+  ArtVpath *result;
+  int n_result, n_result_max;
+  double half_lw = 0.5 * line_width;
+  int closed;
+  int last, this, next, second;
+  double dx, dy;
+
+  n_forw_max = 16;
+  forw = art_new (ArtVpath, n_forw_max);
+
+  n_rev_max = 16;
+  rev = art_new (ArtVpath, n_rev_max);
+
+  n_result = 0;
+  n_result_max = 16;
+  result = art_new (ArtVpath, n_result_max);
+
+  for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx)
+    {
+      n_forw = 0;
+      n_rev = 0;
+
+      closed = (vpath[begin_idx].code == ART_MOVETO);
+
+      /* we don't know what the first point joins with until we get to the
+        last point and see if it's closed. So we start with the second
+        line in the path.
+
+        Note: this is not strictly true (we now know it's closed from
+        the opening pathcode), but why fix code that isn't broken?
+      */
+
+      this = begin_idx;
+      /* skip over identical points at the beginning of the subpath */
+      for (i = this + 1; vpath[i].code == ART_LINETO; i++)
+       {
+         dx = vpath[i].x - vpath[this].x;
+         dy = vpath[i].y - vpath[this].y;
+         if (dx * dx + dy * dy > EPSILON_2)
+           break;
+       }
+      next = i;
+      second = next;
+
+      /* invariant: this doesn't coincide with next */
+      while (vpath[next].code == ART_LINETO)
+       {
+         last = this;
+         this = next;
+         /* skip over identical points after the beginning of the subpath */
+         for (i = this + 1; vpath[i].code == ART_LINETO; i++)
+           {
+             dx = vpath[i].x - vpath[this].x;
+             dy = vpath[i].y - vpath[this].y;
+             if (dx * dx + dy * dy > EPSILON_2)
+               break;
+           }
+         next = i;
+         if (vpath[next].code != ART_LINETO)
+           {
+             /* reached end of path */
+             /* make "closed" detection conform to PostScript
+                semantics (i.e. explicit closepath code rather than
+                just the fact that end of the path is the beginning) */
+             if (closed &&
+                 vpath[this].x == vpath[begin_idx].x &&
+                 vpath[this].y == vpath[begin_idx].y)
+               {
+                 int j;
+
+                 /* path is closed, render join to beginning */
+                 render_seg (&forw, &n_forw, &n_forw_max,
+                             &rev, &n_rev, &n_rev_max,
+                             vpath, last, this, second,
+                             join, half_lw, miter_limit, flatness);
+
+#ifdef VERBOSE
+                 printf ("%% forw %d, rev %d\n", n_forw, n_rev);
+#endif
+                 /* do forward path */
+                 art_vpath_add_point (&result, &n_result, &n_result_max,
+                                  ART_MOVETO, forw[n_forw - 1].x,
+                                  forw[n_forw - 1].y);
+                 for (j = 0; j < n_forw; j++)
+                   art_vpath_add_point (&result, &n_result, &n_result_max,
+                                    ART_LINETO, forw[j].x,
+                                    forw[j].y);
+
+                 /* do reverse path, reversed */
+                 art_vpath_add_point (&result, &n_result, &n_result_max,
+                                  ART_MOVETO, rev[0].x,
+                                  rev[0].y);
+                 for (j = n_rev - 1; j >= 0; j--)
+                   art_vpath_add_point (&result, &n_result, &n_result_max,
+                                    ART_LINETO, rev[j].x,
+                                    rev[j].y);
+               }
+             else
+               {
+                 /* path is open */
+                 int j;
+
+                 /* add to forw rather than result to ensure that
+                    forw has at least one point. */
+                 render_cap (&forw, &n_forw, &n_forw_max,
+                             vpath, last, this,
+                             cap, half_lw, flatness);
+                 art_vpath_add_point (&result, &n_result, &n_result_max,
+                                  ART_MOVETO, forw[0].x,
+                                  forw[0].y);
+                 for (j = 1; j < n_forw; j++)
+                   art_vpath_add_point (&result, &n_result, &n_result_max,
+                                    ART_LINETO, forw[j].x,
+                                    forw[j].y);
+                 for (j = n_rev - 1; j >= 0; j--)
+                   art_vpath_add_point (&result, &n_result, &n_result_max,
+                                    ART_LINETO, rev[j].x,
+                                    rev[j].y);
+                 render_cap (&result, &n_result, &n_result_max,
+                             vpath, second, begin_idx,
+                             cap, half_lw, flatness);
+                 art_vpath_add_point (&result, &n_result, &n_result_max,
+                                  ART_LINETO, forw[0].x,
+                                  forw[0].y);
+               }
+           }
+         else
+           render_seg (&forw, &n_forw, &n_forw_max,
+                       &rev, &n_rev, &n_rev_max,
+                       vpath, last, this, next,
+                       join, half_lw, miter_limit, flatness);
+       }
+      end_idx = next;
+    }
+
+  art_free (forw);
+  art_free (rev);
+#ifdef VERBOSE
+  printf ("%% n_result = %d\n", n_result);
+#endif
+  art_vpath_add_point (&result, &n_result, &n_result_max, ART_END, 0, 0);
+  return result;
+}
+
+#define noVERBOSE
+
+#ifdef VERBOSE
+
+#define XOFF 50
+#define YOFF 700
+
+static void
+print_ps_vpath (ArtVpath *vpath)
+{
+  int i;
+
+  for (i = 0; vpath[i].code != ART_END; i++)
+    {
+      switch (vpath[i].code)
+       {
+       case ART_MOVETO:
+         printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
+         break;
+       case ART_LINETO:
+         printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
+         break;
+       default:
+         break;
+       }
+    }
+  printf ("stroke showpage\n");
+}
+
+static void
+print_ps_svp (ArtSVP *vpath)
+{
+  int i, j;
+
+  printf ("%% begin\n");
+  for (i = 0; i < vpath->n_segs; i++)
+    {
+      printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0);
+      for (j = 0; j < vpath->segs[i].n_points; j++)
+       {
+         printf ("%g %g %s\n",
+                 XOFF + vpath->segs[i].points[j].x,
+                 YOFF - vpath->segs[i].points[j].y,
+                 j ? "lineto" : "moveto");
+       }
+      printf ("stroke\n");
+    }
+
+  printf ("showpage\n");
+}
+#endif
+
+/* Render a vector path into a stroked outline.
+
+   Status of this routine:
+
+   Basic correctness: Only miter and bevel line joins are implemented,
+   and only butt line caps. Otherwise, seems to be fine.
+
+   Numerical stability: We cheat (adding random perturbation). Thus,
+   it seems very likely that no numerical stability problems will be
+   seen in practice.
+
+   Speed: Should be pretty good.
+
+   Precision: The perturbation fuzzes the coordinates slightly,
+   but not enough to be visible.  */
+/**
+ * art_svp_vpath_stroke: Stroke a vector path.
+ * @vpath: #ArtVPath to stroke.
+ * @join: Join style.
+ * @cap: Cap style.
+ * @line_width: Width of stroke.
+ * @miter_limit: Miter limit.
+ * @flatness: Flatness.
+ *
+ * Computes an svp representing the stroked outline of @vpath. The
+ * width of the stroked line is @line_width.
+ *
+ * Lines are joined according to the @join rule. Possible values are
+ * ART_PATH_STROKE_JOIN_MITER (for mitered joins),
+ * ART_PATH_STROKE_JOIN_ROUND (for round joins), and
+ * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join
+ * is converted to a bevelled join if the miter would extend to a
+ * distance of more than @miter_limit * @line_width from the actual
+ * join point.
+ *
+ * If there are open subpaths, the ends of these subpaths are capped
+ * according to the @cap rule. Possible values are
+ * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end
+ * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at
+ * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap,
+ * extending half @line_width past the end point).
+ *
+ * The @flatness parameter controls the accuracy of the rendering. It
+ * is most important for determining the number of points to use to
+ * approximate circular arcs for round lines and joins. In general, the
+ * resulting vector path will be within @flatness pixels of the "ideal"
+ * path containing actual circular arcs. I reserve the right to use
+ * the @flatness parameter to convert bevelled joins to miters for very
+ * small turn angles, as this would reduce the number of points in the
+ * resulting outline path.
+ *
+ * The resulting path is "clean" with respect to self-intersections, i.e.
+ * the winding number is 0 or 1 at each point.
+ *
+ * Return value: Resulting stroked outline in svp format.
+ **/
+ArtSVP *
+art_svp_vpath_stroke (ArtVpath *vpath,
+                     ArtPathStrokeJoinType join,
+                     ArtPathStrokeCapType cap,
+                     double line_width,
+                     double miter_limit,
+                     double flatness)
+{
+#ifdef ART_USE_NEW_INTERSECTOR
+  ArtVpath *vpath_stroke;
+  ArtSVP *svp, *svp2;
+  ArtSvpWriter *swr;
+
+  vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
+                                          line_width, miter_limit, flatness);
+#ifdef VERBOSE
+  //print_ps_vpath (vpath_stroke);
+#endif
+  svp = art_svp_from_vpath (vpath_stroke);
+#ifdef VERBOSE
+  //print_ps_svp (svp);
+#endif
+  art_free (vpath_stroke);
+
+  swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
+  art_svp_intersector (svp, swr);
+
+  svp2 = art_svp_writer_rewind_reap (swr);
+#ifdef VERBOSE
+  //print_ps_svp (svp2);
+#endif
+  art_svp_free (svp);
+  return svp2;
+#else
+  ArtVpath *vpath_stroke, *vpath2;
+  ArtSVP *svp, *svp2, *svp3;
+
+  vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
+                                          line_width, miter_limit, flatness);
+#ifdef VERBOSE
+  //print_ps_vpath (vpath_stroke);
+#endif
+  vpath2 = art_vpath_perturb (vpath_stroke);
+#ifdef VERBOSE
+  //print_ps_vpath (vpath2);
+#endif
+  art_free (vpath_stroke);
+  svp = art_svp_from_vpath (vpath2);
+#ifdef VERBOSE
+  //print_ps_svp (svp);
+#endif
+  art_free (vpath2);
+  svp2 = art_svp_uncross (svp);
+#ifdef VERBOSE
+  //print_ps_svp (svp2);
+#endif
+  art_svp_free (svp);
+  svp3 = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_NONZERO);
+#ifdef VERBOSE
+  //print_ps_svp (svp3);
+#endif
+  art_svp_free (svp2);
+
+  return svp3;
+#endif
+}
diff --git a/lib/art/art_svp_wind.c b/lib/art/art_svp_wind.c
new file mode 100644 (file)
index 0000000..a12b1c7
--- /dev/null
@@ -0,0 +1,1545 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* Primitive intersection and winding number operations on sorted
+   vector paths.
+
+   These routines are internal to libart, used to construct operations
+   like intersection, union, and difference. */
+
+#include "config.h"
+#include "art_svp_wind.h"
+
+#include <stdio.h> /* for printf of debugging info */
+#include <string.h> /* for memcpy */
+#include <math.h>
+#include "art_misc.h"
+
+#include "art_rect.h"
+#include "art_svp.h"
+
+#define noVERBOSE
+
+#define PT_EQ(p1,p2) ((p1).x == (p2).x && (p1).y == (p2).y)
+
+#define PT_CLOSE(p1,p2) (fabs ((p1).x - (p2).x) < 1e-6 && fabs ((p1).y - (p2).y) < 1e-6)
+
+/* return nonzero and set *p to the intersection point if the lines
+   z0-z1 and z2-z3 intersect each other. */
+static int
+intersect_lines (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3,
+                ArtPoint *p)
+{
+  double a01, b01, c01;
+  double a23, b23, c23;
+  double d0, d1, d2, d3;
+  double det;
+
+  /* if the vectors share an endpoint, they don't intersect */
+  if (PT_EQ (z0, z2) || PT_EQ (z0, z3) || PT_EQ (z1, z2) || PT_EQ (z1, z3))
+    return 0;
+
+#if 0
+  if (PT_CLOSE (z0, z2) || PT_CLOSE (z0, z3) || PT_CLOSE (z1, z2) || PT_CLOSE (z1, z3))
+    return 0;
+#endif
+
+  /* find line equations ax + by + c = 0 */
+  a01 = z0.y - z1.y;
+  b01 = z1.x - z0.x;
+  c01 = -(z0.x * a01 + z0.y * b01);
+  /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) 
+     = (z1.x * z0.y - z1.y * z0.x) */
+
+  d2 = a01 * z2.x + b01 * z2.y + c01;
+  d3 = a01 * z3.x + b01 * z3.y + c01;
+  if ((d2 > 0) == (d3 > 0))
+    return 0;
+
+  a23 = z2.y - z3.y;
+  b23 = z3.x - z2.x;
+  c23 = -(z2.x * a23 + z2.y * b23);
+
+  d0 = a23 * z0.x + b23 * z0.y + c23;
+  d1 = a23 * z1.x + b23 * z1.y + c23;
+  if ((d0 > 0) == (d1 > 0))
+    return 0;
+
+  /* now we definitely know that the lines intersect */
+  /* solve the two linear equations ax + by + c = 0 */
+  det = 1.0 / (a01 * b23 - a23 * b01);
+  p->x = det * (c23 * b01 - c01 * b23);
+  p->y = det * (c01 * a23 - c23 * a01);
+
+  return 1;
+}
+
+#define EPSILON 1e-6
+
+static double
+trap_epsilon (double v)
+{
+  const double epsilon = EPSILON;
+
+  if (v < epsilon && v > -epsilon) return 0;
+  else return v;
+}
+
+/* Determine the order of line segments z0-z1 and z2-z3.
+   Return +1 if z2-z3 lies entirely to the right of z0-z1,
+   -1 if entirely to the left,
+   or 0 if overlap.
+
+   The case analysis in this function is quite ugly. The fact that it's
+   almost 200 lines long is ridiculous.
+
+   Ok, so here's the plan to cut it down:
+
+   First, do a bounding line comparison on the x coordinates. This is pretty
+   much the common case, and should go quickly. It also takes care of the
+   case where both lines are horizontal.
+
+   Then, do d0 and d1 computation, but only if a23 is nonzero.
+
+   Finally, do d2 and d3 computation, but only if a01 is nonzero.
+
+   Fall through to returning 0 (this will happen when both lines are
+   horizontal and they overlap).
+   */
+static int
+x_order (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3)
+{
+  double a01, b01, c01;
+  double a23, b23, c23;
+  double d0, d1, d2, d3;
+
+  if (z0.y == z1.y)
+    {
+      if (z2.y == z3.y)
+       {
+         double x01min, x01max;
+         double x23min, x23max;
+
+         if (z0.x > z1.x)
+           {
+             x01min = z1.x;
+             x01max = z0.x;
+           }
+         else
+           {
+             x01min = z0.x;
+             x01max = z1.x;
+           }
+
+         if (z2.x > z3.x)
+           {
+             x23min = z3.x;
+             x23max = z2.x;
+           }
+         else
+           {
+             x23min = z2.x;
+             x23max = z3.x;
+           }
+
+         if (x23min >= x01max) return 1;
+         else if (x01min >= x23max) return -1;
+         else return 0;
+       }
+      else
+       {
+         /* z0-z1 is horizontal, z2-z3 isn't */
+         a23 = z2.y - z3.y;
+         b23 = z3.x - z2.x;
+         c23 = -(z2.x * a23 + z2.y * b23);
+
+         if (z3.y < z2.y)
+           {
+             a23 = -a23;
+             b23 = -b23;
+             c23 = -c23;
+           }
+         
+         d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23);
+         d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23);
+
+         if (d0 > 0)
+           {
+             if (d1 >= 0) return 1;
+             else return 0;
+           }
+         else if (d0 == 0)
+           {
+             if (d1 > 0) return 1;
+             else if (d1 < 0) return -1;
+             else printf ("case 1 degenerate\n");
+             return 0;
+           }
+         else /* d0 < 0 */
+           {
+             if (d1 <= 0) return -1;
+             else return 0;
+           }
+       }
+    }
+  else if (z2.y == z3.y)
+    {
+      /* z2-z3 is horizontal, z0-z1 isn't */
+      a01 = z0.y - z1.y;
+      b01 = z1.x - z0.x;
+      c01 = -(z0.x * a01 + z0.y * b01);
+      /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) 
+        = (z1.x * z0.y - z1.y * z0.x) */
+
+      if (z1.y < z0.y)
+       {
+         a01 = -a01;
+         b01 = -b01;
+         c01 = -c01;
+       }
+
+      d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01);
+      d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01);
+
+      if (d2 > 0)
+       {
+         if (d3 >= 0) return -1;
+         else return 0;
+       }
+      else if (d2 == 0)
+       {
+         if (d3 > 0) return -1;
+         else if (d3 < 0) return 1;
+         else printf ("case 2 degenerate\n");
+         return 0;
+       }
+      else /* d2 < 0 */
+       {
+         if (d3 <= 0) return 1;
+         else return 0;
+       }
+    }
+
+  /* find line equations ax + by + c = 0 */
+  a01 = z0.y - z1.y;
+  b01 = z1.x - z0.x;
+  c01 = -(z0.x * a01 + z0.y * b01);
+  /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) 
+     = -(z1.x * z0.y - z1.y * z0.x) */
+
+  if (a01 > 0)
+    {
+      a01 = -a01;
+      b01 = -b01;
+      c01 = -c01;
+    }
+  /* so now, (a01, b01) points to the left, thus a01 * x + b01 * y + c01
+     is negative if the point lies to the right of the line */
+
+  d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01);
+  d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01);
+  if (d2 > 0)
+    {
+      if (d3 >= 0) return -1;
+    }
+  else if (d2 == 0)
+    {
+      if (d3 > 0) return -1;
+      else if (d3 < 0) return 1;
+      else
+       fprintf (stderr, "colinear!\n");
+    }
+  else /* d2 < 0 */
+    {
+      if (d3 <= 0) return 1;
+    }
+
+  a23 = z2.y - z3.y;
+  b23 = z3.x - z2.x;
+  c23 = -(z2.x * a23 + z2.y * b23);
+
+  if (a23 > 0)
+    {
+      a23 = -a23;
+      b23 = -b23;
+      c23 = -c23;
+    }
+  d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23);
+  d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23);
+  if (d0 > 0)
+    {
+      if (d1 >= 0) return 1;
+    }
+  else if (d0 == 0)
+    {
+      if (d1 > 0) return 1;
+      else if (d1 < 0) return -1;
+      else
+       fprintf (stderr, "colinear!\n");
+    }
+  else /* d0 < 0 */
+    {
+      if (d1 <= 0) return -1;
+    }
+
+  return 0;
+}
+
+/* similar to x_order, but to determine whether point z0 + epsilon lies to
+   the left of the line z2-z3 or to the right */
+static int
+x_order_2 (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3)
+{
+  double a23, b23, c23;
+  double d0, d1;
+
+  a23 = z2.y - z3.y;
+  b23 = z3.x - z2.x;
+  c23 = -(z2.x * a23 + z2.y * b23);
+
+  if (a23 > 0)
+    {
+      a23 = -a23;
+      b23 = -b23;
+      c23 = -c23;
+    }
+
+  d0 = a23 * z0.x + b23 * z0.y + c23;
+
+  if (d0 > EPSILON)
+    return -1;
+  else if (d0 < -EPSILON)
+    return 1;
+
+  d1 = a23 * z1.x + b23 * z1.y + c23;
+  if (d1 > EPSILON)
+    return -1;
+  else if (d1 < -EPSILON)
+    return 1;
+
+  if (z0.x == z1.x && z1.x == z2.x && z2.x == z3.x)
+    {
+      art_dprint ("x_order_2: colinear and horizontally aligned!\n");
+      return 0;
+    }
+
+  if (z0.x <= z2.x && z1.x <= z2.x && z0.x <= z3.x && z1.x <= z3.x)
+    return -1;
+  if (z0.x >= z2.x && z1.x >= z2.x && z0.x >= z3.x && z1.x >= z3.x)
+    return 1;
+  
+  fprintf (stderr, "x_order_2: colinear!\n");
+  return 0;
+}
+
+#ifdef DEAD_CODE
+/* Traverse the vector path, keeping it in x-sorted order.
+
+   This routine doesn't actually do anything - it's just here for
+   explanatory purposes. */
+void
+traverse (ArtSVP *vp)
+{
+  int *active_segs;
+  int n_active_segs;
+  int *cursor;
+  int seg_idx;
+  double y;
+  int tmp1, tmp2;
+  int asi;
+  int i, j;
+
+  active_segs = art_new (int, vp->n_segs);
+  cursor = art_new (int, vp->n_segs);
+
+  n_active_segs = 0;
+  seg_idx = 0;
+  y = vp->segs[0].points[0].y;
+  while (seg_idx < vp->n_segs || n_active_segs > 0)
+    {
+      printf ("y = %g\n", y);
+      /* delete segments ending at y from active list */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         if (vp->segs[asi].n_points - 1 == cursor[asi] &&
+             vp->segs[asi].points[cursor[asi]].y == y)
+           {
+             printf ("deleting %d\n", asi);
+             n_active_segs--;
+             for (j = i; j < n_active_segs; j++)
+               active_segs[j] = active_segs[j + 1];
+             i--;
+           }
+       }
+
+      /* insert new segments into the active list */
+      while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y)
+       {
+         cursor[seg_idx] = 0;
+         printf ("inserting %d\n", seg_idx);
+         for (i = 0; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (x_order (vp->segs[asi].points[cursor[asi]],
+                          vp->segs[asi].points[cursor[asi] + 1],
+                          vp->segs[seg_idx].points[0],
+                          vp->segs[seg_idx].points[1]) == -1)
+             break;
+           }
+         tmp1 = seg_idx;
+         for (j = i; j < n_active_segs; j++)
+           {
+             tmp2 = active_segs[j];
+             active_segs[j] = tmp1;
+             tmp1 = tmp2;
+           }
+         active_segs[n_active_segs] = tmp1;
+         n_active_segs++;
+         seg_idx++;
+       }
+
+      /* all active segs cross the y scanline (considering segs to be
+       closed on top and open on bottom) */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         printf ("%d (%g, %g) - (%g, %g) %s\n", asi,
+                 vp->segs[asi].points[cursor[asi]].x,
+                 vp->segs[asi].points[cursor[asi]].y,
+                 vp->segs[asi].points[cursor[asi] + 1].x,
+                 vp->segs[asi].points[cursor[asi] + 1].y,
+                 vp->segs[asi].dir ? "v" : "^");
+       }
+
+      /* advance y to the next event */
+      if (n_active_segs == 0)
+       {
+         if (seg_idx < vp->n_segs)
+           y = vp->segs[seg_idx].points[0].y;
+         /* else we're done */
+       }
+      else
+       {
+         asi = active_segs[0];
+         y = vp->segs[asi].points[cursor[asi] + 1].y;
+         for (i = 1; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (y > vp->segs[asi].points[cursor[asi] + 1].y)
+               y = vp->segs[asi].points[cursor[asi] + 1].y;
+           }
+         if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y)
+           y = vp->segs[seg_idx].points[0].y;
+       }
+
+      /* advance cursors to reach new y */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         while (cursor[asi] < vp->segs[asi].n_points - 1 &&
+                y >= vp->segs[asi].points[cursor[asi] + 1].y)
+           cursor[asi]++;
+       }
+      printf ("\n");
+    }
+  art_free (cursor);
+  art_free (active_segs);
+}
+#endif
+
+/* I believe that the loop will always break with i=1.
+
+   I think I'll want to change this from a simple sorted list to a
+   modified stack. ips[*][0] will get its own data structure, and
+   ips[*] will in general only be allocated if there is an intersection.
+   Finally, the segment can be traced through the initial point
+   (formerly ips[*][0]), backwards through the stack, and finally
+   to cursor + 1.
+
+   This change should cut down on allocation bandwidth, and also
+   eliminate the iteration through n_ipl below.
+
+*/
+static void
+insert_ip (int seg_i, int *n_ips, int *n_ips_max, ArtPoint **ips, ArtPoint ip)
+{
+  int i;
+  ArtPoint tmp1, tmp2;
+  int n_ipl;
+  ArtPoint *ipl;
+
+  n_ipl = n_ips[seg_i]++;
+  if (n_ipl == n_ips_max[seg_i])
+      art_expand (ips[seg_i], ArtPoint, n_ips_max[seg_i]);
+  ipl = ips[seg_i];
+  for (i = 1; i < n_ipl; i++)
+    if (ipl[i].y > ip.y)
+      break;
+  tmp1 = ip;
+  for (; i <= n_ipl; i++)
+    {
+      tmp2 = ipl[i];
+      ipl[i] = tmp1;
+      tmp1 = tmp2;
+    }
+}
+
+/* test active segment (i - 1) against i for intersection, if
+   so, add intersection point to both ips lists. */
+static void
+intersect_neighbors (int i, int *active_segs,
+                    int *n_ips, int *n_ips_max, ArtPoint **ips,
+                    int *cursor, ArtSVP *vp)
+{
+  ArtPoint z0, z1, z2, z3;
+  int asi01, asi23;
+  ArtPoint ip;
+
+  asi01 = active_segs[i - 1];
+
+  z0 = ips[asi01][0];
+  if (n_ips[asi01] == 1)
+    z1 = vp->segs[asi01].points[cursor[asi01] + 1];
+  else
+    z1 = ips[asi01][1];
+
+  asi23 = active_segs[i];
+
+  z2 = ips[asi23][0];
+  if (n_ips[asi23] == 1)
+    z3 = vp->segs[asi23].points[cursor[asi23] + 1];
+  else
+    z3 = ips[asi23][1];
+
+  if (intersect_lines (z0, z1, z2, z3, &ip))
+    {
+#ifdef VERBOSE
+      printf ("new intersection point: (%g, %g)\n", ip.x, ip.y);
+#endif
+      insert_ip (asi01, n_ips, n_ips_max, ips, ip);
+      insert_ip (asi23, n_ips, n_ips_max, ips, ip);
+    }
+}
+
+/* Add a new point to a segment in the svp.
+
+   Here, we also check to make sure that the segments satisfy nocross.
+   However, this is only valuable for debugging, and could possibly be
+   removed.
+*/
+static void
+svp_add_point (ArtSVP *svp, int *n_points_max,
+              ArtPoint p, int *seg_map, int *active_segs, int n_active_segs,
+              int i)
+{
+  int asi, asi_left, asi_right;
+  int n_points, n_points_left, n_points_right;
+  ArtSVPSeg *seg;
+
+  asi = seg_map[active_segs[i]];
+  seg = &svp->segs[asi];
+  n_points = seg->n_points;
+  /* find out whether neighboring segments share a point */
+  if (i > 0)
+    {
+      asi_left = seg_map[active_segs[i - 1]];
+      n_points_left = svp->segs[asi_left].n_points;
+      if (n_points_left > 1 && 
+         PT_EQ (svp->segs[asi_left].points[n_points_left - 2],
+                svp->segs[asi].points[n_points - 1]))
+       {
+         /* ok, new vector shares a top point with segment to the left -
+            now, check that it satisfies ordering invariant */
+         if (x_order (svp->segs[asi_left].points[n_points_left - 2],
+                      svp->segs[asi_left].points[n_points_left - 1],
+                      svp->segs[asi].points[n_points - 1],
+                      p) < 1)
+
+           {
+#ifdef VERBOSE
+             printf ("svp_add_point: cross on left!\n");
+#endif
+           }
+       }
+    }
+
+  if (i + 1 < n_active_segs)
+    {
+      asi_right = seg_map[active_segs[i + 1]];
+      n_points_right = svp->segs[asi_right].n_points;
+      if (n_points_right > 1 && 
+         PT_EQ (svp->segs[asi_right].points[n_points_right - 2],
+                svp->segs[asi].points[n_points - 1]))
+       {
+         /* ok, new vector shares a top point with segment to the right -
+            now, check that it satisfies ordering invariant */
+         if (x_order (svp->segs[asi_right].points[n_points_right - 2],
+                      svp->segs[asi_right].points[n_points_right - 1],
+                      svp->segs[asi].points[n_points - 1],
+                      p) > -1)
+           {
+#ifdef VERBOSE
+             printf ("svp_add_point: cross on right!\n");
+#endif
+           }
+       }
+    }
+  if (n_points_max[asi] == n_points)
+    art_expand (seg->points, ArtPoint, n_points_max[asi]);
+  seg->points[n_points] = p;
+  if (p.x < seg->bbox.x0)
+    seg->bbox.x0 = p.x;
+  else if (p.x > seg->bbox.x1)
+    seg->bbox.x1 = p.x;
+  seg->bbox.y1 = p.y;
+  seg->n_points++;
+}
+
+#if 0
+/* find where the segment (currently at i) is supposed to go, and return
+   the target index - if equal to i, then there is no crossing problem.
+
+   "Where it is supposed to go" is defined as following:
+
+   Delete element i, re-insert at position target (bumping everything
+   target and greater to the right).
+   */
+static int
+find_crossing (int i, int *active_segs, int n_active_segs,
+              int *cursor, ArtPoint **ips, int *n_ips, ArtSVP *vp)
+{
+  int asi, asi_left, asi_right;
+  ArtPoint p0, p1;
+  ArtPoint p0l, p1l;
+  ArtPoint p0r, p1r;
+  int target;
+
+  asi = active_segs[i];
+  p0 = ips[asi][0];
+  if (n_ips[asi] == 1)
+    p1 = vp->segs[asi].points[cursor[asi] + 1];
+  else
+    p1 = ips[asi][1];
+
+  for (target = i; target > 0; target--)
+    {
+      asi_left = active_segs[target - 1];
+      p0l = ips[asi_left][0];
+      if (n_ips[asi_left] == 1)
+       p1l = vp->segs[asi_left].points[cursor[asi_left] + 1];
+      else
+       p1l = ips[asi_left][1];
+      if (!PT_EQ (p0, p0l))
+       break;
+
+#ifdef VERBOSE
+      printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n",
+             p0l.x, p0l.y, p1l.x, p1l.y, p0.x, p0.y, p1.x, p1.y);
+#endif
+      if (x_order (p0l, p1l, p0, p1) == 1)
+       break;
+
+#ifdef VERBOSE
+      printf ("scanning to the left (i=%d, target=%d)\n", i, target);
+#endif
+    }
+
+  if (target < i) return target;
+
+  for (; target < n_active_segs - 1; target++)
+    {
+      asi_right = active_segs[target + 1];
+      p0r = ips[asi_right][0];
+      if (n_ips[asi_right] == 1)
+       p1r = vp->segs[asi_right].points[cursor[asi_right] + 1];
+      else
+       p1r = ips[asi_right][1];
+      if (!PT_EQ (p0, p0r))
+       break;
+
+#ifdef VERBOSE
+      printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n",
+             p0.x, p0.y, p1.x, p1.y, p0r.x, p0r.y, p1r.x, p1r.y);
+#endif
+      if (x_order (p0r, p1r, p0, p1) == 1)
+       break;
+
+#ifdef VERBOSE
+      printf ("scanning to the right (i=%d, target=%d)\n", i, target);
+#endif
+    }
+
+  return target;
+}
+#endif
+
+/* This routine handles the case where the segment changes its position
+   in the active segment list. Generally, this will happen when the
+   segment (defined by i and cursor) shares a top point with a neighbor,
+   but breaks the ordering invariant.
+
+   Essentially, this routine sorts the lines [start..end), all of which
+   share a top point. This is implemented as your basic insertion sort.
+
+   This routine takes care of intersecting the appropriate neighbors,
+   as well.
+
+   A first argument of -1 immediately returns, which helps reduce special
+   casing in the main unwind routine.
+*/
+static void
+fix_crossing (int start, int end, int *active_segs, int n_active_segs,
+             int *cursor, ArtPoint **ips, int *n_ips, int *n_ips_max,
+             ArtSVP *vp, int *seg_map,
+             ArtSVP **p_new_vp, int *pn_segs_max,
+             int **pn_points_max)
+{
+  int i, j;
+  int target;
+  int asi, asj;
+  ArtPoint p0i, p1i;
+  ArtPoint p0j, p1j;
+  int swap = 0;
+#ifdef VERBOSE
+  int k;
+#endif
+  ArtPoint *pts;
+
+#ifdef VERBOSE
+  printf ("fix_crossing: [%d..%d)", start, end);
+  for (k = 0; k < n_active_segs; k++)
+    printf (" %d", active_segs[k]);
+  printf ("\n");
+#endif
+
+  if (start == -1)
+    return;
+
+  for (i = start + 1; i < end; i++)
+    {
+
+      asi = active_segs[i];
+      if (cursor[asi] < vp->segs[asi].n_points - 1) {
+       p0i = ips[asi][0];
+       if (n_ips[asi] == 1)
+         p1i = vp->segs[asi].points[cursor[asi] + 1];
+       else
+         p1i = ips[asi][1];
+
+       for (j = i - 1; j >= start; j--)
+         {
+           asj = active_segs[j];
+           if (cursor[asj] < vp->segs[asj].n_points - 1)
+             {
+               p0j = ips[asj][0];
+               if (n_ips[asj] == 1)
+                 p1j = vp->segs[asj].points[cursor[asj] + 1];
+               else
+                 p1j = ips[asj][1];
+
+               /* we _hope_ p0i = p0j */
+               if (x_order_2 (p0j, p1j, p0i, p1i) == -1)
+                 break;
+             }
+         }
+
+       target = j + 1;
+       /* target is where active_seg[i] _should_ be in active_segs */
+      
+       if (target != i)
+         {
+           swap = 1;
+
+#ifdef VERBOSE
+           printf ("fix_crossing: at %i should be %i\n", i, target);
+#endif
+
+           /* let's close off all relevant segments */
+           for (j = i; j >= target; j--)
+             {
+               asi = active_segs[j];
+               /* First conjunct: this isn't the last point in the original
+                  segment.
+
+                  Second conjunct: this isn't the first point in the new
+                  segment (i.e. already broken).
+               */
+               if (cursor[asi] < vp->segs[asi].n_points - 1 &&
+                   (*p_new_vp)->segs[seg_map[asi]].n_points != 1)
+                 {
+                   int seg_num;
+                   /* so break here */
+#ifdef VERBOSE
+                   printf ("closing off %d\n", j);
+#endif
+
+                   pts = art_new (ArtPoint, 16);
+                   pts[0] = ips[asi][0];
+                   seg_num = art_svp_add_segment (p_new_vp, pn_segs_max,
+                                                  pn_points_max,
+                                                  1, vp->segs[asi].dir,
+                                                  pts,
+                                                  NULL);
+                   (*pn_points_max)[seg_num] = 16;
+                   seg_map[asi] = seg_num;
+                 }
+             }
+
+           /* now fix the ordering in active_segs */
+           asi = active_segs[i];
+           for (j = i; j > target; j--)
+             active_segs[j] = active_segs[j - 1];
+           active_segs[j] = asi;
+         }
+      }
+    }
+  if (swap && start > 0)
+    {
+      int as_start;
+
+      as_start = active_segs[start];
+      if (cursor[as_start] < vp->segs[as_start].n_points)
+       {
+#ifdef VERBOSE
+         printf ("checking intersection of %d, %d\n", start - 1, start);
+#endif
+         intersect_neighbors (start, active_segs,
+                              n_ips, n_ips_max, ips,
+                              cursor, vp);
+       }
+    }
+
+  if (swap && end < n_active_segs)
+    {
+      int as_end;
+
+      as_end = active_segs[end - 1];
+      if (cursor[as_end] < vp->segs[as_end].n_points)
+       {
+#ifdef VERBOSE
+         printf ("checking intersection of %d, %d\n", end - 1, end);
+#endif
+         intersect_neighbors (end, active_segs,
+                              n_ips, n_ips_max, ips,
+                              cursor, vp);
+       }
+    }
+  if (swap)
+    {
+#ifdef VERBOSE
+      printf ("fix_crossing return: [%d..%d)", start, end);
+      for (k = 0; k < n_active_segs; k++)
+       printf (" %d", active_segs[k]);
+      printf ("\n");
+#endif
+    }
+}
+
+/* Return a new sorted vector that covers the same area as the
+   argument, but which satisfies the nocross invariant.
+
+   Basically, this routine works by finding the intersection points,
+   and cutting the segments at those points.
+
+   Status of this routine:
+
+   Basic correctness: Seems ok.
+
+   Numerical stability: known problems in the case of points falling
+   on lines, and colinear lines. For actual use, randomly perturbing
+   the vertices is currently recommended.
+
+   Speed: pretty good, although a more efficient priority queue, as
+   well as bbox culling of potential intersections, are two
+   optimizations that could help.
+
+   Precision: pretty good, although the numerical stability problems
+   make this routine unsuitable for precise calculations of
+   differences.
+
+*/
+
+/* Here is a more detailed description of the algorithm. It follows
+   roughly the structure of traverse (above), but is obviously quite
+   a bit more complex.
+
+   Here are a few important data structures:
+
+   A new sorted vector path (new_svp).
+
+   For each (active) segment in the original, a list of intersection
+   points.
+
+   Of course, the original being traversed.
+
+   The following invariants hold (in addition to the invariants
+   of the traverse procedure).
+
+   The new sorted vector path lies entirely above the y scan line.
+
+   The new sorted vector path keeps the nocross invariant.
+
+   For each active segment, the y scan line crosses the line from the
+   first to the second of the intersection points (where the second
+   point is cursor + 1 if there is only one intersection point).
+
+   The list of intersection points + the (cursor + 1) point is kept
+   in nondecreasing y order.
+
+   Of the active segments, none of the lines from first to second
+   intersection point cross the 1st ip..2nd ip line of the left or
+   right neighbor. (However, such a line may cross further
+   intersection points of the neighbors, or segments past the
+   immediate neighbors).
+
+   Of the active segments, all lines from 1st ip..2nd ip are in
+   strictly increasing x_order (this is very similar to the invariant
+   of the traverse procedure, but is explicitly stated here in terms
+   of ips). (this basically says that nocross holds on the active
+   segments)
+
+   The combination of the new sorted vector path, the path through all
+   the intersection points to cursor + 1, and [cursor + 1, n_points)
+   covers the same area as the argument.
+
+   Another important data structure is mapping from original segment
+   number to new segment number.
+
+   The algorithm is perhaps best understood as advancing the cursors
+   while maintaining these invariants. Here's roughly how it's done.
+
+   When deleting segments from the active list, those segments are added
+   to the new sorted vector path. In addition, the neighbors may intersect
+   each other, so they are intersection tested (see below).
+
+   When inserting new segments, they are intersection tested against
+   their neighbors. The top point of the segment becomes the first
+   intersection point.
+
+   Advancing the cursor is just a bit different from the traverse
+   routine, as the cursor may advance through the intersection points
+   as well. Only when there is a single intersection point in the list
+   does the cursor advance in the original segment. In either case,
+   the new vector is intersection tested against both neighbors. It
+   also causes the vector over which the cursor is advancing to be
+   added to the new svp.
+
+   Two steps need further clarification:
+
+   Intersection testing: the 1st ip..2nd ip lines of the neighbors
+   are tested to see if they cross (using intersect_lines). If so,
+   then the intersection point is added to the ip list of both
+   segments, maintaining the invariant that the list of intersection
+   points is nondecreasing in y).
+
+   Adding vector to new svp: if the new vector shares a top x
+   coordinate with another vector, then it is checked to see whether
+   it is in order. If not, then both segments are "broken," and then
+   restarted. Note: in the case when both segments are in the same
+   order, they may simply be swapped without breaking.
+
+   For the time being, I'm going to put some of these operations into
+   subroutines. If it turns out to be a performance problem, I could
+   try to reorganize the traverse procedure so that each is only
+   called once, and inline them. But if it's not a performance
+   problem, I'll just keep it this way, because it will probably help
+   to make the code clearer, and I believe this code could use all the
+   clarity it can get. */
+/**
+ * art_svp_uncross: Resolve self-intersections of an svp.
+ * @vp: The original svp.
+ *
+ * Finds all the intersections within @vp, and constructs a new svp
+ * with new points added at these intersections.
+ *
+ * This routine needs to be redone from scratch with numerical robustness
+ * in mind. I'm working on it.
+ *
+ * Return value: The new svp.
+ **/
+ArtSVP *
+art_svp_uncross (ArtSVP *vp)
+{
+  int *active_segs;
+  int n_active_segs;
+  int *cursor;
+  int seg_idx;
+  double y;
+  int tmp1, tmp2;
+  int asi;
+  int i, j;
+  /* new data structures */
+  /* intersection points; invariant: *ips[i] is only allocated if
+     i is active */
+  int *n_ips, *n_ips_max;
+  ArtPoint **ips;
+  /* new sorted vector path */
+  int n_segs_max, seg_num;
+  ArtSVP *new_vp;
+  int *n_points_max;
+  /* mapping from argument to new segment numbers - again, only valid
+   if active */
+  int *seg_map;
+  double y_curs;
+  ArtPoint p_curs;
+  int first_share;
+  double share_x;
+  ArtPoint *pts;
+
+  n_segs_max = 16;
+  new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) +
+                               (n_segs_max - 1) * sizeof(ArtSVPSeg));
+  new_vp->n_segs = 0;
+
+  if (vp->n_segs == 0)
+    return new_vp;
+
+  active_segs = art_new (int, vp->n_segs);
+  cursor = art_new (int, vp->n_segs);
+
+  seg_map = art_new (int, vp->n_segs);
+  n_ips = art_new (int, vp->n_segs);
+  n_ips_max = art_new (int, vp->n_segs);
+  ips = art_new (ArtPoint *, vp->n_segs);
+
+  n_points_max = art_new (int, n_segs_max);
+
+  n_active_segs = 0;
+  seg_idx = 0;
+  y = vp->segs[0].points[0].y;
+  while (seg_idx < vp->n_segs || n_active_segs > 0)
+    {
+#ifdef VERBOSE
+      printf ("y = %g\n", y);
+#endif
+
+      /* maybe move deletions to end of loop (to avoid so much special
+        casing on the end of a segment)? */
+
+      /* delete segments ending at y from active list */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         if (vp->segs[asi].n_points - 1 == cursor[asi] &&
+             vp->segs[asi].points[cursor[asi]].y == y)
+           {
+             do
+               {
+#ifdef VERBOSE
+                 printf ("deleting %d\n", asi);
+#endif
+                 art_free (ips[asi]);
+                 n_active_segs--;
+                 for (j = i; j < n_active_segs; j++)
+                   active_segs[j] = active_segs[j + 1];
+                 if (i < n_active_segs)
+                   asi = active_segs[i];
+                 else
+                   break;
+               }
+             while (vp->segs[asi].n_points - 1 == cursor[asi] &&
+                    vp->segs[asi].points[cursor[asi]].y == y);
+
+             /* test intersection of neighbors */
+             if (i > 0 && i < n_active_segs)
+               intersect_neighbors (i, active_segs,
+                                    n_ips, n_ips_max, ips,
+                                    cursor, vp);
+
+             i--;
+           }         
+       }
+
+      /* insert new segments into the active list */
+      while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y)
+       {
+#ifdef VERBOSE
+         printf ("inserting %d\n", seg_idx);
+#endif
+         cursor[seg_idx] = 0;
+         for (i = 0; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (x_order_2 (vp->segs[seg_idx].points[0],
+                            vp->segs[seg_idx].points[1],
+                            vp->segs[asi].points[cursor[asi]],
+                            vp->segs[asi].points[cursor[asi] + 1]) == -1)
+               break;
+           }
+
+         /* Create and initialize the intersection points data structure */
+         n_ips[seg_idx] = 1;
+         n_ips_max[seg_idx] = 2;
+         ips[seg_idx] = art_new (ArtPoint, n_ips_max[seg_idx]);
+         ips[seg_idx][0] = vp->segs[seg_idx].points[0];
+
+         /* Start a new segment in the new vector path */
+         pts = art_new (ArtPoint, 16);
+         pts[0] = vp->segs[seg_idx].points[0];
+         seg_num = art_svp_add_segment (&new_vp, &n_segs_max,
+                                        &n_points_max,
+                                        1, vp->segs[seg_idx].dir,
+                                        pts,
+                                        NULL);
+         n_points_max[seg_num] = 16;
+         seg_map[seg_idx] = seg_num;
+
+         tmp1 = seg_idx;
+         for (j = i; j < n_active_segs; j++)
+           {
+             tmp2 = active_segs[j];
+             active_segs[j] = tmp1;
+             tmp1 = tmp2;
+           }
+         active_segs[n_active_segs] = tmp1;
+         n_active_segs++;
+
+         if (i > 0)
+           intersect_neighbors (i, active_segs,
+                                n_ips, n_ips_max, ips,
+                                cursor, vp);
+
+         if (i + 1 < n_active_segs)
+           intersect_neighbors (i + 1, active_segs,
+                                n_ips, n_ips_max, ips,
+                                cursor, vp);
+
+         seg_idx++;
+       }
+
+      /* all active segs cross the y scanline (considering segs to be
+       closed on top and open on bottom) */
+#ifdef VERBOSE
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         printf ("%d ", asi);
+         for (j = 0; j < n_ips[asi]; j++)
+           printf ("(%g, %g) - ",
+                   ips[asi][j].x,
+                   ips[asi][j].y);
+         printf ("(%g, %g) %s\n",
+                 vp->segs[asi].points[cursor[asi] + 1].x,
+                 vp->segs[asi].points[cursor[asi] + 1].y,
+                 vp->segs[asi].dir ? "v" : "^");
+       }
+#endif
+
+      /* advance y to the next event 
+       Note: this is quadratic. We'd probably get decent constant
+       factor speed improvement by caching the y_curs values. */
+      if (n_active_segs == 0)
+       {
+         if (seg_idx < vp->n_segs)
+           y = vp->segs[seg_idx].points[0].y;
+         /* else we're done */
+       }
+      else
+       {
+         asi = active_segs[0];
+         if (n_ips[asi] == 1)
+           y = vp->segs[asi].points[cursor[asi] + 1].y;
+         else
+           y = ips[asi][1].y;
+         for (i = 1; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (n_ips[asi] == 1)
+               y_curs = vp->segs[asi].points[cursor[asi] + 1].y;
+             else
+               y_curs = ips[asi][1].y;
+             if (y > y_curs)
+               y = y_curs;
+           }
+         if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y)
+           y = vp->segs[seg_idx].points[0].y;
+       }
+
+      first_share = -1;
+      share_x = 0; /* to avoid gcc warning, although share_x is never
+                     used when first_share is -1 */
+      /* advance cursors to reach new y */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         if (n_ips[asi] == 1)
+           p_curs = vp->segs[asi].points[cursor[asi] + 1];
+         else
+           p_curs = ips[asi][1];
+         if (p_curs.y == y)
+           {
+             svp_add_point (new_vp, n_points_max,
+                            p_curs, seg_map, active_segs, n_active_segs, i);
+
+             n_ips[asi]--;
+             for (j = 0; j < n_ips[asi]; j++)
+               ips[asi][j] = ips[asi][j + 1];
+
+             if (n_ips[asi] == 0)
+               {
+                 ips[asi][0] = p_curs;
+                 n_ips[asi] = 1;
+                 cursor[asi]++;
+               }
+
+             if (first_share < 0 || p_curs.x != share_x)
+               {
+                 /* this is where crossings are detected, and if
+                    found, the active segments switched around. */
+                     
+                 fix_crossing (first_share, i,
+                               active_segs, n_active_segs,
+                               cursor, ips, n_ips, n_ips_max, vp, seg_map,
+                               &new_vp,
+                               &n_segs_max, &n_points_max);
+
+                 first_share = i;
+                 share_x = p_curs.x;
+               }
+
+             if (cursor[asi] < vp->segs[asi].n_points - 1)
+               {
+
+                 if (i > 0)
+                   intersect_neighbors (i, active_segs,
+                                        n_ips, n_ips_max, ips,
+                                        cursor, vp);
+                 
+                 if (i + 1 < n_active_segs)
+                   intersect_neighbors (i + 1, active_segs,
+                                        n_ips, n_ips_max, ips,
+                                        cursor, vp);
+               }
+           }
+         else
+           {
+             /* not on a cursor point */
+             fix_crossing (first_share, i,
+                           active_segs, n_active_segs,
+                           cursor, ips, n_ips, n_ips_max, vp, seg_map,
+                           &new_vp,
+                           &n_segs_max, &n_points_max);
+             first_share = -1;
+           }
+       }
+
+      /* fix crossing on last shared group */
+      fix_crossing (first_share, i,
+                   active_segs, n_active_segs,
+                   cursor, ips, n_ips, n_ips_max, vp, seg_map,
+                   &new_vp,
+                   &n_segs_max, &n_points_max);
+
+#ifdef VERBOSE
+      printf ("\n");
+#endif
+    }
+
+  /* not necessary to sort, new segments only get added at y, which
+     increases monotonically */
+#if 0
+  qsort (&new_vp->segs, new_vp->n_segs, sizeof (svp_seg), svp_seg_compare);
+  {
+    int k;
+    for (k = 0; k < new_vp->n_segs - 1; k++)
+      {
+       printf ("(%g, %g) - (%g, %g) %s (%g, %g) - (%g, %g)\n",
+               new_vp->segs[k].points[0].x,
+               new_vp->segs[k].points[0].y,
+               new_vp->segs[k].points[1].x,
+               new_vp->segs[k].points[1].y,
+               svp_seg_compare (&new_vp->segs[k], &new_vp->segs[k + 1]) > 1 ? ">": "<",
+               new_vp->segs[k + 1].points[0].x,
+               new_vp->segs[k + 1].points[0].y,
+               new_vp->segs[k + 1].points[1].x,
+               new_vp->segs[k + 1].points[1].y);
+      }
+  }
+#endif
+
+  art_free (n_points_max);
+  art_free (seg_map);
+  art_free (n_ips_max);
+  art_free (n_ips);
+  art_free (ips);
+  art_free (cursor);
+  art_free (active_segs);
+
+  return new_vp;
+}
+
+#define noVERBOSE
+
+/* Rewind a svp satisfying the nocross invariant.
+
+   The winding number of a segment is defined as the winding number of
+   the points to the left while travelling in the direction of the
+   segment. Therefore it preincrements and postdecrements as a scan
+   line is traversed from left to right.
+
+   Status of this routine:
+
+   Basic correctness: Was ok in gfonted. However, this code does not
+   yet compute bboxes for the resulting svp segs.
+
+   Numerical stability: known problems in the case of horizontal
+   segments in polygons with any complexity. For actual use, randomly
+   perturbing the vertices is recommended.
+
+   Speed: good.
+
+   Precision: good, except that no attempt is made to remove "hair".
+   Doing random perturbation just makes matters worse.
+
+*/
+/**
+ * art_svp_rewind_uncrossed: Rewind an svp satisfying the nocross invariant.
+ * @vp: The original svp.
+ * @rule: The winding rule.
+ *
+ * Creates a new svp with winding number of 0 or 1 everywhere. The @rule
+ * argument specifies a rule for how winding numbers in the original
+ * @vp map to the winding numbers in the result.
+ *
+ * With @rule == ART_WIND_RULE_NONZERO, the resulting svp has a
+ * winding number of 1 where @vp has a nonzero winding number.
+ *
+ * With @rule == ART_WIND_RULE_INTERSECT, the resulting svp has a
+ * winding number of 1 where @vp has a winding number greater than
+ * 1. It is useful for computing intersections.
+ *
+ * With @rule == ART_WIND_RULE_ODDEVEN, the resulting svp has a
+ * winding number of 1 where @vp has an odd winding number. It is
+ * useful for implementing the even-odd winding rule of the
+ * PostScript imaging model.
+ *
+ * With @rule == ART_WIND_RULE_POSITIVE, the resulting svp has a
+ * winding number of 1 where @vp has a positive winding number. It is
+ * useful for implementing asymmetric difference.
+ *
+ * This routine needs to be redone from scratch with numerical robustness
+ * in mind. I'm working on it.
+ *
+ * Return value: The new svp.
+ **/
+ArtSVP *
+art_svp_rewind_uncrossed (ArtSVP *vp, ArtWindRule rule)
+{
+  int *active_segs;
+  int n_active_segs;
+  int *cursor;
+  int seg_idx;
+  double y;
+  int tmp1, tmp2;
+  int asi;
+  int i, j;
+
+  ArtSVP *new_vp;
+  int n_segs_max;
+  int *winding;
+  int left_wind;
+  int wind;
+  int keep, invert;
+
+#ifdef VERBOSE
+  print_svp (vp);
+#endif
+  n_segs_max = 16;
+  new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) +
+                               (n_segs_max - 1) * sizeof(ArtSVPSeg));
+  new_vp->n_segs = 0;
+
+  if (vp->n_segs == 0)
+    return new_vp;
+
+  winding = art_new (int, vp->n_segs);
+
+  active_segs = art_new (int, vp->n_segs);
+  cursor = art_new (int, vp->n_segs);
+
+  n_active_segs = 0;
+  seg_idx = 0;
+  y = vp->segs[0].points[0].y;
+  while (seg_idx < vp->n_segs || n_active_segs > 0)
+    {
+#ifdef VERBOSE
+      printf ("y = %g\n", y);
+#endif
+      /* delete segments ending at y from active list */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         if (vp->segs[asi].n_points - 1 == cursor[asi] &&
+             vp->segs[asi].points[cursor[asi]].y == y)
+           {
+#ifdef VERBOSE
+             printf ("deleting %d\n", asi);
+#endif
+             n_active_segs--;
+             for (j = i; j < n_active_segs; j++)
+               active_segs[j] = active_segs[j + 1];
+             i--;
+           }
+       }
+
+      /* insert new segments into the active list */
+      while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y)
+       {
+#ifdef VERBOSE
+         printf ("inserting %d\n", seg_idx);
+#endif
+         cursor[seg_idx] = 0;
+         for (i = 0; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (x_order_2 (vp->segs[seg_idx].points[0],
+                            vp->segs[seg_idx].points[1],
+                            vp->segs[asi].points[cursor[asi]],
+                            vp->segs[asi].points[cursor[asi] + 1]) == -1)
+               break;
+           }
+
+         /* Determine winding number for this segment */
+         if (i == 0)
+           left_wind = 0;
+         else if (vp->segs[active_segs[i - 1]].dir)
+           left_wind = winding[active_segs[i - 1]];
+         else
+           left_wind = winding[active_segs[i - 1]] - 1;
+
+         if (vp->segs[seg_idx].dir)
+           wind = left_wind + 1;
+         else
+           wind = left_wind;
+
+         winding[seg_idx] = wind;
+
+         switch (rule)
+           {
+           case ART_WIND_RULE_NONZERO:
+             keep = (wind == 1 || wind == 0);
+             invert = (wind == 0);
+             break;
+           case ART_WIND_RULE_INTERSECT:
+             keep = (wind == 2);
+             invert = 0;
+             break;
+           case ART_WIND_RULE_ODDEVEN:
+             keep = 1;
+             invert = !(wind & 1);
+             break;
+           case ART_WIND_RULE_POSITIVE:
+             keep = (wind == 1);
+             invert = 0;
+             break;
+           default:
+             keep = 0;
+             invert = 0;
+             break;
+           }
+
+         if (keep)
+           {
+             ArtPoint *points, *new_points;
+             int n_points;
+             int new_dir;
+
+#ifdef VERBOSE
+             printf ("keeping segment %d\n", seg_idx);
+#endif
+             n_points = vp->segs[seg_idx].n_points;
+             points = vp->segs[seg_idx].points;
+             new_points = art_new (ArtPoint, n_points);
+             memcpy (new_points, points, n_points * sizeof (ArtPoint));
+             new_dir = vp->segs[seg_idx].dir ^ invert;
+             art_svp_add_segment (&new_vp, &n_segs_max,
+                                  NULL,
+                                  n_points, new_dir, new_points,
+                                  &vp->segs[seg_idx].bbox);
+           }
+
+         tmp1 = seg_idx;
+         for (j = i; j < n_active_segs; j++)
+           {
+             tmp2 = active_segs[j];
+             active_segs[j] = tmp1;
+             tmp1 = tmp2;
+           }
+         active_segs[n_active_segs] = tmp1;
+         n_active_segs++;
+         seg_idx++;
+       }
+
+#ifdef VERBOSE
+      /* all active segs cross the y scanline (considering segs to be
+       closed on top and open on bottom) */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         printf ("%d:%d (%g, %g) - (%g, %g) %s %d\n", asi,
+                 cursor[asi],
+                 vp->segs[asi].points[cursor[asi]].x,
+                 vp->segs[asi].points[cursor[asi]].y,
+                 vp->segs[asi].points[cursor[asi] + 1].x,
+                 vp->segs[asi].points[cursor[asi] + 1].y,
+                 vp->segs[asi].dir ? "v" : "^",
+                 winding[asi]);
+       }
+#endif
+
+      /* advance y to the next event */
+      if (n_active_segs == 0)
+       {
+         if (seg_idx < vp->n_segs)
+           y = vp->segs[seg_idx].points[0].y;
+         /* else we're done */
+       }
+      else
+       {
+         asi = active_segs[0];
+         y = vp->segs[asi].points[cursor[asi] + 1].y;
+         for (i = 1; i < n_active_segs; i++)
+           {
+             asi = active_segs[i];
+             if (y > vp->segs[asi].points[cursor[asi] + 1].y)
+               y = vp->segs[asi].points[cursor[asi] + 1].y;
+           }
+         if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y)
+           y = vp->segs[seg_idx].points[0].y;
+       }
+
+      /* advance cursors to reach new y */
+      for (i = 0; i < n_active_segs; i++)
+       {
+         asi = active_segs[i];
+         while (cursor[asi] < vp->segs[asi].n_points - 1 &&
+                y >= vp->segs[asi].points[cursor[asi] + 1].y)
+           cursor[asi]++;
+       }
+#ifdef VERBOSE
+      printf ("\n");
+#endif
+    }
+  art_free (cursor);
+  art_free (active_segs);
+  art_free (winding);
+
+  return new_vp;
+}
diff --git a/lib/art/art_uta.c b/lib/art/art_uta.c
new file mode 100644 (file)
index 0000000..10bd6ee
--- /dev/null
@@ -0,0 +1,88 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_uta.h"
+
+#include <string.h>
+#include "art_misc.h"
+
+/**
+ * art_uta_new: Allocate a new uta.
+ * @x0: Left coordinate of uta.
+ * @y0: Top coordinate of uta.
+ * @x1: Right coordinate of uta.
+ * @y1: Bottom coordinate of uta.
+ *
+ * Allocates a new microtile array. The arguments are in units of
+ * tiles, not pixels.
+ *
+ * Returns: the newly allocated #ArtUta.
+ **/
+ArtUta *
+art_uta_new (int x0, int y0, int x1, int y1)
+{
+  ArtUta *uta;
+
+  uta = art_new (ArtUta, 1);
+  uta->x0 = x0;
+  uta->y0 = y0;
+  uta->width = x1 - x0;
+  uta->height = y1 - y0;
+
+  uta->utiles = art_new (ArtUtaBbox, uta->width * uta->height);
+
+  memset (uta->utiles, 0, uta->width * uta->height * sizeof(ArtUtaBbox));
+  return uta;
+  }
+
+/**
+ * art_uta_new_coords: Allocate a new uta, based on pixel coordinates.
+ * @x0: Left coordinate of uta.
+ * @y0: Top coordinate of uta.
+ * @x1: Right coordinate of uta.
+ * @y1: Bottom coordinate of uta.
+ *
+ * Allocates a new microtile array. The arguments are in pixels
+ *
+ * Returns: the newly allocated #ArtUta.
+ **/
+ArtUta *
+art_uta_new_coords (int x0, int y0, int x1, int y1)
+{
+  return art_uta_new (x0 >> ART_UTILE_SHIFT, y0 >> ART_UTILE_SHIFT,
+                     1 + (x1 >> ART_UTILE_SHIFT),
+                     1 + (y1 >> ART_UTILE_SHIFT));
+}
+
+/**
+ * art_uta_free: Free a uta.
+ * @uta: The uta to free.
+ *
+ * Frees the microtile array structure, including the actual microtile
+ * data.
+ **/
+void
+art_uta_free (ArtUta *uta)
+{
+  art_free (uta->utiles);
+  art_free (uta);
+}
+
+/* User to Aardvark! */
diff --git a/lib/art/art_uta_ops.c b/lib/art/art_uta_ops.c
new file mode 100644 (file)
index 0000000..1b42516
--- /dev/null
@@ -0,0 +1,112 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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 "config.h"
+#include "art_uta_ops.h"
+
+#include <string.h>
+#include "art_misc.h"
+#include "art_uta.h"
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+/**
+ * art_uta_union: Compute union of two uta's.
+ * @uta1: One uta.
+ * @uta2: The other uta.
+ *
+ * Computes the union of @uta1 and @uta2. The union is approximate,
+ * but coverage is guaranteed over all pixels included in either of
+ * the arguments, ie more pixels may be covered than the "exact"
+ * union.
+ *
+ * Note: this routine is used in the Gnome Canvas to accumulate the
+ * region that needs to be repainted. However, since it copies over
+ * the entire uta (which might be largish) even when the update may be
+ * small, it can be a performance bottleneck. There are two approaches
+ * to this problem, both of which are probably worthwhile. First, the
+ * generated uta's should always be limited to the visible window,
+ * thus guaranteeing that uta's never become large. Second, there
+ * should be a new, destructive union operation that only touches a
+ * small part of the uta when the update is small.
+ *
+ * Return value: The new union uta.
+ **/
+ArtUta *
+art_uta_union (ArtUta *uta1, ArtUta *uta2)
+{
+  ArtUta *uta;
+  int x0, y0, x1, y1;
+  int x, y;
+  int ix, ix1, ix2;
+  ArtUtaBbox bb, bb1, bb2;
+
+  x0 = MIN(uta1->x0, uta2->x0);
+  y0 = MIN(uta1->y0, uta2->y0);
+  x1 = MAX(uta1->x0 + uta1->width, uta2->x0 + uta2->width);
+  y1 = MAX(uta1->y0 + uta1->height, uta2->y0 + uta2->height);
+  uta = art_uta_new (x0, y0, x1, y1);
+
+  /* could move the first two if/else statements out of the loop */
+  ix = 0;
+  for (y = y0; y < y1; y++)
+    {
+      ix1 = (y - uta1->y0) * uta1->width + x0 - uta1->x0;
+      ix2 = (y - uta2->y0) * uta2->width + x0 - uta2->x0;
+      for (x = x0; x < x1; x++)
+       {
+         if (x < uta1->x0 || y < uta1->y0 ||
+             x >= uta1->x0 + uta1->width || y >= uta1->y0 + uta1->height)
+           bb1 = 0;
+         else
+           bb1 = uta1->utiles[ix1];
+
+         if (x < uta2->x0 || y < uta2->y0 ||
+             x >= uta2->x0 + uta2->width || y >= uta2->y0 + uta2->height)
+           bb2 = 0;
+         else
+           bb2 = uta2->utiles[ix2];
+
+         if (bb1 == 0)
+           bb = bb2;
+         else if (bb2 == 0)
+           bb = bb1;
+         else
+           bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb1),
+                                      ART_UTA_BBOX_X0(bb2)),
+                                  MIN(ART_UTA_BBOX_Y0(bb1),
+                                      ART_UTA_BBOX_Y0(bb2)),
+                                  MAX(ART_UTA_BBOX_X1(bb1),
+                                      ART_UTA_BBOX_X1(bb2)),
+                                  MAX(ART_UTA_BBOX_Y1(bb1),
+                                      ART_UTA_BBOX_Y1(bb2)));
+         uta->utiles[ix] = bb;
+         ix++;
+         ix1++;
+         ix2++;
+       }
+    }
+  return uta;
+}
diff --git a/lib/art/art_uta_rect.c b/lib/art/art_uta_rect.c
new file mode 100644 (file)
index 0000000..68a6053
--- /dev/null
@@ -0,0 +1,111 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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 "config.h"
+#include "art_uta_rect.h"
+
+#include "art_misc.h"
+#include "art_uta.h"
+#include "art_rect.h"
+
+/**
+ * art_uta_from_irect: Generate uta covering a rectangle.
+ * @bbox: The source rectangle.
+ *
+ * Generates a uta exactly covering @bbox. Please do not call this
+ * function with a @bbox with zero height or width.
+ *
+ * Return value: the new uta.
+ **/
+ArtUta *
+art_uta_from_irect (ArtIRect *bbox)
+{
+  ArtUta *uta;
+  ArtUtaBbox *utiles;
+  ArtUtaBbox bb;
+  int width, height;
+  int x, y;
+  int xf0, yf0, xf1, yf1;
+  int ix;
+
+  uta = art_new (ArtUta, 1);
+  uta->x0 = bbox->x0 >> ART_UTILE_SHIFT;
+  uta->y0 = bbox->y0 >> ART_UTILE_SHIFT;
+  width = ((bbox->x1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->x0;
+  height = ((bbox->y1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->y0;
+  utiles = art_new (ArtUtaBbox, width * height);
+
+  uta->width = width;
+  uta->height = height;
+  uta->utiles = utiles;
+
+  xf0 = bbox->x0 & (ART_UTILE_SIZE - 1);
+  yf0 = bbox->y0 & (ART_UTILE_SIZE - 1);
+  xf1 = ((bbox->x1 - 1) & (ART_UTILE_SIZE - 1)) + 1;
+  yf1 = ((bbox->y1 - 1) & (ART_UTILE_SIZE - 1)) + 1;
+  if (height == 1)
+    {
+      if (width == 1)
+       utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, yf1);
+      else
+       {
+         utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, yf1);
+         bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, yf1);
+         for (x = 1; x < width - 1; x++)
+           utiles[x] = bb;
+         utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, yf1);
+       }
+    }
+  else
+    {
+      if (width == 1)
+       {
+         utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, ART_UTILE_SIZE);
+         bb = ART_UTA_BBOX_CONS (xf0, 0, xf1, ART_UTILE_SIZE);
+         for (y = 1; y < height - 1; y++)
+           utiles[y] = bb;
+         utiles[y] = ART_UTA_BBOX_CONS (xf0, 0, xf1, yf1);
+       }
+      else
+       {
+         utiles[0] =
+           ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE);
+         bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE);
+         for (x = 1; x < width - 1; x++)
+           utiles[x] = bb;
+         utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, ART_UTILE_SIZE);
+         ix = width;
+         for (y = 1; y < height - 1; y++)
+           {
+             utiles[ix++] =
+               ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE);
+             bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE);
+             for (x = 1; x < width - 1; x++)
+               utiles[ix++] = bb;
+             utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, ART_UTILE_SIZE);
+           }
+         utiles[ix++] = ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, yf1);
+         bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, yf1);
+         for (x = 1; x < width - 1; x++)
+           utiles[ix++] = bb;
+         utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, yf1);
+       }
+    }
+  return uta;
+}
diff --git a/lib/art/art_uta_svp.c b/lib/art/art_uta_svp.c
new file mode 100644 (file)
index 0000000..2a0f372
--- /dev/null
@@ -0,0 +1,54 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* LGPL Copyright 1998 Raph Levien <raph@acm.org> */
+
+#include "config.h"
+#include "art_uta_svp.h"
+
+#include "art_misc.h"
+#include "art_vpath.h"
+#include "art_uta.h"
+#include "art_uta_vpath.h"
+#include "art_svp.h"
+#include "art_vpath_svp.h"
+
+/**
+ * art_uta_from_svp: Generate uta covering an svp.
+ * @svp: The source svp.
+ *
+ * Generates a uta covering @svp. The resulting uta is of course
+ * approximate, ie it may cover more pixels than covered by @svp.
+ *
+ * Note: I will want to replace this with a more direct
+ * implementation. But this gets the api in place.
+ *
+ * Return value: the new uta.
+ **/
+ArtUta *
+art_uta_from_svp (const ArtSVP *svp)
+{
+  ArtVpath *vpath;
+  ArtUta *uta;
+
+  vpath = art_vpath_from_svp (svp);
+  uta = art_uta_from_vpath (vpath);
+  art_free (vpath);
+  return uta;
+}
diff --git a/lib/art/art_uta_vpath.c b/lib/art/art_uta_vpath.c
new file mode 100644 (file)
index 0000000..d7df5ed
--- /dev/null
@@ -0,0 +1,382 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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 "config.h"
+#include "art_uta_vpath.h"
+
+#include <math.h>
+
+#include "art_misc.h"
+#include "art_vpath.h"
+#include "art_uta.h"
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+
+/**
+ * art_uta_add_line: Add a line to the uta.
+ * @uta: The uta to modify.
+ * @x0: X coordinate of line start point.
+ * @y0: Y coordinate of line start point.
+ * @x1: X coordinate of line end point.
+ * @y1: Y coordinate of line end point.
+ * @rbuf: Buffer containing first difference of winding number.
+ * @rbuf_rowstride: Rowstride of @rbuf.
+ *
+ * Add the line (@x0, @y0) - (@x1, @y1) to @uta, and also update the
+ * winding number buffer used for rendering the interior. @rbuf
+ * contains the first partial difference (in the X direction) of the
+ * winding number, measured in grid cells. Thus, each time that a line
+ * crosses a horizontal uta grid line, an entry of @rbuf is
+ * incremented if @y1 > @y0, decremented otherwise.
+ *
+ * Note that edge handling is fairly delicate. Please rtfs for
+ * details.
+ **/
+void
+art_uta_add_line (ArtUta *uta, double x0, double y0, double x1, double y1,
+                 int *rbuf, int rbuf_rowstride)
+{
+  int xmin, ymin;
+  double xmax, ymax;
+  int xmaxf, ymaxf;
+  int xmaxc, ymaxc;
+  int xt0, yt0;
+  int xt1, yt1;
+  int xf0, yf0;
+  int xf1, yf1;
+  int ix, ix1;
+  ArtUtaBbox bb;
+
+  xmin = floor (MIN(x0, x1));
+  xmax = MAX(x0, x1);
+  xmaxf = floor (xmax);
+  xmaxc = ceil (xmax);
+  ymin = floor (MIN(y0, y1));
+  ymax = MAX(y0, y1);
+  ymaxf = floor (ymax);
+  ymaxc = ceil (ymax);
+  xt0 = (xmin >> ART_UTILE_SHIFT) - uta->x0;
+  yt0 = (ymin >> ART_UTILE_SHIFT) - uta->y0;
+  xt1 = (xmaxf >> ART_UTILE_SHIFT) - uta->x0;
+  yt1 = (ymaxf >> ART_UTILE_SHIFT) - uta->y0;
+  if (xt0 == xt1 && yt0 == yt1)
+    {
+      /* entirely inside a microtile, this is easy! */
+      xf0 = xmin & (ART_UTILE_SIZE - 1);
+      yf0 = ymin & (ART_UTILE_SIZE - 1);
+      xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf;
+      yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf;
+
+      ix = yt0 * uta->width + xt0;
+      bb = uta->utiles[ix];
+      if (bb == 0)
+       bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1);
+      else
+       bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0),
+                          MIN(ART_UTA_BBOX_Y0(bb), yf0),
+                          MAX(ART_UTA_BBOX_X1(bb), xf1),
+                          MAX(ART_UTA_BBOX_Y1(bb), yf1));
+      uta->utiles[ix] = bb;
+    }
+  else
+    {
+      double dx, dy;
+      int sx, sy;
+
+      dx = x1 - x0;
+      dy = y1 - y0;
+      sx = dx > 0 ? 1 : dx < 0 ? -1 : 0;
+      sy = dy > 0 ? 1 : dy < 0 ? -1 : 0;
+      if (ymin == ymaxf)
+       {
+         /* special case horizontal (dx/dy slope would be infinite) */
+         xf0 = xmin & (ART_UTILE_SIZE - 1);
+         yf0 = ymin & (ART_UTILE_SIZE - 1);
+         xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf;
+         yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf;
+
+         ix = yt0 * uta->width + xt0;
+         ix1 = yt0 * uta->width + xt1;
+         while (ix != ix1)
+           {
+             bb = uta->utiles[ix];
+             if (bb == 0)
+               bb = ART_UTA_BBOX_CONS(xf0, yf0, ART_UTILE_SIZE, yf1);
+             else
+               bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0),
+                                  MIN(ART_UTA_BBOX_Y0(bb), yf0),
+                                  ART_UTILE_SIZE,
+                                  MAX(ART_UTA_BBOX_Y1(bb), yf1));
+             uta->utiles[ix] = bb;
+             xf0 = 0;
+             ix++;
+           }
+         bb = uta->utiles[ix];
+         if (bb == 0)
+           bb = ART_UTA_BBOX_CONS(0, yf0, xf1, yf1);
+         else
+           bb = ART_UTA_BBOX_CONS(0,
+                              MIN(ART_UTA_BBOX_Y0(bb), yf0),
+                              MAX(ART_UTA_BBOX_X1(bb), xf1),
+                              MAX(ART_UTA_BBOX_Y1(bb), yf1));
+         uta->utiles[ix] = bb;
+       }
+      else
+       {
+         /* Do a Bresenham-style traversal of the line */
+         double dx_dy;
+         double x, y;
+         double xn, yn;
+
+         /* normalize coordinates to uta origin */
+         x0 -= uta->x0 << ART_UTILE_SHIFT;
+         y0 -= uta->y0 << ART_UTILE_SHIFT;
+         x1 -= uta->x0 << ART_UTILE_SHIFT;
+         y1 -= uta->y0 << ART_UTILE_SHIFT;
+         if (dy < 0)
+           {
+             double tmp;
+
+             tmp = x0;
+             x0 = x1;
+             x1 = tmp;
+
+             tmp = y0;
+             y0 = y1;
+             y1 = tmp;
+
+             dx = -dx;
+             sx = -sx;
+             dy = -dy;
+             /* we leave sy alone, because it would always be 1,
+                and we need it for the rbuf stuff. */
+           }
+         xt0 = ((int)floor (x0) >> ART_UTILE_SHIFT);
+         xt1 = ((int)floor (x1) >> ART_UTILE_SHIFT);
+         /* now [xy]0 is above [xy]1 */
+
+         ix = yt0 * uta->width + xt0;
+         ix1 = yt1 * uta->width + xt1;
+#ifdef VERBOSE
+         printf ("%% ix = %d,%d; ix1 = %d,%d\n", xt0, yt0, xt1, yt1);
+#endif
+
+         dx_dy = dx / dy;
+         x = x0;
+         y = y0;
+         while (ix != ix1)
+           {
+             int dix;
+
+             /* figure out whether next crossing is horizontal or vertical */
+#ifdef VERBOSE
+             printf ("%% %d,%d\n", xt0, yt0);
+#endif
+             yn = (yt0 + 1) << ART_UTILE_SHIFT;
+
+             /* xn is the intercept with bottom edge of this tile. The
+                following expression is careful to result in exactly
+                x1 when yn = y1. */
+             xn = x1 + dx_dy * (yn - y1);
+
+             if (xt0 != (int)floor (xn) >> ART_UTILE_SHIFT)
+               {
+                 /* horizontal crossing */
+                 xt0 += sx;
+                 dix = sx;
+                 if (dx > 0)
+                   {
+                     xn = xt0 << ART_UTILE_SHIFT;
+                     yn = y0 + (xn - x0) / dx_dy;
+
+                     xf0 = (int)floor (x) & (ART_UTILE_SIZE - 1);
+                     xf1 = ART_UTILE_SIZE;
+                   }
+                 else
+                   {
+                     xn = (xt0 + 1) << ART_UTILE_SHIFT;
+                     yn = y0 + (xn - x0) / dx_dy;
+
+                     xf0 = 0;
+                     xmaxc = (int)ceil (x);
+                     xf1 = xmaxc - ((xt0 + 1) << ART_UTILE_SHIFT);
+                   }
+                 ymaxf = (int)floor (yn);
+                 ymaxc = (int)ceil (yn);
+                 yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf;
+               }
+             else
+               {
+                 /* vertical crossing */
+                 dix = uta->width;
+                 xf0 = (int)floor (MIN(x, xn)) & (ART_UTILE_SIZE - 1);
+                 xmax = MAX(x, xn);
+                 xmaxc = (int)ceil (xmax);
+                 xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT);
+                 yf1 = ART_UTILE_SIZE;
+
+                 if (rbuf != NULL)
+                   rbuf[yt0 * rbuf_rowstride + xt0] += sy;
+
+                 yt0++;
+               }
+             yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1);
+             bb = uta->utiles[ix];
+             if (bb == 0)
+               bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1);
+             else
+               bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0),
+                                      MIN(ART_UTA_BBOX_Y0(bb), yf0),
+                                      MAX(ART_UTA_BBOX_X1(bb), xf1),
+                                      MAX(ART_UTA_BBOX_Y1(bb), yf1));
+             uta->utiles[ix] = bb;
+
+             x = xn;
+             y = yn;
+             ix += dix;
+           }
+         xmax = MAX(x, x1);
+         xmaxc = ceil (xmax);
+         ymaxc = ceil (y1);
+         xf0 = (int)floor (MIN(x1, x)) & (ART_UTILE_SIZE - 1);
+         yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1);
+         xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT);
+         yf1 = ymaxc - (yt0 << ART_UTILE_SHIFT);
+         bb = uta->utiles[ix];
+         if (bb == 0)
+           bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1);
+         else
+           bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0),
+                                  MIN(ART_UTA_BBOX_Y0(bb), yf0),
+                                  MAX(ART_UTA_BBOX_X1(bb), xf1),
+                                  MAX(ART_UTA_BBOX_Y1(bb), yf1));
+         uta->utiles[ix] = bb;
+       }
+    }
+}
+
+/**
+ * art_uta_from_vpath: Generate uta covering a vpath.
+ * @vec: The source vpath.
+ *
+ * Generates a uta covering @vec. The resulting uta is of course
+ * approximate, ie it may cover more pixels than covered by @vec.
+ *
+ * Return value: the new uta.
+ **/
+ArtUta *
+art_uta_from_vpath (const ArtVpath *vec)
+{
+  ArtUta *uta;
+  ArtIRect bbox;
+  int *rbuf;
+  int i;
+  double x, y;
+  int sum;
+  int xt, yt;
+  ArtUtaBbox *utiles;
+  ArtUtaBbox bb;
+  int width;
+  int height;
+  int ix;
+
+  art_vpath_bbox_irect (vec, &bbox);
+
+  uta = art_uta_new_coords (bbox.x0, bbox.y0, bbox.x1, bbox.y1);
+
+  width = uta->width;
+  height = uta->height;
+  utiles = uta->utiles;
+
+  rbuf = art_new (int, width * height);
+  for (i = 0; i < width * height; i++)
+    rbuf[i] = 0;
+
+  x = 0;
+  y = 0;
+  for (i = 0; vec[i].code != ART_END; i++)
+    {
+      switch (vec[i].code)
+       {
+       case ART_MOVETO:
+         x = vec[i].x;
+         y = vec[i].y;
+         break;
+       case ART_LINETO:
+         art_uta_add_line (uta, vec[i].x, vec[i].y, x, y, rbuf, width);
+         x = vec[i].x;
+         y = vec[i].y;
+         break;
+       default:
+         /* this shouldn't happen */
+          art_free (rbuf);
+          art_free (uta);
+          return NULL;
+       }
+    }
+
+  /* now add in the filling from rbuf */
+  ix = 0;
+  for (yt = 0; yt < height; yt++)
+    {
+      sum = 0;
+      for (xt = 0; xt < width; xt++)
+       {
+         sum += rbuf[ix];
+         /* Nonzero winding rule - others are possible, but hardly
+            worth it. */
+         if (sum != 0)
+           {
+             bb = utiles[ix];
+             bb &= 0xffff0000;
+             bb |= (ART_UTILE_SIZE << 8) | ART_UTILE_SIZE;
+             utiles[ix] = bb;
+             if (xt != width - 1)
+               {
+                 bb = utiles[ix + 1];
+                 bb &= 0xffff00;
+                 bb |= ART_UTILE_SIZE;
+                 utiles[ix + 1] = bb;
+               }
+             if (yt != height - 1)
+               {
+                 bb = utiles[ix + width];
+                 bb &= 0xff0000ff;
+                 bb |= ART_UTILE_SIZE << 8;
+                 utiles[ix + width] = bb;
+                 if (xt != width - 1)
+                   {
+                     utiles[ix + width + 1] &= 0xffff;
+                   }
+               }
+           }
+         ix++;
+       }
+    }
+
+  art_free (rbuf);
+
+  return uta;
+}
diff --git a/lib/art/art_vpath.c b/lib/art/art_vpath.c
new file mode 100644 (file)
index 0000000..fa7b903
--- /dev/null
@@ -0,0 +1,241 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* Basic constructors and operations for vector paths */
+
+#include "config.h"
+#include "art_vpath.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "art_misc.h"
+
+#include "art_rect.h"
+
+/**
+ * art_vpath_add_point: Add point to vpath.
+ * @p_vpath: Where the pointer to the #ArtVpath structure is stored.
+ * @pn_points: Pointer to the number of points in *@p_vpath.
+ * @pn_points_max: Pointer to the number of points allocated.
+ * @code: The pathcode for the new point.
+ * @x: The X coordinate of the new point.
+ * @y: The Y coordinate of the new point.
+ *
+ * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath
+ * and *@pn_points_max as necessary. *@pn_points is incremented.
+ *
+ * This routine always adds the point after all points already in the
+ * vpath. Thus, it should be called in the order the points are
+ * desired.
+ **/
+void
+art_vpath_add_point (ArtVpath **p_vpath, int *pn_points, int *pn_points_max,
+                    ArtPathcode code, double x, double y)
+{
+  int i;
+
+  i = (*pn_points)++;
+  if (i == *pn_points_max)
+    art_expand (*p_vpath, ArtVpath, *pn_points_max);
+  (*p_vpath)[i].code = code;
+  (*p_vpath)[i].x = x;
+  (*p_vpath)[i].y = y;
+}
+
+/* number of steps should really depend on radius. */
+#define CIRCLE_STEPS 128
+
+/**
+ * art_vpath_new_circle: Create a new circle.
+ * @x: X coordinate of center.
+ * @y: Y coordinate of center.
+ * @r: radius.
+ *
+ * Creates a new polygon closely approximating a circle with center
+ * (@x, @y) and radius @r. Currently, the number of points used in the
+ * approximation is fixed, but that will probably change.
+ *
+ * Return value: The newly created #ArtVpath.
+ **/
+ArtVpath *
+art_vpath_new_circle (double x, double y, double r)
+{
+  int i;
+  ArtVpath *vec;
+  double theta;
+
+  vec = art_new (ArtVpath, CIRCLE_STEPS + 2);
+
+  for (i = 0; i < CIRCLE_STEPS + 1; i++)
+    {
+      vec[i].code = i ? ART_LINETO : ART_MOVETO;
+      theta = (i & (CIRCLE_STEPS - 1)) * (M_PI * 2.0 / CIRCLE_STEPS);
+      vec[i].x = x + r * cos (theta);
+      vec[i].y = y - r * sin (theta);
+    }
+  vec[i].code = ART_END;
+
+  return vec;
+}
+
+/**
+ * art_vpath_affine_transform: Affine transform a vpath.
+ * @src: Source vpath to transform.
+ * @matrix: Affine transform.
+ *
+ * Computes the affine transform of the vpath, using @matrix as the
+ * transform. @matrix is stored in the same format as PostScript, ie.
+ * x' = @matrix[0] * x + @matrix[2] * y + @matrix[4]
+ * y' = @matrix[1] * x + @matrix[3] * y + @matrix[5]
+ *
+ * Return value: the newly allocated vpath resulting from the transform.
+**/
+ArtVpath *
+art_vpath_affine_transform (const ArtVpath *src, const double matrix[6])
+{
+  int i;
+  int size;
+  ArtVpath *new;
+  double x, y;
+
+  for (i = 0; src[i].code != ART_END; i++);
+  size = i;
+
+  new = art_new (ArtVpath, size + 1);
+
+  for (i = 0; i < size; i++)
+    {
+      new[i].code = src[i].code;
+      x = src[i].x;
+      y = src[i].y;
+      new[i].x = matrix[0] * x + matrix[2] * y + matrix[4];
+      new[i].y = matrix[1] * x + matrix[3] * y + matrix[5];
+    }
+  new[i].code = ART_END;
+
+  return new;
+}
+
+/**
+ * art_vpath_bbox_drect: Determine bounding box of vpath.
+ * @vec: Source vpath.
+ * @drect: Where to store bounding box.
+ *
+ * Determines bounding box of @vec, and stores it in @drect.
+ **/
+void
+art_vpath_bbox_drect (const ArtVpath *vec, ArtDRect *drect)
+{
+  int i;
+  double x0, y0, x1, y1;
+
+  if (vec[0].code == ART_END)
+    {
+      x0 = y0 = x1 = y1 = 0;
+    }
+  else
+    {
+      x0 = x1 = vec[0].x;
+      y0 = y1 = vec[0].y;
+      for (i = 1; vec[i].code != ART_END; i++)
+       {
+         if (vec[i].x < x0) x0 = vec[i].x;
+         if (vec[i].x > x1) x1 = vec[i].x;
+         if (vec[i].y < y0) y0 = vec[i].y;
+         if (vec[i].y > y1) y1 = vec[i].y;
+       }
+    }
+  drect->x0 = x0;
+  drect->y0 = y0;
+  drect->x1 = x1;
+  drect->y1 = y1;
+}
+
+/**
+ * art_vpath_bbox_irect: Determine integer bounding box of vpath.
+ * @vec: Source vpath.
+ * idrect: Where to store bounding box.
+ *
+ * Determines integer bounding box of @vec, and stores it in @irect.
+ **/
+void
+art_vpath_bbox_irect (const ArtVpath *vec, ArtIRect *irect)
+{
+  ArtDRect drect;
+
+  art_vpath_bbox_drect (vec, &drect);
+  art_drect_to_irect (irect, &drect);
+}
+
+#define PERTURBATION 2e-3
+
+/**
+ * art_vpath_perturb: Perturb each point in vpath by small random amount.
+ * @src: Source vpath.
+ *
+ * Perturbs each of the points by a small random amount. This is
+ * helpful for cheating in cases when algorithms haven't attained
+ * numerical stability yet.
+ *
+ * Return value: Newly allocated vpath containing perturbed @src.
+ **/ 
+ArtVpath *
+art_vpath_perturb (ArtVpath *src)
+{
+  int i;
+  int size;
+  ArtVpath *new;
+  double x, y;
+  double x_start, y_start;
+  int open;
+
+  for (i = 0; src[i].code != ART_END; i++);
+  size = i;
+
+  new = art_new (ArtVpath, size + 1);
+
+  x_start = 0;
+  y_start = 0;
+  open = 0;
+  for (i = 0; i < size; i++)
+    {
+      new[i].code = src[i].code;
+      x = src[i].x + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
+      y = src[i].y + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
+      if (src[i].code == ART_MOVETO)
+       {
+         x_start = x;
+         y_start = y;
+         open = 0;
+       }
+      else if (src[i].code == ART_MOVETO_OPEN)
+       open = 1;
+      if (!open && (i + 1 == size || src[i + 1].code != ART_LINETO))
+       {
+         x = x_start;
+         y = y_start;
+       }
+      new[i].x = x;
+      new[i].y = y;
+    }
+  new[i].code = ART_END;
+
+  return new;
+}
diff --git a/lib/art/art_vpath_bpath.c b/lib/art/art_vpath_bpath.c
new file mode 100644 (file)
index 0000000..55a46e6
--- /dev/null
@@ -0,0 +1,317 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * 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.
+ */
+
+/* Basic constructors and operations for bezier paths */
+
+#include "config.h"
+#include "art_vpath_bpath.h"
+
+#include <math.h>
+
+#include "art_misc.h"
+
+#include "art_bpath.h"
+#include "art_vpath.h"
+
+/* p must be allocated 2^level points. */
+
+/* level must be >= 1 */
+ArtPoint *
+art_bezier_to_vec (double x0, double y0,
+                  double x1, double y1,
+                  double x2, double y2,
+                  double x3, double y3,
+                  ArtPoint *p,
+                  int level)
+{
+  double x_m, y_m;
+
+#ifdef VERBOSE
+  printf ("bezier_to_vec: %g,%g %g,%g %g,%g %g,%g %d\n",
+         x0, y0, x1, y1, x2, y2, x3, y3, level);
+#endif
+  if (level == 1) {
+    x_m = (x0 + 3 * (x1 + x2) + x3) * 0.125;
+    y_m = (y0 + 3 * (y1 + y2) + y3) * 0.125;
+    p->x = x_m;
+    p->y = y_m;
+    p++;
+    p->x = x3;
+    p->y = y3;
+    p++;
+#ifdef VERBOSE
+    printf ("-> (%g, %g) -> (%g, %g)\n", x_m, y_m, x3, y3);
+#endif
+  } else {
+    double xa1, ya1;
+    double xa2, ya2;
+    double xb1, yb1;
+    double xb2, yb2;
+
+    xa1 = (x0 + x1) * 0.5;
+    ya1 = (y0 + y1) * 0.5;
+    xa2 = (x0 + 2 * x1 + x2) * 0.25;
+    ya2 = (y0 + 2 * y1 + y2) * 0.25;
+    xb1 = (x1 + 2 * x2 + x3) * 0.25;
+    yb1 = (y1 + 2 * y2 + y3) * 0.25;
+    xb2 = (x2 + x3) * 0.5;
+    yb2 = (y2 + y3) * 0.5;
+    x_m = (xa2 + xb1) * 0.5;
+    y_m = (ya2 + yb1) * 0.5;
+#ifdef VERBOSE
+    printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2,
+           xb1, yb1, xb2, yb2);
+#endif
+    p = art_bezier_to_vec (x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, p, level - 1);
+    p = art_bezier_to_vec (x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, p, level - 1);
+  }
+  return p;
+}
+
+#define RENDER_LEVEL 4
+#define RENDER_SIZE (1 << (RENDER_LEVEL))
+
+/**
+ * art_vpath_render_bez: Render a bezier segment into the vpath. 
+ * @p_vpath: Where the pointer to the #ArtVpath structure is stored.
+ * @pn_points: Pointer to the number of points in *@p_vpath.
+ * @pn_points_max: Pointer to the number of points allocated.
+ * @x0: X coordinate of starting bezier point.
+ * @y0: Y coordinate of starting bezier point.
+ * @x1: X coordinate of first bezier control point.
+ * @y1: Y coordinate of first bezier control point.
+ * @x2: X coordinate of second bezier control point.
+ * @y2: Y coordinate of second bezier control point.
+ * @x3: X coordinate of ending bezier point.
+ * @y3: Y coordinate of ending bezier point.
+ * @flatness: Flatness control.
+ *
+ * Renders a bezier segment into the vector path, reallocating and
+ * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is
+ * incremented by the number of vector points added.
+ *
+ * This step includes (@x0, @y0) but not (@x3, @y3).
+ *
+ * The @flatness argument guides the amount of subdivision. The Adobe
+ * PostScript reference manual defines flatness as the maximum
+ * deviation between the any point on the vpath approximation and the
+ * corresponding point on the "true" curve, and we follow this
+ * definition here. A value of 0.25 should ensure high quality for aa
+ * rendering.
+**/
+static void
+art_vpath_render_bez (ArtVpath **p_vpath, int *pn, int *pn_max,
+                     double x0, double y0,
+                     double x1, double y1,
+                     double x2, double y2,
+                     double x3, double y3,
+                     double flatness)
+{
+  double x3_0, y3_0;
+  double z3_0_dot;
+  double z1_dot, z2_dot;
+  double z1_perp, z2_perp;
+  double max_perp_sq;
+
+  double x_m, y_m;
+  double xa1, ya1;
+  double xa2, ya2;
+  double xb1, yb1;
+  double xb2, yb2;
+
+  /* It's possible to optimize this routine a fair amount.
+
+     First, once the _dot conditions are met, they will also be met in
+     all further subdivisions. So we might recurse to a different
+     routine that only checks the _perp conditions.
+
+     Second, the distance _should_ decrease according to fairly
+     predictable rules (a factor of 4 with each subdivision). So it might
+     be possible to note that the distance is within a factor of 4 of
+     acceptable, and subdivide once. But proving this might be hard.
+
+     Third, at the last subdivision, x_m and y_m can be computed more
+     expeditiously (as in the routine above).
+
+     Finally, if we were able to subdivide by, say 2 or 3, this would
+     allow considerably finer-grain control, i.e. fewer points for the
+     same flatness tolerance. This would speed things up downstream.
+
+     In any case, this routine is unlikely to be the bottleneck. It's
+     just that I have this undying quest for more speed...
+
+  */
+
+  x3_0 = x3 - x0;
+  y3_0 = y3 - y0;
+
+  /* z3_0_dot is dist z0-z3 squared */
+  z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0;
+
+  /* todo: this test is far from satisfactory. */
+  if (z3_0_dot < 0.001)
+    goto nosubdivide;
+
+  /* we can avoid subdivision if:
+
+     z1 has distance no more than flatness from the z0-z3 line
+
+     z1 is no more z0'ward than flatness past z0-z3
+
+     z1 is more z0'ward than z3'ward on the line traversing z0-z3
+
+     and correspondingly for z2 */
+
+  /* perp is distance from line, multiplied by dist z0-z3 */
+  max_perp_sq = flatness * flatness * z3_0_dot;
+
+  z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0;
+  if (z1_perp * z1_perp > max_perp_sq)
+    goto subdivide;
+
+  z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0;
+  if (z2_perp * z2_perp > max_perp_sq)
+    goto subdivide;
+
+  z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0;
+  if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq)
+    goto subdivide;
+
+  z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0;
+  if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq)
+    goto subdivide;
+
+  if (z1_dot + z1_dot > z3_0_dot)
+    goto subdivide;
+
+  if (z2_dot + z2_dot > z3_0_dot)
+    goto subdivide;
+
+ nosubdivide:
+  /* don't subdivide */
+  art_vpath_add_point (p_vpath, pn, pn_max,
+                      ART_LINETO, x3, y3);
+  return;
+
+ subdivide:
+
+  xa1 = (x0 + x1) * 0.5;
+  ya1 = (y0 + y1) * 0.5;
+  xa2 = (x0 + 2 * x1 + x2) * 0.25;
+  ya2 = (y0 + 2 * y1 + y2) * 0.25;
+  xb1 = (x1 + 2 * x2 + x3) * 0.25;
+  yb1 = (y1 + 2 * y2 + y3) * 0.25;
+  xb2 = (x2 + x3) * 0.5;
+  yb2 = (y2 + y3) * 0.5;
+  x_m = (xa2 + xb1) * 0.5;
+  y_m = (ya2 + yb1) * 0.5;
+#ifdef VERBOSE
+  printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2,
+         xb1, yb1, xb2, yb2);
+#endif
+  art_vpath_render_bez (p_vpath, pn, pn_max,
+                       x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness);
+  art_vpath_render_bez (p_vpath, pn, pn_max,
+                       x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness);
+}
+
+/**
+ * art_bez_path_to_vec: Create vpath from bezier path.
+ * @bez: Bezier path.
+ * @flatness: Flatness control.
+ *
+ * Creates a vector path closely approximating the bezier path defined by
+ * @bez. The @flatness argument controls the amount of subdivision. In
+ * general, the resulting vpath deviates by at most @flatness pixels
+ * from the "ideal" path described by @bez.
+ *
+ * Return value: Newly allocated vpath.
+ **/
+ArtVpath *
+art_bez_path_to_vec (const ArtBpath *bez, double flatness)
+{
+  ArtVpath *vec;
+  int vec_n, vec_n_max;
+  int bez_index;
+  double x, y;
+
+  vec_n = 0;
+  vec_n_max = RENDER_SIZE;
+  vec = art_new (ArtVpath, vec_n_max);
+
+  /* Initialization is unnecessary because of the precondition that the
+     bezier path does not begin with LINETO or CURVETO, but is here
+     to make the code warning-free. */
+  x = 0;
+  y = 0;
+
+  bez_index = 0;
+  do
+    {
+#ifdef VERBOSE
+      printf ("%s %g %g\n",
+             bez[bez_index].code == ART_CURVETO ? "curveto" :
+             bez[bez_index].code == ART_LINETO ? "lineto" :
+             bez[bez_index].code == ART_MOVETO ? "moveto" :
+             bez[bez_index].code == ART_MOVETO_OPEN ? "moveto-open" :
+             "end", bez[bez_index].x3, bez[bez_index].y3);
+#endif
+      /* make sure space for at least one more code */
+      if (vec_n >= vec_n_max)
+       art_expand (vec, ArtVpath, vec_n_max);
+      switch (bez[bez_index].code)
+       {
+       case ART_MOVETO_OPEN:
+       case ART_MOVETO:
+       case ART_LINETO:
+         x = bez[bez_index].x3;
+         y = bez[bez_index].y3;
+         vec[vec_n].code = bez[bez_index].code;
+         vec[vec_n].x = x;
+         vec[vec_n].y = y;
+         vec_n++;
+         break;
+       case ART_END:
+         vec[vec_n].code = bez[bez_index].code;
+         vec[vec_n].x = 0;
+         vec[vec_n].y = 0;
+         vec_n++;
+         break;
+       case ART_CURVETO:
+#ifdef VERBOSE
+         printf ("%g,%g %g,%g %g,%g %g,%g\n", x, y,
+                        bez[bez_index].x1, bez[bez_index].y1,
+                        bez[bez_index].x2, bez[bez_index].y2,
+                        bez[bez_index].x3, bez[bez_index].y3);
+#endif
+         art_vpath_render_bez (&vec, &vec_n, &vec_n_max,
+                               x, y,
+                               bez[bez_index].x1, bez[bez_index].y1,
+                               bez[bez_index].x2, bez[bez_index].y2,
+                               bez[bez_index].x3, bez[bez_index].y3,
+                               flatness);
+         x = bez[bez_index].x3;
+         y = bez[bez_index].y3;
+         break;
+       }
+    }
+  while (bez[bez_index++].code != ART_END);
+  return vec;
+}
+
diff --git a/lib/art/art_vpath_dash.c b/lib/art/art_vpath_dash.c
new file mode 100644 (file)
index 0000000..3c98a96
--- /dev/null
@@ -0,0 +1,200 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1999-2000 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.
+ */
+
+/* Apply a dash style to a vector path. */
+
+#include "config.h"
+#include "art_vpath_dash.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "art_misc.h"
+
+#include "art_vpath.h"
+
+
+/* Return the length of the largest subpath within vpath */
+static int
+art_vpath_dash_max_subpath (const ArtVpath *vpath)
+{
+  int max_subpath;
+  int i;
+  int start;
+
+  max_subpath = 0;
+  start = 0;
+  for (i = 0; vpath[i].code != ART_END; i++)
+    {
+      if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN)
+       {
+         if (i - start > max_subpath)
+           max_subpath = i - start;
+         start = i;
+       }
+    }
+  if (i - start > max_subpath)
+    max_subpath = i - start;
+
+  return max_subpath;
+}
+
+/**
+ * art_vpath_dash: Add dash style to vpath.
+ * @vpath: Original vpath.
+ * @dash: Dash style.
+ *
+ * Creates a new vpath that is the result of applying dash style @dash
+ * to @vpath.
+ *
+ * This implementation has two known flaws:
+ *
+ * First, it adds a spurious break at the beginning of the vpath. The
+ * only way I see to resolve this flaw is to run the state forward one
+ * dash break at the beginning, and fix up by looping back to the
+ * first dash break at the end. This is doable but of course adds some
+ * complexity.
+ *
+ * Second, it does not suppress output points that are within epsilon
+ * of each other.
+ *
+ * Return value: Newly created vpath.
+ **/
+ArtVpath *
+art_vpath_dash (const ArtVpath *vpath, const ArtVpathDash *dash)
+{
+  int max_subpath;
+  double *dists;
+  ArtVpath *result;
+  int n_result, n_result_max;
+  int start, end;
+  int i;
+  double total_dist;
+
+  /* state while traversing dasharray - offset is offset of current dash
+     value, toggle is 0 for "off" and 1 for "on", and phase is the distance
+     in, >= 0, < dash->dash[offset]. */
+  int offset, toggle;
+  double phase;
+
+  /* initial values */
+  int offset_init, toggle_init;
+  double phase_init;
+
+  max_subpath = art_vpath_dash_max_subpath (vpath);
+  dists = art_new (double, max_subpath);
+
+  n_result = 0;
+  n_result_max = 16;
+  result = art_new (ArtVpath, n_result_max);
+
+  /* determine initial values of dash state */
+  toggle_init = 1;
+  offset_init = 0;
+  phase_init = dash->offset;
+  while (phase_init >= dash->dash[offset_init])
+    {
+      toggle_init = !toggle_init;
+      phase_init -= dash->dash[offset_init];
+      offset_init++;
+      if (offset_init == dash->n_dash)
+       offset_init = 0;
+    }
+
+  for (start = 0; vpath[start].code != ART_END; start = end)
+    {
+      for (end = start + 1; vpath[end].code == ART_LINETO; end++);
+      /* subpath is [start..end) */
+      total_dist = 0;
+      for (i = start; i < end - 1; i++)
+       {
+         double dx, dy;
+
+         dx = vpath[i + 1].x - vpath[i].x;
+         dy = vpath[i + 1].y - vpath[i].y;
+         dists[i - start] = sqrt (dx * dx + dy * dy);
+         total_dist += dists[i - start];
+       }
+      if (total_dist <= dash->dash[offset_init] - phase_init)
+       {
+         /* subpath fits entirely within first dash */
+         if (toggle_init)
+           {
+             for (i = start; i < end; i++)
+               art_vpath_add_point (&result, &n_result, &n_result_max,
+                                    vpath[i].code, vpath[i].x, vpath[i].y);
+           }
+       }
+      else
+       {
+         /* subpath is composed of at least one dash - thus all
+            generated pieces are open */
+         double dist;
+
+         phase = phase_init;
+         offset = offset_init;
+         toggle = toggle_init;
+         dist = 0;
+         i = start;
+         if (toggle)
+           art_vpath_add_point (&result, &n_result, &n_result_max,
+                                ART_MOVETO_OPEN, vpath[i].x, vpath[i].y);
+         while (i != end - 1)
+           {
+             if (dists[i - start] - dist > dash->dash[offset] - phase)
+               {
+                 /* dash boundary is next */
+                 double a;
+                 double x, y;
+
+                 dist += dash->dash[offset] - phase;
+                 a = dist / dists[i - start];
+                 x = vpath[i].x + a * (vpath[i + 1].x - vpath[i].x);
+                 y = vpath[i].y + a * (vpath[i + 1].y - vpath[i].y);
+                 art_vpath_add_point (&result, &n_result, &n_result_max,
+                                      toggle ? ART_LINETO : ART_MOVETO_OPEN,
+                                      x, y);
+                 /* advance to next dash */
+                 toggle = !toggle;
+                 phase = 0;
+                 offset++;
+                 if (offset == dash->n_dash)
+                   offset = 0;
+               }
+             else
+               {
+                 /* end of line in vpath is next */
+                 phase += dists[i - start] - dist;
+                 i++;
+                 dist = 0;
+                 if (toggle)
+                   art_vpath_add_point (&result, &n_result, &n_result_max,
+                                        ART_LINETO, vpath[i].x, vpath[i].y);
+               }
+           }
+       }
+    }
+
+  art_vpath_add_point (&result, &n_result, &n_result_max,
+                      ART_END, 0, 0);
+
+  art_free (dists);
+
+  return result;
+}
diff --git a/lib/art/art_vpath_svp.c b/lib/art/art_vpath_svp.c
new file mode 100644 (file)
index 0000000..000265c
--- /dev/null
@@ -0,0 +1,196 @@
+/* Libart_LGPL - library of basic graphic primitives
+ * Copyright (C) 1998-2000 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.
+ */
+
+/* "Unsort" a sorted vector path into an ordinary vector path. */
+
+#include "config.h"
+#include "art_vpath_svp.h"
+
+#include <stdio.h> /* for printf - debugging */
+#include "art_misc.h"
+
+#include "art_vpath.h"
+#include "art_svp.h"
+
+typedef struct _ArtVpathSVPEnd ArtVpathSVPEnd;
+
+struct _ArtVpathSVPEnd {
+  int seg_num;
+  int which; /* 0 = top, 1 = bottom */
+  double x, y;
+};
+
+#define EPSILON 1e-6
+
+static int
+art_vpath_svp_point_compare (double x1, double y1, double x2, double y2)
+{
+  if (y1 - EPSILON > y2) return 1;
+  if (y1 + EPSILON < y2) return -1;
+  if (x1 - EPSILON > x2) return 1;
+  if (x1 + EPSILON < x2) return -1;
+  return 0;
+}
+
+static int
+art_vpath_svp_compare (const void *s1, const void *s2)
+{
+  const ArtVpathSVPEnd *e1 = s1;
+  const ArtVpathSVPEnd *e2 = s2;
+
+  return art_vpath_svp_point_compare (e1->x, e1->y, e2->x, e2->y);
+}
+
+/* Convert from sorted vector path representation into regular
+   vector path representation.
+
+   Status of this routine:
+
+   Basic correctness: Only works with closed paths.
+
+   Numerical stability: Not known to work when more than two segments
+   meet at a point.
+
+   Speed: Should be pretty good.
+
+   Precision: Does not degrade precision.
+
+*/
+/**
+ * art_vpath_from_svp: Convert from svp to vpath form.
+ * @svp: Original #ArtSVP.
+ *
+ * Converts the sorted vector path @svp into standard vpath form.
+ *
+ * Return value: the newly allocated vpath.
+ **/
+ArtVpath *
+art_vpath_from_svp (const ArtSVP *svp)
+{
+  int n_segs = svp->n_segs;
+  ArtVpathSVPEnd *ends;
+  ArtVpath *new;
+  int *visited;
+  int n_new, n_new_max;
+  int i, k;
+  int j = 0; /* Quiet compiler */
+  int seg_num;
+  int first;
+  double last_x, last_y;
+  int n_points;
+  int pt_num;
+
+  last_x = 0; /* to eliminate "uninitialized" warning */
+  last_y = 0;
+
+  ends = art_new (ArtVpathSVPEnd, n_segs * 2);
+  for (i = 0; i < svp->n_segs; i++)
+    {
+      int lastpt;
+
+      ends[i * 2].seg_num = i;
+      ends[i * 2].which = 0;
+      ends[i * 2].x = svp->segs[i].points[0].x;
+      ends[i * 2].y = svp->segs[i].points[0].y;
+
+      lastpt = svp->segs[i].n_points - 1;
+      ends[i * 2 + 1].seg_num = i;
+      ends[i * 2 + 1].which = 1;
+      ends[i * 2 + 1].x = svp->segs[i].points[lastpt].x;
+      ends[i * 2 + 1].y = svp->segs[i].points[lastpt].y;
+    }
+  qsort (ends, n_segs * 2, sizeof (ArtVpathSVPEnd), art_vpath_svp_compare);
+
+  n_new = 0;
+  n_new_max = 16; /* I suppose we _could_ estimate this from traversing
+                    the svp, so we don't have to reallocate */
+  new = art_new (ArtVpath, n_new_max);
+
+  visited = art_new (int, n_segs);
+  for (i = 0; i < n_segs; i++)
+    visited[i] = 0;
+
+  first = 1;
+  for (i = 0; i < n_segs; i++)
+    {
+      if (!first)
+       {
+         /* search for the continuation of the existing subpath */
+         /* This could be a binary search (which is why we sorted, above) */
+         for (j = 0; j < n_segs * 2; j++)
+           {
+             if (!visited[ends[j].seg_num] &&
+                 art_vpath_svp_point_compare (last_x, last_y,
+                                              ends[j].x, ends[j].y) == 0)
+               break;
+           }
+         if (j == n_segs * 2)
+           first = 1;
+       }
+      if (first)
+       {
+         /* start a new subpath */
+         for (j = 0; j < n_segs * 2; j++)
+           if (!visited[ends[j].seg_num])
+             break;
+       }
+      if (j == n_segs * 2)
+       {
+         printf ("failure\n");
+       }
+      seg_num = ends[j].seg_num;
+      n_points = svp->segs[seg_num].n_points;
+      for (k = 0; k < n_points; k++)
+       {
+         pt_num = svp->segs[seg_num].dir ? k : n_points - (1 + k);
+         if (k == 0)
+           {
+             if (first)
+               {
+                 art_vpath_add_point (&new, &n_new, &n_new_max,
+                                      ART_MOVETO,
+                                      svp->segs[seg_num].points[pt_num].x,
+                                      svp->segs[seg_num].points[pt_num].y);
+               }
+           }
+         else
+           {
+             art_vpath_add_point (&new, &n_new, &n_new_max,
+                                  ART_LINETO,
+                                  svp->segs[seg_num].points[pt_num].x,
+                                  svp->segs[seg_num].points[pt_num].y);
+             if (k == n_points - 1)
+               {
+                 last_x = svp->segs[seg_num].points[pt_num].x;
+                 last_y = svp->segs[seg_num].points[pt_num].y;
+                 /* to make more robust, check for meeting first_[xy],
+                    set first if so */
+               }
+           }
+         first = 0;
+       }
+      visited[seg_num] = 1;
+    }
+
+  art_vpath_add_point (&new, &n_new, &n_new_max,
+                      ART_END, 0, 0);
+  art_free (visited);
+  art_free (ends);
+  return new;
+}
diff --git a/lib/art/gen_art_config.c b/lib/art/gen_art_config.c
new file mode 100644 (file)
index 0000000..8d88815
--- /dev/null
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include "config.h"
+#include <stdlib.h>
+
+/**
+ * A little utility function to generate header info.
+ *
+ * Yes, it would be possible to do this using more "native" autoconf
+ * features, but I personally find this approach to be cleaner.
+ *
+ * The output of this program is generally written to art_config.h,
+ * which is installed in libart's include dir.
+ **/
+
+static void
+die (char *why)
+{
+  fprintf (stderr, "gen_art_config: %s\n", why);
+  exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+  printf ("/* Automatically generated by gen_art_config.c */\n"
+         "\n"
+         "#define ART_SIZEOF_CHAR %d\n"
+         "#define ART_SIZEOF_SHORT %d\n"
+         "#define ART_SIZEOF_INT %d\n"
+         "#define ART_SIZEOF_LONG %d\n"
+         "\n",
+         (int)sizeof(char), (int)sizeof(short), (int)sizeof(int), (int)sizeof(long));
+
+  if (sizeof(char) == 1)
+    printf ("typedef unsigned char art_u8;\n");
+  else
+    die ("sizeof(char) != 1");
+
+  if (sizeof(short) == 2)
+    printf ("typedef unsigned short art_u16;\n");
+  else
+    die ("sizeof(short) != 2");
+
+  if (sizeof(int) == 4)
+    printf ("typedef unsigned int art_u32;\n");
+  else if (sizeof(long) == 4)
+    printf ("typedef unsigned long art_u32;\n");
+  else
+    die ("sizeof(int) != 4 and sizeof(long) != 4");
+
+  return 0;
+}