Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/draw-path.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/draw-path.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1619 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF 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 Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "draw-imp.h" + +#include <math.h> +#include <float.h> +#include <assert.h> + +#define MAX_DEPTH 8 + +/* + When stroking/filling, we now label the edges as we emit them. + + For filling, we walk the outline of the shape in order, so everything + is labelled as '0'. + + For stroking, we walk up both sides of the stroke at once; the forward + side (0), and the reverse side (1). When we get to the top, either + both sides join back to where they started, or we cap them. + + The start cap is labelled 2, the end cap is labelled 0. + + These labels are ignored for edge based rasterization, but are required + for edgebuffer based rasterization. + + Consider the following simplified ascii art diagram of a stroke from + left to right with 3 sections. + + | 0 0 0 + | +----->-----+----->-----+----->-----+ + | | | + | ^ 2 A B C v 0 + | | | + | +-----<-----+-----<-----+-----<-----+ + | 1 1 1 + + Edge 0 is sent in order (the top edge of A then B then C, left to right + in the above diagram). Edge 1 is sent in reverse order (the bottom edge + of A then B then C, still left to right in the above diagram, even though + the sense of the line is right to left). + + Finally any caps required are sent, 0 and 2. + + It would be nicer if we could roll edge 2 into edge 1, but to do that + we'd need to know in advance if a stroke was closed or not, so we have + special case code in the edgebuffer based rasterizer to cope with this. +*/ + +static void +line(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float x0, float y0, float x1, float y1) +{ + float tx0 = ctm.a * x0 + ctm.c * y0 + ctm.e; + float ty0 = ctm.b * x0 + ctm.d * y0 + ctm.f; + float tx1 = ctm.a * x1 + ctm.c * y1 + ctm.e; + float ty1 = ctm.b * x1 + ctm.d * y1 + ctm.f; + fz_insert_rasterizer(ctx, rast, tx0, ty0, tx1, ty1, 0); +} + +static void +bezier(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float flatness, + float xa, float ya, + float xb, float yb, + float xc, float yc, + float xd, float yd, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xcd, ycd; + float xabc, yabc; + float xbcd, ybcd; + float xabcd, yabcd; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xd - xc)); + dmax = fz_max(dmax, fz_abs(yd - yc)); + if (dmax < flatness || depth >= MAX_DEPTH) + { + line(ctx, rast, ctm, xa, ya, xd, yd); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + xcd = xc + xd; + ycd = yc + yd; + + xabc = xab + xbc; + yabc = yab + ybc; + xbcd = xbc + xcd; + ybcd = ybc + ycd; + + xabcd = xabc + xbcd; + yabcd = yabc + ybcd; + + xab *= 0.5f; yab *= 0.5f; + /* xbc *= 0.5f; ybc *= 0.5f; */ + xcd *= 0.5f; ycd *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + xbcd *= 0.25f; ybcd *= 0.25f; + + xabcd *= 0.125f; yabcd *= 0.125f; + + bezier(ctx, rast, ctm, flatness, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1); + bezier(ctx, rast, ctm, flatness, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1); +} + +static void +quad(fz_context *ctx, fz_rasterizer *rast, fz_matrix ctm, float flatness, + float xa, float ya, + float xb, float yb, + float xc, float yc, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xabc, yabc; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xc - xb)); + dmax = fz_max(dmax, fz_abs(yc - yb)); + if (dmax < flatness || depth >= MAX_DEPTH) + { + line(ctx, rast, ctm, xa, ya, xc, yc); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + + xabc = xab + xbc; + yabc = yab + ybc; + + xab *= 0.5f; yab *= 0.5f; + xbc *= 0.5f; ybc *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + + quad(ctx, rast, ctm, flatness, xa, ya, xab, yab, xabc, yabc, depth + 1); + quad(ctx, rast, ctm, flatness, xabc, yabc, xbc, ybc, xc, yc, depth + 1); +} + +typedef struct +{ + fz_rasterizer *rast; + fz_matrix ctm; + float flatness; + fz_point b; + fz_point c; +} +flatten_arg; + +static void +flatten_moveto(fz_context *ctx, void *arg_, float x, float y) +{ + flatten_arg *arg = (flatten_arg *)arg_; + + /* implicit closepath before moveto */ + if (arg->c.x != arg->b.x || arg->c.y != arg->b.y) + line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, arg->b.x, arg->b.y); + arg->c.x = arg->b.x = x; + arg->c.y = arg->b.y = y; + + fz_gap_rasterizer(ctx, arg->rast); +} + +static void +flatten_lineto(fz_context *ctx, void *arg_, float x, float y) +{ + flatten_arg *arg = (flatten_arg *)arg_; + + line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, x, y); + arg->c.x = x; + arg->c.y = y; +} + +static void +flatten_curveto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + flatten_arg *arg = (flatten_arg *)arg_; + + bezier(ctx, arg->rast, arg->ctm, arg->flatness, arg->c.x, arg->c.y, x1, y1, x2, y2, x3, y3, 0); + arg->c.x = x3; + arg->c.y = y3; +} + +static void +flatten_quadto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2) +{ + flatten_arg *arg = (flatten_arg *)arg_; + + quad(ctx, arg->rast, arg->ctm, arg->flatness, arg->c.x, arg->c.y, x1, y1, x2, y2, 0); + arg->c.x = x2; + arg->c.y = y2; +} + +static void +flatten_close(fz_context *ctx, void *arg_) +{ + flatten_arg *arg = (flatten_arg *)arg_; + + line(ctx, arg->rast, arg->ctm, arg->c.x, arg->c.y, arg->b.x, arg->b.y); + arg->c.x = arg->b.x; + arg->c.y = arg->b.y; +} + +static void +flatten_rectto(fz_context *ctx, void *arg_, float x0, float y0, float x1, float y1) +{ + flatten_arg *arg = (flatten_arg *)arg_; + fz_matrix ctm = arg->ctm; + + flatten_moveto(ctx, arg_, x0, y0); + + if (fz_antidropout_rasterizer(ctx, arg->rast)) + { + /* In the case where we have an axis aligned rectangle, do some + * horrid antidropout stuff. */ + if (ctm.b == 0 && ctm.c == 0) + { + float tx0 = ctm.a * x0 + ctm.e; + float ty0 = ctm.d * y0 + ctm.f; + float tx1 = ctm.a * x1 + ctm.e; + float ty1 = ctm.d * y1 + ctm.f; + fz_insert_rasterizer_rect(ctx, arg->rast, tx0, ty0, tx1, ty1); + return; + } + else if (ctm.a == 0 && ctm.d == 0) + { + float tx0 = ctm.c * y0 + ctm.e; + float ty0 = ctm.b * x0 + ctm.f; + float tx1 = ctm.c * y1 + ctm.e; + float ty1 = ctm.b * x1 + ctm.f; + fz_insert_rasterizer_rect(ctx, arg->rast, tx0, ty1, tx1, ty0); + return; + } + } + + flatten_lineto(ctx, arg_, x1, y0); + flatten_lineto(ctx, arg_, x1, y1); + flatten_lineto(ctx, arg_, x0, y1); + flatten_close(ctx, arg_); +} + +static const fz_path_walker flatten_proc = +{ + flatten_moveto, + flatten_lineto, + flatten_curveto, + flatten_close, + flatten_quadto, + NULL, + NULL, + flatten_rectto +}; + +static int +do_flatten_fill(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, fz_matrix ctm, float flatness) +{ + flatten_arg arg; + + arg.rast = rast; + arg.ctm = ctm; + arg.flatness = flatness; + arg.b.x = arg.b.y = arg.c.x = arg.c.y = 0; + + fz_walk_path(ctx, path, &flatten_proc, &arg); + if (arg.c.x != arg.b.x || arg.c.y != arg.b.y) + line(ctx, rast, ctm, arg.c.x, arg.c.y, arg.b.x, arg.b.y); + + fz_gap_rasterizer(ctx, rast); + + return fz_is_empty_irect(fz_bound_rasterizer(ctx, rast)); +} + +int +fz_flatten_fill_path(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, fz_matrix ctm, float flatness, fz_irect scissor, fz_irect *bbox) +{ + int empty; + fz_irect local_bbox; + if (!bbox) + bbox = &local_bbox; + + /* If we're given an empty scissor, sanitize it. This makes life easier + * down the line. */ + if (fz_is_empty_irect(scissor)) + scissor.x1 = scissor.x0, scissor.y1 = scissor.y0; + + if (fz_reset_rasterizer(ctx, rast, scissor)) + { + empty = do_flatten_fill(ctx, rast, path, ctm, flatness); + if (empty) + return *bbox = fz_empty_irect, 1; + fz_postindex_rasterizer(ctx, rast); + } + + empty = do_flatten_fill(ctx, rast, path, ctm, flatness); + if (empty) + return *bbox = fz_empty_irect, 1; + + *bbox = fz_intersect_irect(scissor, fz_bound_rasterizer(ctx, rast)); + return fz_is_empty_irect(*bbox); +} + +typedef struct sctx +{ + fz_rasterizer *rast; + fz_matrix ctm; + float flatness; + const fz_stroke_state *stroke; + + int linejoin; + float linewidth; + float miterlimit; + fz_point beg[2]; + fz_point seg[2]; + int sn; + int not_just_moves; + int from_bezier; + fz_point cur; + + fz_rect rect; + const float *dash_list; + float dash_phase; + int dash_len; + float dash_total; + int toggle, cap; + int offset; + float phase; + fz_point dash_cur; + fz_point dash_beg; + + float dirn_x; + float dirn_y; +} sctx; + +static void +fz_add_line(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1, int rev) +{ + float tx0 = s->ctm.a * x0 + s->ctm.c * y0 + s->ctm.e; + float ty0 = s->ctm.b * x0 + s->ctm.d * y0 + s->ctm.f; + float tx1 = s->ctm.a * x1 + s->ctm.c * y1 + s->ctm.e; + float ty1 = s->ctm.b * x1 + s->ctm.d * y1 + s->ctm.f; + + fz_insert_rasterizer(ctx, s->rast, tx0, ty0, tx1, ty1, rev); +} + +static void +fz_add_horiz_rect(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1) +{ + if (fz_antidropout_rasterizer(ctx, s->rast)) { + if (s->ctm.b == 0 && s->ctm.c == 0) + { + float tx0 = s->ctm.a * x0 + s->ctm.e; + float ty0 = s->ctm.d * y0 + s->ctm.f; + float tx1 = s->ctm.a * x1 + s->ctm.e; + float ty1 = s->ctm.d * y1 + s->ctm.f; + fz_insert_rasterizer_rect(ctx, s->rast, tx1, ty1, tx0, ty0); + return; + } + else if (s->ctm.a == 0 && s->ctm.d == 0) + { + float tx0 = s->ctm.c * y0 + s->ctm.e; + float ty0 = s->ctm.b * x0 + s->ctm.f; + float tx1 = s->ctm.c * y1 + s->ctm.e; + float ty1 = s->ctm.b * x1 + s->ctm.f; + fz_insert_rasterizer_rect(ctx, s->rast, tx1, ty0, tx0, ty1); + return; + } + } + + fz_add_line(ctx, s, x0, y0, x1, y0, 0); + fz_add_line(ctx, s, x1, y1, x0, y1, 1); +} + +static void +fz_add_vert_rect(fz_context *ctx, sctx *s, float x0, float y0, float x1, float y1) +{ + if (fz_antidropout_rasterizer(ctx, s->rast)) + { + if (s->ctm.b == 0 && s->ctm.c == 0) + { + float tx0 = s->ctm.a * x0 + s->ctm.e; + float ty0 = s->ctm.d * y0 + s->ctm.f; + float tx1 = s->ctm.a * x1 + s->ctm.e; + float ty1 = s->ctm.d * y1 + s->ctm.f; + fz_insert_rasterizer_rect(ctx, s->rast, tx0, ty1, tx1, ty0); + return; + } + else if (s->ctm.a == 0 && s->ctm.d == 0) + { + float tx0 = s->ctm.c * y0 + s->ctm.e; + float ty0 = s->ctm.b * x0 + s->ctm.f; + float tx1 = s->ctm.c * y1 + s->ctm.e; + float ty1 = s->ctm.b * x1 + s->ctm.f; + fz_insert_rasterizer_rect(ctx, s->rast, tx0, ty0, tx1, ty1); + return; + } + } + + fz_add_line(ctx, s, x1, y0, x0, y0, 0); + fz_add_line(ctx, s, x0, y1, x1, y1, 1); +} + +static void +fz_add_arc(fz_context *ctx, sctx *s, + float xc, float yc, + float x0, float y0, + float x1, float y1, + int rev) +{ + float th0, th1, r; + float theta; + float ox, oy, nx, ny; + int n, i; + + r = fabsf(s->linewidth); + theta = 2 * FZ_SQRT2 * sqrtf(s->flatness / r); + th0 = atan2f(y0, x0); + th1 = atan2f(y1, x1); + + if (r > 0) + { + if (th0 < th1) + th0 += FZ_PI * 2; + n = ceilf((th0 - th1) / theta); + } + else + { + if (th1 < th0) + th1 += FZ_PI * 2; + n = ceilf((th1 - th0) / theta); + } + + if (rev) + { + ox = x1; + oy = y1; + for (i = n-1; i > 0; i--) + { + theta = th0 + (th1 - th0) * i / n; + nx = cosf(theta) * r; + ny = sinf(theta) * r; + fz_add_line(ctx, s, xc + nx, yc + ny, xc + ox, yc + oy, rev); + ox = nx; + oy = ny; + } + + fz_add_line(ctx, s, xc + x0, yc + y0, xc + ox, yc + oy, rev); + } + else + { + ox = x0; + oy = y0; + for (i = 1; i < n; i++) + { + theta = th0 + (th1 - th0) * i / n; + nx = cosf(theta) * r; + ny = sinf(theta) * r; + fz_add_line(ctx, s, xc + ox, yc + oy, xc + nx, yc + ny, rev); + ox = nx; + oy = ny; + } + + fz_add_line(ctx, s, xc + ox, yc + oy, xc + x1, yc + y1, rev); + } +} + +/* FLT_TINY * FLT_TINY is approximately FLT_EPSILON */ +#define FLT_TINY 3.4e-4F +static int find_normal_vectors(float dx, float dy, float linewidth, float *dlx, float *dly) +{ + if (dx == 0) + { + if (dy < FLT_TINY && dy > - FLT_TINY) + goto tiny; + else if (dy > 0) + *dlx = linewidth; + else + *dlx = -linewidth; + *dly = 0; + } + else if (dy == 0) + { + if (dx < FLT_TINY && dx > - FLT_TINY) + goto tiny; + else if (dx > 0) + *dly = -linewidth; + else + *dly = linewidth; + *dlx = 0; + } + else + { + float sq = dx * dx + dy * dy; + float scale; + + if (sq < FLT_EPSILON) + goto tiny; + scale = linewidth / sqrtf(sq); + *dlx = dy * scale; + *dly = -dx * scale; + } + return 0; +tiny: + *dlx = 0; + *dly = 0; + return 1; +} + +static void +fz_add_line_join(fz_context *ctx, sctx *s, float ax, float ay, float bx, float by, float cx, float cy, int join_under) +{ + float miterlimit = s->miterlimit; + float linewidth = s->linewidth; + fz_linejoin linejoin = s->linejoin; + float dx0, dy0; + float dx1, dy1; + float dlx0, dly0; + float dlx1, dly1; + float dmx, dmy; + float dmr2; + float scale; + float cross; + int rev = 0; + + dx0 = bx - ax; + dy0 = by - ay; + + dx1 = cx - bx; + dy1 = cy - by; + + cross = dx1 * dy0 - dx0 * dy1; + /* Ensure that cross >= 0 */ + if (cross < 0) + { + float tmp; + tmp = dx1; dx1 = -dx0; dx0 = -tmp; + tmp = dy1; dy1 = -dy0; dy0 = -tmp; + cross = -cross; + rev = !rev; + } + + if (find_normal_vectors(dx0, dy0, linewidth, &dlx0, &dly0)) + linejoin = FZ_LINEJOIN_BEVEL; + + if (find_normal_vectors(dx1, dy1, linewidth, &dlx1, &dly1)) + linejoin = FZ_LINEJOIN_BEVEL; + + dmx = (dlx0 + dlx1) * 0.5f; + dmy = (dly0 + dly1) * 0.5f; + dmr2 = dmx * dmx + dmy * dmy; + + if (cross * cross < FLT_EPSILON && dx0 * dx1 + dy0 * dy1 >= 0) + linejoin = FZ_LINEJOIN_BEVEL; + + /* XPS miter joins are clipped at miterlength, rather than simply + * being converted to bevelled joins. */ + if (linejoin == FZ_LINEJOIN_MITER_XPS) + { + if (cross == 0) + linejoin = FZ_LINEJOIN_BEVEL; + else if (dmr2 * miterlimit * miterlimit >= linewidth * linewidth) + linejoin = FZ_LINEJOIN_MITER; + } + else if (linejoin == FZ_LINEJOIN_MITER) + if (dmr2 * miterlimit * miterlimit < linewidth * linewidth) + linejoin = FZ_LINEJOIN_BEVEL; + + if (join_under) + { + fz_add_line(ctx, s, bx + dlx1, by + dly1, bx + dlx0, by + dly0, !rev); + } + else if (rev) + { + fz_add_line(ctx, s, bx + dlx1, by + dly1, bx, by, 0); + fz_add_line(ctx, s, bx, by, bx + dlx0, by + dly0, 0); + } + else + { + fz_add_line(ctx, s, bx, by, bx + dlx0, by + dly0, 1); + fz_add_line(ctx, s, bx + dlx1, by + dly1, bx, by, 1); + } + + switch (linejoin) + { + case FZ_LINEJOIN_MITER_XPS: + { + float k, t0x, t0y, t1x, t1y; + + scale = linewidth * linewidth / dmr2; + dmx *= scale; + dmy *= scale; + k = (scale - linewidth * miterlimit / sqrtf(dmr2)) / (scale - 1); + t0x = bx - dmx + k * (dmx - dlx0); + t0y = by - dmy + k * (dmy - dly0); + t1x = bx - dmx + k * (dmx - dlx1); + t1y = by - dmy + k * (dmy - dly1); + + if (rev) + { + fz_add_line(ctx, s, t1x, t1y, bx - dlx1, by - dly1, 1); + fz_add_line(ctx, s, t0x, t0y, t1x, t1y, 1); + fz_add_line(ctx, s, bx - dlx0, by - dly0, t0x, t0y, 1); + } + else + { + fz_add_line(ctx, s, bx - dlx0, by - dly0, t0x, t0y, 0); + fz_add_line(ctx, s, t0x, t0y, t1x, t1y, 0); + fz_add_line(ctx, s, t1x, t1y, bx - dlx1, by - dly1, 0); + } + break; + } + case FZ_LINEJOIN_MITER: + scale = linewidth * linewidth / dmr2; + dmx *= scale; + dmy *= scale; + + if (rev) + { + fz_add_line(ctx, s, bx - dmx, by - dmy, bx - dlx1, by - dly1, 1); + fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dmx, by - dmy, 1); + } + else + { + fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dmx, by - dmy, 0); + fz_add_line(ctx, s, bx - dmx, by - dmy, bx - dlx1, by - dly1, 0); + } + break; + + case FZ_LINEJOIN_BEVEL: + fz_add_line(ctx, s, bx - dlx0, by - dly0, bx - dlx1, by - dly1, rev); + break; + + case FZ_LINEJOIN_ROUND: + fz_add_arc(ctx, s, bx, by, -dlx0, -dly0, -dlx1, -dly1, rev); + break; + + default: + assert("Invalid line join" == NULL); + } +} + +static void +do_linecap(fz_context *ctx, sctx *s, float bx, float by, fz_linecap linecap, int rev, float dlx, float dly) +{ + float flatness = s->flatness; + float linewidth = s->linewidth; + + switch (linecap) + { + case FZ_LINECAP_BUTT: + fz_add_line(ctx, s, bx - dlx, by - dly, bx + dlx, by + dly, rev); + break; + + case FZ_LINECAP_ROUND: + { + int i; + int n = ceilf(FZ_PI / (2.0f * FZ_SQRT2 * sqrtf(flatness / linewidth))); + float ox = bx - dlx; + float oy = by - dly; + for (i = 1; i < n; i++) + { + float theta = FZ_PI * i / n; + float cth = cosf(theta); + float sth = sinf(theta); + float nx = bx - dlx * cth - dly * sth; + float ny = by - dly * cth + dlx * sth; + fz_add_line(ctx, s, ox, oy, nx, ny, rev); + ox = nx; + oy = ny; + } + fz_add_line(ctx, s, ox, oy, bx + dlx, by + dly, rev); + break; + } + + case FZ_LINECAP_SQUARE: + fz_add_line(ctx, s, bx - dlx, by - dly, + bx - dlx - dly, by - dly + dlx, rev); + fz_add_line(ctx, s, bx - dlx - dly, by - dly + dlx, + bx + dlx - dly, by + dly + dlx, rev); + fz_add_line(ctx, s, bx + dlx - dly, by + dly + dlx, + bx + dlx, by + dly, rev); + break; + + case FZ_LINECAP_TRIANGLE: + { + float mx = -dly; + float my = dlx; + fz_add_line(ctx, s, bx - dlx, by - dly, bx + mx, by + my, rev); + fz_add_line(ctx, s, bx + mx, by + my, bx + dlx, by + dly, rev); + break; + } + + default: + assert("Invalid line cap" == NULL); + } +} + +static void +fz_add_line_cap(fz_context *ctx, sctx *s, float ax, float ay, float bx, float by, fz_linecap linecap, int rev) +{ + float linewidth = s->linewidth; + float dx = bx - ax; + float dy = by - ay; + + float scale = linewidth / sqrtf(dx * dx + dy * dy); + float dlx = dy * scale; + float dly = -dx * scale; + + do_linecap(ctx, s, bx, by, linecap, rev, dlx, dly); +} + +static void +fz_add_zero_len_cap(fz_context *ctx, sctx *s, float ax, float ay, fz_linecap linecap, int rev) +{ + float linewidth = s->linewidth; + float dx = rev ? -s->dirn_x : s->dirn_x; + float dy = rev ? -s->dirn_y : s->dirn_y; + float scale, dlx, dly; + + if (dx == 0 && dy == 0) + return; + + scale = linewidth / sqrtf(dx * dx + dy * dy); + dlx = dy * scale; + dly = -dx * scale; + + do_linecap(ctx, s, ax, ay, linecap, rev, dlx, dly); +} + +static void +fz_add_line_dot(fz_context *ctx, sctx *s, float ax, float ay) +{ + float flatness = s->flatness; + float linewidth = s->linewidth; + int n = ceilf(FZ_PI / (FZ_SQRT2 * sqrtf(flatness / linewidth))); + float ox = ax - linewidth; + float oy = ay; + int i; + + if (n < 3) + n = 3; + for (i = 1; i < n; i++) + { + float theta = FZ_PI * 2 * i / n; + float cth = cosf(theta); + float sth = sinf(theta); + float nx = ax - cth * linewidth; + float ny = ay + sth * linewidth; + fz_add_line(ctx, s, ox, oy, nx, ny, 0); + ox = nx; + oy = ny; + } + + fz_add_line(ctx, s, ox, oy, ax - linewidth, ay, 0); +} + +static void +fz_stroke_flush(fz_context *ctx, sctx *s, fz_linecap start_cap, fz_linecap end_cap) +{ + if (s->sn == 1) + { + fz_add_line_cap(ctx, s, s->beg[1].x, s->beg[1].y, s->beg[0].x, s->beg[0].y, start_cap, 2); + fz_add_line_cap(ctx, s, s->seg[0].x, s->seg[0].y, s->seg[1].x, s->seg[1].y, end_cap, 0); + } + else if (s->not_just_moves) + { + if (s->cap == FZ_LINECAP_ROUND) + { + fz_add_line_dot(ctx, s, s->beg[0].x, s->beg[0].y); + } + else + { + fz_add_zero_len_cap(ctx, s, s->beg[0].x, s->beg[0].y, s->cap, 2); + fz_add_zero_len_cap(ctx, s, s->beg[0].x, s->beg[0].y, s->cap, 0); + } + } + + fz_gap_rasterizer(ctx, s->rast); +} + +static void +fz_stroke_moveto(fz_context *ctx, void *s_, float x, float y) +{ + struct sctx *s = (struct sctx *)s_; + + s->seg[0].x = s->beg[0].x = x; + s->seg[0].y = s->beg[0].y = y; + s->sn = 0; + s->not_just_moves = 0; + s->from_bezier = 0; + s->dirn_x = 0; + s->dirn_y = 0; +} + +static void +fz_stroke_lineto_aux(fz_context *ctx, sctx *s, float x, float y, int from_bezier, float dirn_x, float dirn_y) +{ + float ox = s->seg[s->sn].x; + float oy = s->seg[s->sn].y; + float dx = x - ox; + float dy = y - oy; + float dlx, dly; + + s->not_just_moves = 1; + + /* We store the direction (as used for the alignment of caps etc) based on the + * direction we are passed in. */ + s->dirn_x = dirn_x; + s->dirn_y = dirn_y; + + /* We calculate the normal vectors from the delta that we have just moved. */ + if (find_normal_vectors(dx, dy, s->linewidth, &dlx, &dly)) + { + return; + } + + if (s->sn == 1) + fz_add_line_join(ctx, s, s->seg[0].x, s->seg[0].y, ox, oy, x, y, s->from_bezier & from_bezier); + +#if 1 + if (0 && dx == 0) + { + fz_add_vert_rect(ctx, s, ox - dlx, oy, x + dlx, y); + } + else if (dy == 0) + { + fz_add_horiz_rect(ctx, s, ox, oy - dly, x, y + dly); + } + else +#endif + { + + fz_add_line(ctx, s, ox - dlx, oy - dly, x - dlx, y - dly, 0); + fz_add_line(ctx, s, x + dlx, y + dly, ox + dlx, oy + dly, 1); + } + + if (s->sn) + { + s->seg[0] = s->seg[1]; + s->seg[1].x = x; + s->seg[1].y = y; + } + else + { + s->seg[1].x = s->beg[1].x = x; + s->seg[1].y = s->beg[1].y = y; + s->sn = 1; + } + s->from_bezier = from_bezier; +} + +static void +fz_stroke_lineto(fz_context *ctx, sctx *s, float x, float y, int from_bezier) +{ + float ox = s->seg[s->sn].x; + float oy = s->seg[s->sn].y; + float dx = x - ox; + float dy = y - oy; + fz_stroke_lineto_aux(ctx, s, x, y, from_bezier, dx, dy); +} + +static void +fz_stroke_closepath(fz_context *ctx, sctx *s) +{ + if (s->sn == 1) + { + fz_stroke_lineto(ctx, s, s->beg[0].x, s->beg[0].y, 0); + /* fz_stroke_lineto will *normally* end up with s->seg[1] being the x,y coords passed in. + * As such, the following line should draw a linejoin between the closing segment of this + * subpath (seg[0]->seg[1]) == (seg[0]->beg[0]) and the first segment of this subpath + * (beg[0]->beg[1]). + * In cases where the line was already at an x,y infinitesimally close to s->beg[0], + * fz_stroke_lineto may exit without doing any processing. This leaves seg[0]->seg[1] + * pointing at the penultimate line segment. Thus this draws a linejoin between that + * penultimate segment and the end segment. This is what we want. */ + fz_add_line_join(ctx, s, s->seg[0].x, s->seg[0].y, s->beg[0].x, s->beg[0].y, s->beg[1].x, s->beg[1].y, 0); + } + else if (s->not_just_moves && s->cap == FZ_LINECAP_ROUND) + fz_add_line_dot(ctx, s, s->beg[0].x, s->beg[0].y); + + s->seg[0] = s->beg[0]; + s->sn = 0; + s->not_just_moves = 0; + s->from_bezier = 0; + s->dirn_x = 0; + s->dirn_y = 0; + + fz_gap_rasterizer(ctx, s->rast); +} + +static void +fz_stroke_bezier(fz_context *ctx, struct sctx *s, + float xa, float ya, + float xb, float yb, + float xc, float yc, + float xd, float yd, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xcd, ycd; + float xabc, yabc; + float xbcd, ybcd; + float xabcd, yabcd; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xd - xc)); + dmax = fz_max(dmax, fz_abs(yd - yc)); + if (dmax < s->flatness || depth >= MAX_DEPTH) + { + fz_stroke_lineto(ctx, s, xd, yd, 1); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + xcd = xc + xd; + ycd = yc + yd; + + xabc = xab + xbc; + yabc = yab + ybc; + xbcd = xbc + xcd; + ybcd = ybc + ycd; + + xabcd = xabc + xbcd; + yabcd = yabc + ybcd; + + xab *= 0.5f; yab *= 0.5f; + /* xbc *= 0.5f; ybc *= 0.5f; */ + xcd *= 0.5f; ycd *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + xbcd *= 0.25f; ybcd *= 0.25f; + + xabcd *= 0.125f; yabcd *= 0.125f; + + fz_stroke_bezier(ctx, s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1); + fz_stroke_bezier(ctx, s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1); +} + +static void +fz_stroke_quad(fz_context *ctx, struct sctx *s, + float xa, float ya, + float xb, float yb, + float xc, float yc, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xabc, yabc; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xc - xb)); + dmax = fz_max(dmax, fz_abs(yc - yb)); + if (dmax < s->flatness || depth >= MAX_DEPTH) + { + fz_stroke_lineto(ctx, s, xc, yc, 1); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + + xabc = xab + xbc; + yabc = yab + ybc; + + xab *= 0.5f; yab *= 0.5f; + xbc *= 0.5f; ybc *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + + fz_stroke_quad(ctx, s, xa, ya, xab, yab, xabc, yabc, depth + 1); + fz_stroke_quad(ctx, s, xabc, yabc, xbc, ybc, xc, yc, depth + 1); +} + +static void +stroke_moveto(fz_context *ctx, void *s_, float x, float y) +{ + sctx *s = (sctx *)s_; + + fz_stroke_flush(ctx, s, s->stroke->start_cap, s->stroke->end_cap); + fz_stroke_moveto(ctx, s, x, y); + s->cur.x = x; + s->cur.y = y; +} + +static void +stroke_lineto(fz_context *ctx, void *s_, float x, float y) +{ + sctx *s = (sctx *)s_; + + fz_stroke_lineto(ctx, s, x, y, 0); + s->cur.x = x; + s->cur.y = y; +} + +static void +stroke_curveto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + sctx *s = (sctx *)s_; + + fz_stroke_bezier(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, x3, y3, 0); + s->cur.x = x3; + s->cur.y = y3; +} + +static void +stroke_quadto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2) +{ + sctx *s = (sctx *)s_; + + fz_stroke_quad(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, 0); + s->cur.x = x2; + s->cur.y = y2; +} + +static void +stroke_close(fz_context *ctx, void *s_) +{ + sctx *s = (sctx *)s_; + + fz_stroke_closepath(ctx, s); +} + +static const fz_path_walker stroke_proc = +{ + stroke_moveto, + stroke_lineto, + stroke_curveto, + stroke_close, + stroke_quadto +}; + +static void +fz_dash_moveto(fz_context *ctx, struct sctx *s, float x, float y) +{ + s->toggle = 1; + s->offset = 0; + s->phase = s->dash_phase; + + while (s->phase > 0 && s->phase >= s->dash_list[s->offset]) + { + s->toggle = !s->toggle; + s->phase -= s->dash_list[s->offset]; + s->offset ++; + if (s->offset == s->dash_len) + s->offset = 0; + } + + s->dash_cur.x = x; + s->dash_cur.y = y; + + if (s->toggle) + { + fz_stroke_flush(ctx, s, s->cap, s->stroke->end_cap); + s->cap = s->stroke->start_cap; + fz_stroke_moveto(ctx, s, x, y); + } +} + +/* + Performs: a += (b-a) * i/n + allowing for FP inaccuracies that can cause a to "overrun" b. +*/ +static float advance(float a, float b, float i, float n) +{ + float d = b - a; + float target = a + d * i/n; + + if (d < 0 && target < b) + target = b; + else if (d > 0 && target > b) + target = b; + + return target; +} + +static void +fz_dash_lineto(fz_context *ctx, struct sctx *s, float bx, float by, int from_bezier) +{ + float dx, dy, d; + float total, used, ratio, tail; + float ax, ay; + float mx, my; + float old_bx = 0, old_by = 0; + int n; + int dash_cap = s->stroke->dash_cap; + + ax = s->dash_cur.x; + ay = s->dash_cur.y; + dx = bx - ax; + dy = by - ay; + used = 0; + tail = 0; + total = sqrtf(dx * dx + dy * dy); + + /* If a is off screen, bring it onto the screen. First + * horizontally... */ + if ((d = s->rect.x0 - ax) > 0) + { + if (bx < s->rect.x0) + { + /* Entirely off screen */ + tail = total; + old_bx = bx; + old_by = by; + goto adjust_for_tail; + } + ax = s->rect.x0; /* d > 0, dx > 0 */ + goto a_moved_horizontally; + } + else if (d < 0 && (d = (s->rect.x1 - ax)) < 0) + { + if (bx > s->rect.x1) + { + /* Entirely off screen */ + tail = total; + old_bx = bx; + old_by = by; + goto adjust_for_tail; + } + ax = s->rect.x1; /* d < 0, dx < 0 */ +a_moved_horizontally: /* d and dx have the same sign */ + assert((d > 0 && dx > 0) || (d < 0 && dx < 0)); + assert(dx != 0); + ay = advance(ay, by, d, dx); + used = total * d/dx; + total -= used; + dx = bx - ax; + dy = by - ay; + } + /* Then vertically... */ + if ((d = s->rect.y0 - ay) > 0) + { + if (by < s->rect.y0) + { + /* Entirely off screen */ + tail = total; + old_bx = bx; + old_by = by; + goto adjust_for_tail; + } + ay = s->rect.y0; /* d > 0, dy > 0 */ + goto a_moved_vertically; + } + else if (d < 0 && (d = (s->rect.y1 - ay)) < 0) + { + if (by > s->rect.y1) + { + /* Entirely off screen */ + tail = total; + old_bx = bx; + old_by = by; + goto adjust_for_tail; + } + ay = s->rect.y1; /* d < 0, dy < 0 */ +a_moved_vertically: /* d and dy have the same sign */ + assert((d > 0 && dy > 0) || (d < 0 && dy < 0)); + assert(dy != 0); + ax = advance(ax, bx, d, dy); + d = total * d/dy; + total -= d; + used += d; + dx = bx - ax; + dy = by - ay; + } + if (used != 0.0f) + { + /* Update the position in the dash array */ + if (s->toggle) + { + fz_stroke_lineto(ctx, s, ax, ay, from_bezier); + } + else + { + fz_stroke_flush(ctx, s, s->cap, s->stroke->dash_cap); + s->cap = s->stroke->dash_cap; + fz_stroke_moveto(ctx, s, ax, ay); + } + used += s->phase; + n = used/s->dash_total; + used -= n*s->dash_total; + if (n & s->dash_len & 1) + s->toggle = !s->toggle; + while (used >= s->dash_list[s->offset]) + { + used -= s->dash_list[s->offset]; + s->offset++; + if (s->offset == s->dash_len) + s->offset = 0; + s->toggle = !s->toggle; + } + if (s->toggle) + { + fz_stroke_lineto(ctx, s, ax, ay, from_bezier); + } + else + { + fz_stroke_flush(ctx, s, s->cap, s->stroke->dash_cap); + s->cap = s->stroke->dash_cap; + fz_stroke_moveto(ctx, s, ax, ay); + } + s->phase = used; + used = 0; + } + + /* Now if bx is off screen, bring it back */ + if (dx == 0) + { + /* Earlier stages can have moved a to be b, while leaving it completely off screen. */ + } + else if ((d = bx - s->rect.x0) < 0) + { + old_bx = bx; + old_by = by; + bx = s->rect.x0; /* d < 0, dx < 0 */ + goto b_moved_horizontally; + } + else if (d > 0 && (d = (bx - s->rect.x1)) > 0) + { + old_bx = bx; + old_by = by; + bx = s->rect.x1; /* d > 0, dx > 0 */ +b_moved_horizontally: /* d and dx have the same sign */ + assert((d > 0 && dx > 0) || (d < 0 && dx < 0)); + assert(dx != 0); + by = advance(by, ay, d, dx); + tail = total * d/dx; + total -= tail; + dx = bx - ax; + dy = by - ay; + } + /* Then vertically... */ + if (dy == 0) + { + /* Earlier stages can have moved a to be b, while leaving it completely off screen. */ + } + else if ((d = by - s->rect.y0) < 0) + { + old_bx = bx; + old_by = by; + by = s->rect.y0; /* d < 0, dy < 0 */ + goto b_moved_vertically; + } + else if (d > 0 && (d = (by - s->rect.y1)) > 0) + { + float t; + old_bx = bx; + old_by = by; + by = s->rect.y1; /* d > 0, dy > 0 */ +b_moved_vertically: /* d and dy have the same sign */ + assert((d > 0 && dy > 0) || (d < 0 && dy < 0)); + assert(dy != 0); + bx = advance(bx, ax, d, dy); + t = total * d/dy; + tail += t; + total -= t; + dx = bx - ax; + dy = by - ay; + } + + while (total - used > s->dash_list[s->offset] - s->phase) + { + used += s->dash_list[s->offset] - s->phase; + ratio = used / total; + mx = ax + ratio * dx; + my = ay + ratio * dy; + + if (s->toggle) + { + fz_stroke_lineto_aux(ctx, s, mx, my, from_bezier, dx, dy); + } + else + { + fz_stroke_flush(ctx, s, s->cap, dash_cap); + s->cap = dash_cap; + fz_stroke_moveto(ctx, s, mx, my); + } + + s->toggle = !s->toggle; + s->phase = 0; + s->offset ++; + if (s->offset == s->dash_len) + s->offset = 0; + } + + s->phase += total - used; + + if (tail == 0.0f) + { + s->dash_cur.x = bx; + s->dash_cur.y = by; + + if (s->toggle) + { + fz_stroke_lineto_aux(ctx, s, bx, by, from_bezier, dx, dy); + } + } + else + { +adjust_for_tail: + s->dash_cur.x = old_bx; + s->dash_cur.y = old_by; + /* Update the position in the dash array */ + if (s->toggle) + { + fz_stroke_lineto_aux(ctx, s, old_bx, old_by, from_bezier, dx, dy); + } + else + { + fz_stroke_flush(ctx, s, s->cap, dash_cap); + s->cap = dash_cap; + fz_stroke_moveto(ctx, s, old_bx, old_by); + } + tail += s->phase; + n = tail/s->dash_total; + tail -= n*s->dash_total; + if (n & s->dash_len & 1) + s->toggle = !s->toggle; + while (tail > s->dash_list[s->offset]) + { + tail -= s->dash_list[s->offset]; + s->offset++; + if (s->offset == s->dash_len) + s->offset = 0; + s->toggle = !s->toggle; + } + if (s->toggle) + { + fz_stroke_lineto_aux(ctx, s, old_bx, old_by, from_bezier, dx, dy); + } + else + { + fz_stroke_flush(ctx, s, s->cap, dash_cap); + s->cap = dash_cap; + fz_stroke_moveto(ctx, s, old_bx, old_by); + } + s->phase = tail; + } +} + +static void +fz_dash_bezier(fz_context *ctx, struct sctx *s, + float xa, float ya, + float xb, float yb, + float xc, float yc, + float xd, float yd, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xcd, ycd; + float xabc, yabc; + float xbcd, ybcd; + float xabcd, yabcd; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xd - xc)); + dmax = fz_max(dmax, fz_abs(yd - yc)); + if (dmax < s->flatness || depth >= MAX_DEPTH) + { + fz_dash_lineto(ctx, s, xd, yd, 1); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + xcd = xc + xd; + ycd = yc + yd; + + xabc = xab + xbc; + yabc = yab + ybc; + xbcd = xbc + xcd; + ybcd = ybc + ycd; + + xabcd = xabc + xbcd; + yabcd = yabc + ybcd; + + xab *= 0.5f; yab *= 0.5f; + /* xbc *= 0.5f; ybc *= 0.5f; */ + xcd *= 0.5f; ycd *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + xbcd *= 0.25f; ybcd *= 0.25f; + + xabcd *= 0.125f; yabcd *= 0.125f; + + fz_dash_bezier(ctx, s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1); + fz_dash_bezier(ctx, s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1); +} + +static void +fz_dash_quad(fz_context *ctx, struct sctx *s, + float xa, float ya, + float xb, float yb, + float xc, float yc, int depth) +{ + float dmax; + float xab, yab; + float xbc, ybc; + float xabc, yabc; + + /* termination check */ + dmax = fz_abs(xa - xb); + dmax = fz_max(dmax, fz_abs(ya - yb)); + dmax = fz_max(dmax, fz_abs(xc - xb)); + dmax = fz_max(dmax, fz_abs(yc - yb)); + if (dmax < s->flatness || depth >= MAX_DEPTH) + { + fz_dash_lineto(ctx, s, xc, yc, 1); + return; + } + + xab = xa + xb; + yab = ya + yb; + xbc = xb + xc; + ybc = yb + yc; + + xabc = xab + xbc; + yabc = yab + ybc; + + xab *= 0.5f; yab *= 0.5f; + xbc *= 0.5f; ybc *= 0.5f; + + xabc *= 0.25f; yabc *= 0.25f; + + fz_dash_quad(ctx, s, xa, ya, xab, yab, xabc, yabc, depth + 1); + fz_dash_quad(ctx, s, xabc, yabc, xbc, ybc, xc, yc, depth + 1); +} + +static void +dash_moveto(fz_context *ctx, void *s_, float x, float y) +{ + sctx *s = (sctx *)s_; + + fz_dash_moveto(ctx, s, x, y); + s->dash_beg.x = s->cur.x = x; + s->dash_beg.y = s->cur.y = y; +} + +static void +dash_lineto(fz_context *ctx, void *s_, float x, float y) +{ + sctx *s = (sctx *)s_; + + fz_dash_lineto(ctx, s, x, y, 0); + s->cur.x = x; + s->cur.y = y; +} + +static void +dash_curveto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + sctx *s = (sctx *)s_; + + fz_dash_bezier(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, x3, y3, 0); + s->cur.x = x3; + s->cur.y = y3; +} + +static void +dash_quadto(fz_context *ctx, void *s_, float x1, float y1, float x2, float y2) +{ + sctx *s = (sctx *)s_; + + fz_dash_quad(ctx, s, s->cur.x, s->cur.y, x1, y1, x2, y2, 0); + s->cur.x = x2; + s->cur.y = y2; +} + +static void +dash_close(fz_context *ctx, void *s_) +{ + sctx *s = (sctx *)s_; + + fz_dash_lineto(ctx, s, s->dash_beg.x, s->dash_beg.y, 0); + s->cur.x = s->dash_beg.x; + s->cur.y = s->dash_beg.y; +} + +static const fz_path_walker dash_proc = +{ + dash_moveto, + dash_lineto, + dash_curveto, + dash_close, + dash_quadto +}; + +static int +do_flatten_stroke(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, float flatness, float linewidth) +{ + struct sctx s; + const fz_path_walker *proc = &stroke_proc; + + s.stroke = stroke; + s.rast = rast; + s.ctm = ctm; + s.flatness = flatness; + s.linejoin = stroke->linejoin; + s.linewidth = linewidth * 0.5f; /* hairlines use a different value from the path value */ + s.miterlimit = stroke->miterlimit; + s.sn = 0; + s.not_just_moves = 0; + s.toggle = 0; + s.offset = 0; + s.phase = 0; + s.dirn_x = 0; + s.dirn_y = 0; + + s.cap = stroke->start_cap; + + s.dash_list = NULL; + s.dash_len = stroke->dash_len; + if (s.dash_len > 0) + { + int i; + fz_matrix inv; + float max_expand; + const float *list = stroke->dash_list; + + s.dash_total = 0; + for (i = 0; i < s.dash_len; i++) + s.dash_total += list[i]; + if (s.dash_total == 0) + return 1; + + s.rect = fz_scissor_rasterizer(ctx, rast); + if (fz_try_invert_matrix(&inv, ctm)) + return 1; + s.rect = fz_transform_rect(s.rect, inv); + s.rect.x0 -= linewidth; + s.rect.x1 += linewidth; + s.rect.y0 -= linewidth; + s.rect.y1 += linewidth; + + max_expand = fz_matrix_max_expansion(ctm); + if (s.dash_total >= 0.01f && s.dash_total * max_expand >= 0.5f) + { + proc = &dash_proc; + s.dash_phase = fmodf(stroke->dash_phase, s.dash_total); + s.dash_list = list; + } + } + + s.cur.x = s.cur.y = 0; + fz_walk_path(ctx, path, proc, &s); + fz_stroke_flush(ctx, &s, s.cap, stroke->end_cap); + + return fz_is_empty_irect(fz_bound_rasterizer(ctx, rast)); +} + +int +fz_flatten_stroke_path(fz_context *ctx, fz_rasterizer *rast, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, float flatness, float linewidth, fz_irect scissor, fz_irect *bbox) +{ + int empty; + fz_irect local_bbox; + if (!bbox) + bbox = &local_bbox; + + if (fz_reset_rasterizer(ctx, rast, scissor)) + { + empty = do_flatten_stroke(ctx, rast, path, stroke, ctm, flatness, linewidth); + if (empty) + return *bbox = fz_empty_irect, 1; + fz_postindex_rasterizer(ctx, rast); + } + + empty = do_flatten_stroke(ctx, rast, path, stroke, ctm, flatness, linewidth); + if (empty) + return *bbox = fz_empty_irect, 1; + + *bbox = fz_intersect_irect(scissor, fz_bound_rasterizer(ctx, rast)); + return fz_is_empty_irect(*bbox); +}
