Exemplo n.º 1
0
/* 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
}
/**
 * art_vpath_new_vpath_array: Generate a heap-based ArtVpath-array from a vpath iterator
 * @src: Source Iterator
 * Return value: Newly allocated vpath
 **/
ArtVpath *art_vpath_new_vpath_array(ArtVpathIterator *src)
{
  ArtVpath *result;
  ArtVpath *current;
  int n_result, n_result_max;

  if (!src->current(src)) return 0;

  n_result = 0;
  n_result_max = 16;
  result = art_new (ArtVpath, n_result_max);

  while(1) {
    current =  src->current(src);
    if (!current) break;

    art_vpath_add_point (&result, &n_result, &n_result_max,
                         current->code, current->x, current->y);
    if (current->code == ART_END) break;
    src->next(src);
  }
  return result;
}
Exemplo n.º 3
0
/**
 * 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;
}
Exemplo n.º 4
0
/* 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;
    }
}
Exemplo n.º 5
0
/* 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);

    }
}
Exemplo n.º 6
0
/**
 * 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);
}