summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'base/gxpcopy.c')
-rw-r--r--base/gxpcopy.c1046
1 files changed, 1046 insertions, 0 deletions
diff --git a/base/gxpcopy.c b/base/gxpcopy.c
new file mode 100644
index 00000000..5d77de78
--- /dev/null
+++ b/base/gxpcopy.c
@@ -0,0 +1,1046 @@
+/* Copyright (C) 2001-2019 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied,
+ modified or distributed except as expressly authorized under the terms
+ of the license contained in the file LICENSE in this distribution.
+
+ Refer to licensing information at http://www.artifex.com or contact
+ Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato,
+ CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+
+/* Path copying and flattening */
+#include "math_.h"
+#include "gx.h"
+#include "gserrors.h"
+#include "gxfixed.h"
+#include "gxfarith.h"
+#include "gxgstate.h" /* for access to line params */
+#include "gzpath.h"
+
+/* Forward declarations */
+static void adjust_point_to_tangent(segment *, const segment *,
+ const gs_fixed_point *);
+
+static inline int
+break_line_if_long(gx_path *ppath, const segment *pseg)
+{
+ fixed x0 = ppath->position.x;
+ fixed y0 = ppath->position.y;
+
+ if (gx_check_fixed_diff_overflow(pseg->pt.x, x0) ||
+ gx_check_fixed_diff_overflow(pseg->pt.y, y0)) {
+ fixed x, y;
+
+ if (gx_check_fixed_sum_overflow(pseg->pt.x, x0))
+ x = (pseg->pt.x >> 1) + (x0 >> 1);
+ else
+ x = (pseg->pt.x + x0) >> 1;
+ if (gx_check_fixed_sum_overflow(pseg->pt.y, y0))
+ y = (pseg->pt.y >> 1) + (y0 >> 1);
+ else
+ y = (pseg->pt.y + y0) >> 1;
+ return gx_path_add_line_notes(ppath, x, y, pseg->notes);
+ /* WARNING: Stringly speaking, the next half segment must get
+ the sn_not_first flag. We don't bother, because that flag
+ has no important meaning with colinear segments.
+ */
+ }
+ return 0;
+}
+static inline int
+break_gap_if_long(gx_path *ppath, const segment *pseg)
+{
+ fixed x0 = ppath->position.x;
+ fixed y0 = ppath->position.y;
+
+ if (gx_check_fixed_diff_overflow(pseg->pt.x, x0) ||
+ gx_check_fixed_diff_overflow(pseg->pt.y, y0)) {
+ fixed x, y;
+
+ if (gx_check_fixed_sum_overflow(pseg->pt.x, x0))
+ x = (pseg->pt.x >> 1) + (x0 >> 1);
+ else
+ x = (pseg->pt.x + x0) >> 1;
+ if (gx_check_fixed_sum_overflow(pseg->pt.y, y0))
+ y = (pseg->pt.y >> 1) + (y0 >> 1);
+ else
+ y = (pseg->pt.y + y0) >> 1;
+ return gx_path_add_gap_notes(ppath, x, y, pseg->notes);
+ /* WARNING: Stringly speaking, the next half segment must get
+ the sn_not_first flag. We don't bother, because that flag
+ has no important meaning with colinear segments.
+ */
+ }
+ return 0;
+}
+
+/* Copy a path, optionally flattening or monotonizing it. */
+/* If the copy fails, free the new path. */
+int
+gx_path_copy_reducing(const gx_path *ppath_old, gx_path *ppath,
+ fixed fixed_flatness, const gs_gstate *pgs,
+ gx_path_copy_options options)
+{
+ const segment *pseg;
+ fixed flat = fixed_flatness;
+ gs_fixed_point expansion;
+ /*
+ * Since we're going to be adding to the path, unshare it
+ * before we start.
+ */
+ int code = gx_path_unshare(ppath);
+
+ if (code < 0)
+ return code;
+#ifdef DEBUG
+ if (gs_debug_c('P'))
+ gx_dump_path(ppath_old, "before reducing");
+#endif
+ if (options & pco_for_stroke) {
+ /* Precompute the maximum expansion of the bounding box. */
+ double width = pgs->line_params.half_width;
+
+ expansion.x =
+ float2fixed((fabs(pgs->ctm.xx) + fabs(pgs->ctm.yx)) * width) * 2;
+ expansion.y =
+ float2fixed((fabs(pgs->ctm.xy) + fabs(pgs->ctm.yy)) * width) * 2;
+ } else
+ expansion.x = expansion.y = 0; /* Quiet gcc warning. */
+ pseg = (const segment *)(ppath_old->first_subpath);
+ while (pseg) {
+ switch (pseg->type) {
+ case s_start:
+ code = gx_path_add_point(ppath,
+ pseg->pt.x, pseg->pt.y);
+ break;
+ case s_curve:
+ {
+ const curve_segment *pc = (const curve_segment *)pseg;
+
+ if (fixed_flatness == max_fixed) { /* don't flatten */
+ if (options & pco_monotonize)
+ code = gx_curve_monotonize(ppath, pc);
+ else
+ code = gx_path_add_curve_notes(ppath,
+ pc->p1.x, pc->p1.y, pc->p2.x, pc->p2.y,
+ pc->pt.x, pc->pt.y, pseg->notes);
+ } else {
+ fixed x0 = ppath->position.x;
+ fixed y0 = ppath->position.y;
+ segment_notes notes = pseg->notes;
+ curve_segment cseg;
+ int k;
+
+ if (options & pco_for_stroke) {
+ /*
+ * When flattening for stroking, the flatness
+ * must apply to the outside of the resulting
+ * stroked region. We approximate this by
+ * dividing the flatness by the ratio of the
+ * expanded bounding box to the original
+ * bounding box. This is crude, but pretty
+ * simple to calculate, and produces reasonably
+ * good results.
+ */
+ fixed min01, max01, min23, max23;
+ fixed ex, ey, flat_x, flat_y;
+
+#define SET_EXTENT(r, c0, c1, c2, c3)\
+ BEGIN\
+ if (c0 < c1) min01 = c0, max01 = c1;\
+ else min01 = c1, max01 = c0;\
+ if (c2 < c3) min23 = c2, max23 = c3;\
+ else min23 = c3, max23 = c2;\
+ r = max(max01, max23) - min(min01, min23);\
+ END
+ SET_EXTENT(ex, x0, pc->p1.x, pc->p2.x, pc->pt.x);
+ SET_EXTENT(ey, y0, pc->p1.y, pc->p2.y, pc->pt.y);
+#undef SET_EXTENT
+ /*
+ * We check for the degenerate case specially
+ * to avoid a division by zero.
+ */
+ if (ex == 0 || ey == 0)
+ if (ex != 0) {
+ flat = fixed_mult_quo(fixed_flatness, ex,
+ ex + expansion.x);
+ k = gx_curve_log2_samples(x0,y0,pc,flat);
+ } else if (ey != 0) {
+ flat = fixed_mult_quo(fixed_flatness, ey,
+ ey + expansion.y);
+ k = gx_curve_log2_samples(x0,y0,pc,flat);
+ } else
+ k = 0;
+ else {
+ flat_x =
+ fixed_mult_quo(fixed_flatness, ex,
+ ex + expansion.x);
+ flat_y =
+ fixed_mult_quo(fixed_flatness, ey,
+ ey + expansion.y);
+ flat = min(flat_x, flat_y);
+ k = gx_curve_log2_samples(x0, y0, pc, flat);
+ }
+ } else
+ k = gx_curve_log2_samples(x0, y0, pc, flat);
+ if (options & pco_accurate) {
+ segment *start;
+ segment *end;
+
+ /*
+ * Add an extra line, which will become
+ * the tangent segment.
+ */
+ code = gx_path_add_line_notes(ppath, x0, y0,
+ notes);
+ if (code < 0)
+ break;
+ start = ppath->current_subpath->last;
+ notes |= sn_not_first;
+ cseg = *pc;
+ code = gx_subdivide_curve(ppath, k, &cseg, notes);
+ if (code < 0)
+ break;
+ /*
+ * Adjust the first and last segments so that
+ * they line up with the tangents.
+ */
+ end = ppath->current_subpath->last;
+ if ((code = gx_path_add_line_notes(ppath,
+ ppath->position.x,
+ ppath->position.y,
+ pseg->notes | sn_not_first)) < 0)
+ break;
+ if (start->next->pt.x != pc->p1.x || start->next->pt.y != pc->p1.y)
+ adjust_point_to_tangent(start, start->next, &pc->p1);
+ else if (start->next->pt.x != pc->p2.x || start->next->pt.y != pc->p2.y)
+ adjust_point_to_tangent(start, start->next, &pc->p2);
+ else
+ adjust_point_to_tangent(start, start->next, &end->prev->pt);
+ if (end->prev->pt.x != pc->p2.x || end->prev->pt.y != pc->p2.y)
+ adjust_point_to_tangent(end, end->prev, &pc->p2);
+ else if (end->prev->pt.x != pc->p1.x || end->prev->pt.y != pc->p1.y)
+ adjust_point_to_tangent(end, end->prev, &pc->p1);
+ else
+ adjust_point_to_tangent(end, end->prev, &start->pt);
+ } else {
+ cseg = *pc;
+ code = gx_subdivide_curve(ppath, k, &cseg, notes);
+ }
+ }
+ break;
+ }
+ case s_line:
+ code = break_line_if_long(ppath, pseg);
+ if (code < 0)
+ break;
+ code = gx_path_add_line_notes(ppath,
+ pseg->pt.x, pseg->pt.y, pseg->notes);
+ break;
+ case s_gap:
+ code = break_gap_if_long(ppath, pseg);
+ if (code < 0)
+ break;
+ code = gx_path_add_gap_notes(ppath,
+ pseg->pt.x, pseg->pt.y, pseg->notes);
+ break;
+ case s_dash:
+ {
+ const dash_segment *pd = (const dash_segment *)pseg;
+
+ code = gx_path_add_dash_notes(ppath,
+ pd->pt.x, pd->pt.y, pd->tangent.x, pd->tangent.y, pseg->notes);
+ break;
+ }
+ case s_line_close:
+ code = break_line_if_long(ppath, pseg);
+ if (code < 0)
+ break;
+ code = gx_path_close_subpath(ppath);
+ break;
+ default: /* can't happen */
+ code = gs_note_error(gs_error_unregistered);
+ }
+ if (code < 0) {
+ gx_path_new(ppath);
+ return code;
+ }
+ pseg = pseg->next;
+ }
+ if (path_last_is_moveto(ppath_old)) {
+ code = gx_path_add_point(ppath, ppath_old->position.x,
+ ppath_old->position.y);
+ if (code < 0) {
+ gx_path_new(ppath);
+ return code;
+ }
+ }
+ if (ppath_old->bbox_set) {
+ if (ppath->bbox_set) {
+ ppath->bbox.p.x = min(ppath_old->bbox.p.x, ppath->bbox.p.x);
+ ppath->bbox.p.y = min(ppath_old->bbox.p.y, ppath->bbox.p.y);
+ ppath->bbox.q.x = max(ppath_old->bbox.q.x, ppath->bbox.q.x);
+ ppath->bbox.q.y = max(ppath_old->bbox.q.y, ppath->bbox.q.y);
+ } else {
+ ppath->bbox_set = true;
+ ppath->bbox = ppath_old->bbox;
+ }
+ }
+#ifdef DEBUG
+ if (gs_debug_c('P'))
+ gx_dump_path(ppath, "after reducing");
+#endif
+ return 0;
+}
+
+/*
+ * Adjust one end of a line (the first or last line of a flattened curve)
+ * so it falls on the curve tangent. The closest point on the line from
+ * (0,0) to (C,D) to a point (U,V) -- i.e., the point on the line at which
+ * a perpendicular line from the point intersects it -- is given by
+ * T = (C*U + D*V) / (C^2 + D^2)
+ * (X,Y) = (C*T,D*T)
+ * However, any smaller value of T will also work: the one we actually
+ * use is 0.25 * the value we just derived. We must check that
+ * numerical instabilities don't lead to a negative value of T.
+ */
+static void
+adjust_point_to_tangent(segment * pseg, const segment * next,
+ const gs_fixed_point * p1)
+{
+ const fixed x0 = pseg->pt.x, y0 = pseg->pt.y;
+ const fixed fC = p1->x - x0, fD = p1->y - y0;
+
+ /*
+ * By far the commonest case is that the end of the curve is
+ * horizontal or vertical. Check for this specially, because
+ * we can handle it with far less work (and no floating point).
+ */
+ if (fC == 0) {
+ /* Vertical tangent. */
+ const fixed DT = arith_rshift(next->pt.y - y0, 2);
+
+ if (fD == 0)
+ return; /* anomalous case */
+ if_debug1('2', "[2]adjusting vertical: DT = %g\n",
+ fixed2float(DT));
+ if ((DT ^ fD) > 0)
+ pseg->pt.y = DT + y0;
+ } else if (fD == 0) {
+ /* Horizontal tangent. */
+ const fixed CT = arith_rshift(next->pt.x - x0, 2);
+
+ if_debug1('2', "[2]adjusting horizontal: CT = %g\n",
+ fixed2float(CT));
+ if ((CT ^ fC) > 0)
+ pseg->pt.x = CT + x0;
+ } else {
+ /* General case. */
+ const double C = fC, D = fD;
+ double T = (C * (next->pt.x - x0) + D * (next->pt.y - y0)) /
+ (C * C + D * D);
+
+ if_debug3('2', "[2]adjusting: C = %g, D = %g, T = %g\n",
+ C, D, T);
+ if (T > 0) {
+ if (T > 1) {
+ /* Don't go outside the curve bounding box. */
+ T = 1;
+ }
+ pseg->pt.x = arith_rshift((fixed) (C * T), 2) + x0;
+ pseg->pt.y = arith_rshift((fixed) (D * T), 2) + y0;
+ }
+ }
+}
+
+/* ---------------- Monotonic curves ---------------- */
+
+/* Test whether a path is free of non-monotonic curves. */
+bool
+gx_path__check_curves(const gx_path * ppath, gx_path_copy_options options, fixed fixed_flat)
+{
+ const segment *pseg = (const segment *)(ppath->first_subpath);
+ gs_fixed_point pt0;
+
+ pt0.x = pt0.y = 0; /* Quiet gcc warning. */
+ while (pseg) {
+ switch (pseg->type) {
+ case s_start:
+ {
+ const subpath *psub = (const subpath *)pseg;
+
+ /* Skip subpaths without curves. */
+ if (!psub->curve_count)
+ pseg = psub->last;
+ }
+ break;
+ case s_line:
+ case s_gap:
+ if (gx_check_fixed_diff_overflow(pseg->pt.x, pt0.x) ||
+ gx_check_fixed_diff_overflow(pseg->pt.y, pt0.y))
+ return false;
+ break;
+ case s_curve:
+ {
+ const curve_segment *pc = (const curve_segment *)pseg;
+
+ if (options & pco_monotonize) {
+ double t[2];
+ int nz = gx_curve_monotonic_points(pt0.y,
+ pc->p1.y, pc->p2.y, pc->pt.y, t);
+
+ if (nz != 0)
+ return false;
+ nz = gx_curve_monotonic_points(pt0.x,
+ pc->p1.x, pc->p2.x, pc->pt.x, t);
+ if (nz != 0)
+ return false;
+ }
+ if (options & pco_small_curves) {
+ fixed ax, bx, cx, ay, by, cy;
+ int k = gx_curve_log2_samples(pt0.x, pt0.y, pc, fixed_flat);
+
+ if(!curve_coeffs_ranged(pt0.x, pc->p1.x, pc->p2.x, pc->pt.x,
+ pt0.y, pc->p1.y, pc->p2.y, pc->pt.y,
+ &ax, &bx, &cx, &ay, &by, &cy, k))
+ return false;
+ if (gx_check_fixed_diff_overflow(pseg->pt.x, pt0.x) ||
+ gx_check_fixed_diff_overflow(pseg->pt.y, pt0.y))
+ return false;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ pt0 = pseg->pt;
+ pseg = pseg->next;
+ }
+ return true;
+}
+
+/* Test whether a path is free of long segments. */
+/* WARNING : This function checks the distance between
+ * the starting point and the ending point of a segment.
+ * When they are not too far, a curve nevertheless may be too long.
+ * Don't worry about it here, because we assume
+ * this function is never called with paths which have curves.
+ */
+bool
+gx_path_has_long_segments(const gx_path * ppath)
+{
+ const segment *pseg = (const segment *)(ppath->first_subpath);
+ gs_fixed_point pt0;
+
+ pt0.x = pt0.y = 0; /* Quiet gcc warning. */
+ while (pseg) {
+ switch (pseg->type) {
+ case s_start:
+ break;
+ default:
+ if (gx_check_fixed_diff_overflow(pseg->pt.x, pt0.x) ||
+ gx_check_fixed_diff_overflow(pseg->pt.y, pt0.y))
+ return true;
+ break;
+ }
+ pt0 = pseg->pt;
+ pseg = pseg->next;
+ }
+ return false;
+}
+
+/* Monotonize a curve, by splitting it if necessary. */
+/* In the worst case, this could split the curve into 9 pieces. */
+int
+gx_curve_monotonize(gx_path * ppath, const curve_segment * pc)
+{
+ fixed x0 = ppath->position.x, y0 = ppath->position.y;
+ segment_notes notes = pc->notes;
+ double t[5], tt = 1, tp;
+ int c[5];
+ int n0, n1, n, i, j, k = 0;
+ fixed ax, bx, cx, ay, by, cy, v01, v12;
+ fixed px, py, qx, qy, rx, ry, sx, sy;
+ const double delta = 0.0000001;
+
+ /* Roots of the derivative : */
+ n0 = gx_curve_monotonic_points(x0, pc->p1.x, pc->p2.x, pc->pt.x, t);
+ n1 = gx_curve_monotonic_points(y0, pc->p1.y, pc->p2.y, pc->pt.y, t + n0);
+ n = n0 + n1;
+ if (n == 0)
+ return gx_path_add_curve_notes(ppath, pc->p1.x, pc->p1.y,
+ pc->p2.x, pc->p2.y, pc->pt.x, pc->pt.y, notes);
+ if (n0 > 0)
+ c[0] = 1;
+ if (n0 > 1)
+ c[1] = 1;
+ if (n1 > 0)
+ c[n0] = 2;
+ if (n1 > 1)
+ c[n0 + 1] = 2;
+ /* Order roots : */
+ for (i = 0; i < n; i++)
+ for (j = i + 1; j < n; j++)
+ if (t[i] > t[j]) {
+ int w;
+ double v = t[i]; t[i] = t[j]; t[j] = v;
+ w = c[i]; c[i] = c[j]; c[j] = w;
+ }
+ /* Drop roots near zero : */
+ for (k = 0; k < n; k++)
+ if (t[k] >= delta)
+ break;
+ /* Merge close roots, and drop roots at 1 : */
+ if (t[n - 1] > 1 - delta)
+ n--;
+ for (i = k + 1, j = k; i < n && t[k] < 1 - delta; i++)
+ if (any_abs(t[i] - t[j]) < delta) {
+ t[j] = (t[j] + t[i]) / 2; /* Unlikely 3 roots are close. */
+ c[j] |= c[i];
+ } else {
+ j++;
+ t[j] = t[i];
+ c[j] = c[i];
+ }
+ n = j + 1;
+ /* Do split : */
+ curve_points_to_coefficients(x0, pc->p1.x, pc->p2.x, pc->pt.x, ax, bx, cx, v01, v12);
+ curve_points_to_coefficients(y0, pc->p1.y, pc->p2.y, pc->pt.y, ay, by, cy, v01, v12);
+ ax *= 3, bx *= 2; /* Coefficients of the derivative. */
+ ay *= 3, by *= 2;
+ px = x0;
+ py = y0;
+ qx = (fixed)((pc->p1.x - px) * t[0] + 0.5);
+ qy = (fixed)((pc->p1.y - py) * t[0] + 0.5);
+ tp = 0;
+ for (i = k; i < n; i++) {
+ double ti = t[i];
+ double t2 = ti * ti, t3 = t2 * ti;
+ double omt = 1 - ti, omt2 = omt * omt, omt3 = omt2 * omt;
+ double x = x0 * omt3 + 3 * pc->p1.x * omt2 * ti + 3 * pc->p2.x * omt * t2 + pc->pt.x * t3;
+ double y = y0 * omt3 + 3 * pc->p1.y * omt2 * ti + 3 * pc->p2.y * omt * t2 + pc->pt.y * t3;
+ double ddx = (c[i] & 1 ? 0 : ax * t2 + bx * ti + cx); /* Suppress noise. */
+ double ddy = (c[i] & 2 ? 0 : ay * t2 + by * ti + cy);
+ fixed dx = (fixed)(ddx + 0.5);
+ fixed dy = (fixed)(ddy + 0.5);
+ int code;
+
+ tt = (i + 1 < n ? t[i + 1] : 1) - ti;
+ rx = (fixed)(dx * (t[i] - tp) / 3 + 0.5);
+ ry = (fixed)(dy * (t[i] - tp) / 3 + 0.5);
+ sx = (fixed)(x + 0.5);
+ sy = (fixed)(y + 0.5);
+ /* Suppress the derivative sign noise near a peak : */
+ if ((double)(sx - px) * qx + (double)(sy - py) * qy < 0)
+ qx = -qx, qy = -qy;
+ if ((double)(sx - px) * rx + (double)(sy - py) * ry < 0)
+ rx = -rx, ry = -qy;
+ /* Do add : */
+ code = gx_path_add_curve_notes(ppath, px + qx, py + qy, sx - rx, sy - ry, sx, sy, notes);
+ if (code < 0)
+ return code;
+ notes |= sn_not_first;
+ px = sx;
+ py = sy;
+ qx = (fixed)(dx * tt / 3 + 0.5);
+ qy = (fixed)(dy * tt / 3 + 0.5);
+ tp = t[i];
+ }
+ sx = pc->pt.x;
+ sy = pc->pt.y;
+ rx = (fixed)((pc->pt.x - pc->p2.x) * tt + 0.5);
+ ry = (fixed)((pc->pt.y - pc->p2.y) * tt + 0.5);
+ /* Suppress the derivative sign noise near peaks : */
+ if ((double)(sx - px) * qx + (double)(sy - py) * qy < 0)
+ qx = -qx, qy = -qy;
+ if ((double)(sx - px) * rx + (double)(sy - py) * ry < 0)
+ rx = -rx, ry = -qy;
+ return gx_path_add_curve_notes(ppath, px + qx, py + qy, sx - rx, sy - ry, sx, sy, notes);
+}
+
+/*
+ * Split a curve if necessary into pieces that are monotonic in X or Y as a
+ * function of the curve parameter t. This allows us to rasterize curves
+ * directly without pre-flattening. This takes a fair amount of analysis....
+ * Store the values of t of the split points in pst[0] and pst[1]. Return
+ * the number of split points (0, 1, or 2).
+ */
+int
+gx_curve_monotonic_points(fixed v0, fixed v1, fixed v2, fixed v3,
+ double pst[2])
+{
+ /*
+ Let
+ v(t) = a*t^3 + b*t^2 + c*t + d, 0 <= t <= 1.
+ Then
+ dv(t) = 3*a*t^2 + 2*b*t + c.
+ v(t) has a local minimum or maximum (or inflection point)
+ precisely where dv(t) = 0. Now the roots of dv(t) = 0 (i.e.,
+ the zeros of dv(t)) are at
+ t = ( -2*b +/- sqrt(4*b^2 - 12*a*c) ) / 6*a
+ = ( -b +/- sqrt(b^2 - 3*a*c) ) / 3*a
+ (Note that real roots exist iff b^2 >= 3*a*c.)
+ We want to know if these lie in the range (0..1).
+ (The endpoints don't count.) Call such a root a "valid zero."
+ Since computing the roots is expensive, we would like to have
+ some cheap tests to filter out cases where they don't exist
+ (i.e., where the curve is already monotonic).
+ */
+ fixed v01, v12, a, b, c, b2, a3;
+ fixed dv_end, b2abs, a3abs;
+
+ curve_points_to_coefficients(v0, v1, v2, v3, a, b, c, v01, v12);
+ b2 = b << 1;
+ a3 = (a << 1) + a;
+ /*
+ If a = 0, the only possible zero is t = -c / 2*b.
+ This zero is valid iff sign(c) != sign(b) and 0 < |c| < 2*|b|.
+ */
+ if (a == 0) {
+ if ((b ^ c) < 0 && any_abs(c) < any_abs(b2) && c != 0) {
+ *pst = (double)(-c) / b2;
+ return 1;
+ } else
+ return 0;
+ }
+ /*
+ Iff a curve is horizontal at t = 0, c = 0. In this case,
+ there can be at most one other zero, at -2*b / 3*a.
+ This zero is valid iff sign(a) != sign(b) and 0 < 2*|b| < 3*|a|.
+ */
+ if (c == 0) {
+ if ((a ^ b) < 0 && any_abs(b2) < any_abs(a3) && b != 0) {
+ *pst = (double)(-b2) / a3;
+ return 1;
+ } else
+ return 0;
+ }
+ /*
+ Similarly, iff a curve is horizontal at t = 1, 3*a + 2*b + c = 0.
+ In this case, there can be at most one other zero,
+ at -1 - 2*b / 3*a, iff sign(a) != sign(b) and 1 < -2*b / 3*a < 2,
+ i.e., 3*|a| < 2*|b| < 6*|a|.
+ */
+ else if ((dv_end = a3 + b2 + c) == 0) {
+ if ((a ^ b) < 0 &&
+ (b2abs = any_abs(b2)) > (a3abs = any_abs(a3)) &&
+ b2abs < a3abs << 1
+ ) {
+ *pst = (double)(-b2 - a3) / a3;
+ return 1;
+ } else
+ return 0;
+ }
+ /*
+ If sign(dv_end) != sign(c), at least one valid zero exists,
+ since dv(0) and dv(1) have opposite signs and hence
+ dv(t) must be zero somewhere in the interval [0..1].
+ */
+ else if ((dv_end ^ c) < 0);
+ /*
+ If sign(a) = sign(b), no valid zero exists,
+ since dv is monotonic on [0..1] and has the same sign
+ at both endpoints.
+ */
+ else if ((a ^ b) >= 0)
+ return 0;
+ /*
+ Otherwise, dv(t) may be non-monotonic on [0..1]; it has valid zeros
+ iff its sign anywhere in this interval is different from its sign
+ at the endpoints, which occurs iff it has an extremum in this
+ interval and the extremum is of the opposite sign from c.
+ To find this out, we look for the local extremum of dv(t)
+ by observing
+ d2v(t) = 6*a*t + 2*b
+ which has a zero only at
+ t1 = -b / 3*a
+ Now if t1 <= 0 or t1 >= 1, no valid zero exists.
+ Note that we just determined that sign(a) != sign(b), so we know t1 > 0.
+ */
+ else if (any_abs(b) >= any_abs(a3))
+ return 0;
+ /*
+ Otherwise, we just go ahead with the computation of the roots,
+ and test them for being in the correct range. Note that a valid
+ zero is an inflection point of v(t) iff d2v(t) = 0; we don't
+ bother to check for this case, since it's rare.
+ */
+ {
+ double nbf = (double)(-b);
+ double a3f = (double)a3;
+ double radicand = nbf * nbf - a3f * c;
+
+ if (radicand < 0) {
+ if_debug1('2', "[2]negative radicand = %g\n", radicand);
+ return 0;
+ } {
+ double root = sqrt(radicand);
+ int nzeros = 0;
+ double z = (nbf - root) / a3f;
+
+ /*
+ * We need to return the zeros in the correct order.
+ * We know that root is non-negative, but a3f may be either
+ * positive or negative, so we need to check the ordering
+ * explicitly.
+ */
+ if_debug2('2', "[2]zeros at %g, %g\n", z, (nbf + root) / a3f);
+ if (z > 0 && z < 1)
+ *pst = z, nzeros = 1;
+ if (root != 0) {
+ z = (nbf + root) / a3f;
+ if (z > 0 && z < 1) {
+ if (nzeros && a3f < 0) /* order is reversed */
+ pst[1] = *pst, *pst = z;
+ else
+ pst[nzeros] = z;
+ nzeros++;
+ }
+ }
+ return nzeros;
+ }
+ }
+}
+
+/* ---------------- Path optimization for the filling algorithm. ---------------- */
+
+static bool
+find_contacting_segments(const subpath *sp0, segment *sp0last,
+ const subpath *sp1, segment *sp1last,
+ segment **sc0, segment **sc1)
+{
+ segment *s0, *s1;
+ const segment *s0s, *s1s;
+ int count0, count1, search_limit = 50;
+ int min_length = fixed_1 * 1;
+
+ /* This is a simplified algorithm, which only checks for quazi-colinear vertical lines.
+ "Quazi-vertical" means dx <= 1 && dy >= min_length . */
+ /* To avoid a big unuseful expence of the processor time,
+ we search the first subpath from the end
+ (assuming that it was recently merged near the end),
+ and restrict the search with search_limit segments
+ against a quadratic scanning of two long subpaths.
+ Thus algorithm is not necessary finds anything contacting.
+ Instead it either quickly finds something, or maybe not. */
+ for (s0 = sp0last, count0 = 0; count0 < search_limit && s0 != (segment *)sp0; s0 = s0->prev, count0++) {
+ s0s = s0->prev;
+ if ((s0->type == s_line || s0->type == s_gap) &&
+ (s0s->pt.x == s0->pt.x ||
+ (any_abs(s0s->pt.x - s0->pt.x) == 1 &&
+ any_abs(s0s->pt.y - s0->pt.y) > min_length))) {
+ for (s1 = sp1last, count1 = 0; count1 < search_limit && s1 != (segment *)sp1; s1 = s1->prev, count1++) {
+ s1s = s1->prev;
+ if ((s1->type == s_line || s1->type == s_gap) &&
+ (s1s->pt.x == s1->pt.x ||
+ (any_abs(s1s->pt.x - s1->pt.x) == 1 && any_abs(s1s->pt.y - s1->pt.y) > min_length))) {
+ if (s0s->pt.x == s1s->pt.x || s0->pt.x == s1->pt.x || s0->pt.x == s1s->pt.x || s0s->pt.x == s1->pt.x) {
+ if (s0s->pt.y < s0->pt.y && s1s->pt.y > s1->pt.y) {
+ fixed y0 = max(s0s->pt.y, s1->pt.y);
+ fixed y1 = min(s0->pt.y, s1s->pt.y);
+
+ if (y0 <= y1) {
+ *sc0 = s0;
+ *sc1 = s1;
+ return true;
+ }
+ }
+ if (s0s->pt.y > s0->pt.y && s1s->pt.y < s1->pt.y) {
+ fixed y0 = max(s0->pt.y, s1s->pt.y);
+ fixed y1 = min(s0s->pt.y, s1->pt.y);
+
+ if (y0 <= y1) {
+ *sc0 = s0;
+ *sc1 = s1;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+int
+gx_path_merge_contacting_contours(gx_path *ppath)
+{
+ /* Now this is a simplified algorithm,
+ which merge only contours by a common quazi-vertical line. */
+ /* Note the merged contour is not equivalent to sum of original contours,
+ because we ignore small coordinate differences within fixed_epsilon. */
+ int window = 5/* max spot holes */ * 6/* segments per subpath */;
+ subpath *sp0 = ppath->segments->contents.subpath_first;
+
+ for (; sp0 != NULL; sp0 = (subpath *)sp0->last->next) {
+ segment *sp0last = sp0->last;
+ subpath *sp1 = (subpath *)sp0last->next, *spnext;
+ subpath *sp1p = sp0;
+ int count;
+
+ for (count = 0; sp1 != NULL && count < window; sp1 = spnext, count++) {
+ segment *sp1last = sp1->last;
+ segment *sc0, *sc1, *old_first;
+
+ spnext = (subpath *)sp1last->next;
+ if (find_contacting_segments(sp0, sp0last, sp1, sp1last, &sc0, &sc1)) {
+ /* Detach the subpath 1 from the path: */
+ sp1->prev->next = sp1last->next;
+ if (sp1last->next != NULL)
+ sp1last->next->prev = sp1->prev;
+ sp1->prev = 0;
+ sp1last->next = 0;
+ old_first = sp1->next;
+ /* sp1 is not longer in use. Move subpath_current from it for safe removing : */
+ if (ppath->segments->contents.subpath_current == sp1) {
+ ppath->segments->contents.subpath_current = sp1p;
+ }
+ if (sp1last->type == s_line_close) {
+ /* Change 'closepath' of the subpath 1 to a line (maybe degenerate) : */
+ sp1last->type = s_line;
+ /* sp1 is not longer in use. Free it : */
+ gs_free_object(gs_memory_stable(ppath->memory), sp1, "gx_path_merge_contacting_contours");
+ } else if (sp1last->pt.x == sp1->pt.x && sp1last->pt.y == sp1->pt.y) {
+ /* Implicit closepath with zero length. Don't need a new segment. */
+ /* sp1 is not longer in use. Free it : */
+ gs_free_object(gs_memory_stable(ppath->memory), sp1, "gx_path_merge_contacting_contours");
+ } else {
+ /* Insert the closing line segment. */
+ /* sp1 is not longer in use. Convert it to the line segment : */
+ sp1->type = s_line;
+ sp1last->next = (segment *)sp1;
+ sp1->next = NULL;
+ sp1->prev = sp1last;
+ sp1->last = NULL; /* Safety for garbager. */
+ sp1last = (segment *)sp1;
+ }
+ sp1 = 0; /* Safety. */
+ /* Rotate the subpath 1 to sc1 : */
+ { /* Detach s_start and make a loop : */
+ sp1last->next = old_first;
+ old_first->prev = sp1last;
+ /* Unlink before sc1 : */
+ sp1last = sc1->prev;
+ sc1->prev->next = 0;
+ sc1->prev = 0; /* Safety. */
+ /* sp1 is not longer in use. Free it : */
+ if (ppath->segments->contents.subpath_current == sp1) {
+ ppath->segments->contents.subpath_current = sp1p;
+ }
+ gs_free_object(gs_memory_stable(ppath->memory), sp1, "gx_path_merge_contacting_contours");
+ sp1 = 0; /* Safety. */
+ }
+ /* Insert the subpath 1 into the subpath 0 before sc0 :*/
+ sc0->prev->next = sc1;
+ sc1->prev = sc0->prev;
+ sp1last->next = sc0;
+ sc0->prev = sp1last;
+ /* Remove degenearte "bridge" segments : (fixme: Not done due to low importance). */
+ /* Edit the subpath count : */
+ ppath->subpath_count--;
+ } else
+ sp1p = sp1;
+ }
+ }
+ return 0;
+}
+
+static int
+is_colinear(gs_fixed_rect *rect, fixed x, fixed y)
+{
+ fixed x0 = rect->p.x;
+ fixed y0 = rect->p.y;
+ fixed x1 = rect->q.x;
+ fixed y1 = rect->q.y;
+
+ if (x0 == x1) {
+ if (y0 == y1) {
+ /* Initial case */
+ /* Still counts as colinear */
+ } else if (x == x0) {
+ /* OK! */
+ } else {
+ return 0; /* Not colinear */
+ }
+ } else if (rect->p.y == rect->q.y) {
+ if (y == rect->p.y) {
+ /* OK */
+ } else {
+ return 0; /* Not colinear */
+ }
+ } else {
+ /* Need to do hairy maths */
+ /* The distance of a point (x,y) from the line passing through
+ * (x0,y0) and (x1,y1) is:
+ * d = |(y1-y0)x - (x1-x0)y + x1y0 - y1x0| / SQR((y1-y0)^2 + (x1-x0)^2)
+ *
+ * We want d <= epsilon to count as colinear.
+ *
+ * d = |(y1-y0)x - (x1-x0)y + x1y0 - y1x0| / SQR((y1-y0)^2 + (x1-x0)^2) <= epsilon
+ *
+ * |(y1-y0)x - (x1-x0)y + x1y0 - y1x0| <= epsilon * SQR((y1-y0)^2 + (x1-x0)^2)
+ *
+ * ((y1-y0)x - (x1-x0)y + x1y0 - y1x0)^2 <= epsilon^2 * ((y1-y0)^2 + (x1-x0)^2)
+ */
+ int64_t ix1 = ((int64_t)x1);
+ int64_t iy1 = ((int64_t)y1);
+ int64_t dx = ix1 - x0;
+ int64_t dy = iy1 - y0;
+ int64_t num = dy*x - dx*y + ix1*y0 - iy1*x0;
+ int64_t den = dx*dx + dy*dy;
+ int epsilon_squared = 2;
+
+ if (num < 0)
+ num = -num;
+ while (num > (1<<30)) {
+ num >>= 2;
+ den >>= 1;
+ if (den == 0)
+ return 0; /* Not colinear */
+ }
+ num *= num;
+ if (num > epsilon_squared * den)
+ return 0;
+ }
+ /* rect is not really a rect. It's just a pair of points. We guarantee that x0 <= x1. */
+ if (x == x0) {
+ if (y < y0)
+ rect->p.y = y;
+ else if (y > y1)
+ rect->q.y = y;
+ } else if (x < x0) {
+ rect->p.x = x;
+ rect->p.y = y;
+ } else {
+ rect->q.x = x;
+ rect->q.y = y;
+ }
+
+ return 1;
+}
+
+static int
+gx_path_copy_eliding_1d(const gx_path *ppath_old, gx_path *ppath)
+{
+ const segment *pseg;
+ /*
+ * Since we're going to be adding to the path, unshare it
+ * before we start.
+ */
+ int code = gx_path_unshare(ppath);
+
+ if (code < 0)
+ return code;
+#ifdef DEBUG
+ if (gs_debug_c('P'))
+ gx_dump_path(ppath_old, "before eliding_1d");
+#endif
+
+ pseg = (const segment *)(ppath_old->first_subpath);
+ while (pseg != NULL) {
+ const segment *look = pseg;
+ gs_fixed_rect rect;
+
+ rect.p.x = rect.q.x = look->pt.x;
+ rect.p.y = rect.q.y = look->pt.y;
+
+ if (look->type != s_start) {
+ dlprintf("Unlikely?");
+ }
+
+ look = look->next;
+ while (look != NULL && look->type != s_start) {
+ if (look->type == s_curve) {
+ const curve_segment *pc = (const curve_segment *)look;
+ if (!is_colinear(&rect, pc->p1.x, pc->p1.y) ||
+ !is_colinear(&rect, pc->p2.x, pc->p2.y) ||
+ !is_colinear(&rect, pc->pt.x, pc->pt.y))
+ goto not_colinear;
+ } else if (!is_colinear(&rect, look->pt.x, look->pt.y)) {
+ goto not_colinear;
+ }
+ look = look->next;
+ }
+ pseg = look;
+ if (0)
+ {
+not_colinear:
+ /* Not colinear. We want to keep this section. */
+ while (look != NULL && look->type != s_start)
+ look = look->next;
+ while (pseg != look && code >= 0) {
+ /* Copy */
+ switch (pseg->type) {
+ case s_start:
+ code = gx_path_add_point(ppath,
+ pseg->pt.x, pseg->pt.y);
+ break;
+ case s_curve:
+ {
+ const curve_segment *pc = (const curve_segment *)pseg;
+
+ code = gx_path_add_curve_notes(ppath,
+ pc->p1.x, pc->p1.y, pc->p2.x, pc->p2.y,
+ pc->pt.x, pc->pt.y, pseg->notes);
+ break;
+ }
+ case s_line:
+ code = gx_path_add_line_notes(ppath,
+ pseg->pt.x, pseg->pt.y, pseg->notes);
+ break;
+ case s_gap:
+ code = gx_path_add_gap_notes(ppath,
+ pseg->pt.x, pseg->pt.y, pseg->notes);
+ break;
+ case s_dash:
+ {
+ const dash_segment *pd = (const dash_segment *)pseg;
+
+ code = gx_path_add_dash_notes(ppath,
+ pd->pt.x, pd->pt.y, pd->tangent.x, pd->tangent.y, pseg->notes);
+ break;
+ }
+ case s_line_close:
+ code = gx_path_close_subpath(ppath);
+ break;
+ default: /* can't happen */
+ code = gs_note_error(gs_error_unregistered);
+ }
+ pseg = pseg->next;
+ }
+ if (code < 0) {
+ gx_path_new(ppath);
+ return code;
+ }
+ }
+ }
+ ppath->bbox_set = false;
+#ifdef DEBUG
+ if (gs_debug_c('P'))
+ gx_dump_path(ppath, "after eliding_1d");
+#endif
+ return 0;
+}
+
+int
+gx_path_elide_1d(gx_path *ppath)
+{
+ int code;
+ gx_path path;
+
+ gx_path_init_local(&path, ppath->memory);
+ code = gx_path_copy_eliding_1d(ppath, &path);
+ if (code < 0)
+ return code;
+ gx_path_assign_free(ppath, &path);
+ gx_path_free(&path, "gx_path_elide_1d");
+
+ return 0;
+}