diff options
Diffstat (limited to 'source/fitz/draw-path.c')
-rw-r--r-- | source/fitz/draw-path.c | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/source/fitz/draw-path.c b/source/fitz/draw-path.c new file mode 100644 index 00000000..31b038b1 --- /dev/null +++ b/source/fitz/draw-path.c @@ -0,0 +1,831 @@ +#include "mupdf/fitz.h" +#include "draw-imp.h" + +#define MAX_DEPTH 8 + +static void +line(fz_gel *gel, const 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_gel(gel, tx0, ty0, tx1, ty1); +} + +static void +bezier(fz_gel *gel, const 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(gel, 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(gel, ctm, flatness, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1); + bezier(gel, ctm, flatness, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1); +} + +void +fz_flatten_fill_path(fz_gel *gel, fz_path *path, const fz_matrix *ctm, float flatness) +{ + float x1, y1, x2, y2, x3, y3; + float cx = 0; + float cy = 0; + float bx = 0; + float by = 0; + int i = 0; + + while (i < path->len) + { + switch (path->items[i++].k) + { + case FZ_MOVETO: + /* implicit closepath before moveto */ + if (cx != bx || cy != by) + line(gel, ctm, cx, cy, bx, by); + x1 = path->items[i++].v; + y1 = path->items[i++].v; + cx = bx = x1; + cy = by = y1; + break; + + case FZ_LINETO: + x1 = path->items[i++].v; + y1 = path->items[i++].v; + line(gel, ctm, cx, cy, x1, y1); + cx = x1; + cy = y1; + break; + + case FZ_CURVETO: + x1 = path->items[i++].v; + y1 = path->items[i++].v; + x2 = path->items[i++].v; + y2 = path->items[i++].v; + x3 = path->items[i++].v; + y3 = path->items[i++].v; + bezier(gel, ctm, flatness, cx, cy, x1, y1, x2, y2, x3, y3, 0); + cx = x3; + cy = y3; + break; + + case FZ_CLOSE_PATH: + line(gel, ctm, cx, cy, bx, by); + cx = bx; + cy = by; + break; + } + } + + if (cx != bx || cy != by) + line(gel, ctm, cx, cy, bx, by); +} + +struct sctx +{ + fz_gel *gel; + const fz_matrix *ctm; + float flatness; + + int linejoin; + float linewidth; + float miterlimit; + fz_point beg[2]; + fz_point seg[2]; + int sn, bn; + int dot; + int from_bezier; + + const float *dash_list; + float dash_phase; + int dash_len; + int toggle, cap; + int offset; + float phase; + fz_point cur; +}; + +static void +fz_add_line(struct sctx *s, float x0, float y0, float x1, float y1) +{ + 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_gel(s->gel, tx0, ty0, tx1, ty1); +} + +static void +fz_add_arc(struct sctx *s, + float xc, float yc, + float x0, float y0, + float x1, float y1) +{ + float th0, th1, r; + float theta; + float ox, oy, nx, ny; + int n, i; + + r = fabsf(s->linewidth); + theta = 2 * (float)M_SQRT2 * sqrtf(s->flatness / r); + th0 = atan2f(y0, x0); + th1 = atan2f(y1, x1); + + if (r > 0) + { + if (th0 < th1) + th0 += (float)M_PI * 2; + n = ceilf((th0 - th1) / theta); + } + else + { + if (th1 < th0) + th1 += (float)M_PI * 2; + n = ceilf((th1 - th0) / theta); + } + + 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(s, xc + ox, yc + oy, xc + nx, yc + ny); + ox = nx; + oy = ny; + } + + fz_add_line(s, xc + ox, yc + oy, xc + x1, yc + y1); +} + +static void +fz_add_line_stroke(struct sctx *s, fz_point a, fz_point b) +{ + float dx = b.x - a.x; + float dy = b.y - a.y; + float scale = s->linewidth / sqrtf(dx * dx + dy * dy); + float dlx = dy * scale; + float dly = -dx * scale; + fz_add_line(s, a.x - dlx, a.y - dly, b.x - dlx, b.y - dly); + fz_add_line(s, b.x + dlx, b.y + dly, a.x + dlx, a.y + dly); +} + +static void +fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c, 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; + float len0, len1; + + dx0 = b.x - a.x; + dy0 = b.y - a.y; + + dx1 = c.x - b.x; + dy1 = c.y - b.y; + + 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; + } + + len0 = dx0 * dx0 + dy0 * dy0; + if (len0 < FLT_EPSILON) + linejoin = FZ_LINEJOIN_BEVEL; + len1 = dx1 * dx1 + dy1 * dy1; + if (len1 < FLT_EPSILON) + linejoin = FZ_LINEJOIN_BEVEL; + + scale = linewidth / sqrtf(len0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + scale = linewidth / sqrtf(len1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + + 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; + + if (join_under) + { + fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); + } + else + { + fz_add_line(s, b.x + dlx1, b.y + dly1, b.x, b.y); + fz_add_line(s, b.x, b.y, b.x + dlx0, b.y + dly0); + } + + /* 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 + { + float k, t0x, t0y, t1x, t1y; + scale = linewidth * linewidth / dmr2; + dmx *= scale; + dmy *= scale; + k = (scale - linewidth * miterlimit / sqrtf(dmr2)) / (scale - 1); + t0x = b.x - dmx + k * (dmx - dlx0); + t0y = b.y - dmy + k * (dmy - dly0); + t1x = b.x - dmx + k * (dmx - dlx1); + t1y = b.y - dmy + k * (dmy - dly1); + + fz_add_line(s, b.x - dlx0, b.y - dly0, t0x, t0y); + fz_add_line(s, t0x, t0y, t1x, t1y); + fz_add_line(s, t1x, t1y, b.x - dlx1, b.y - dly1); + } + } + else if (linejoin == FZ_LINEJOIN_MITER) + if (dmr2 * miterlimit * miterlimit < linewidth * linewidth) + linejoin = FZ_LINEJOIN_BEVEL; + + if (linejoin == FZ_LINEJOIN_MITER) + { + scale = linewidth * linewidth / dmr2; + dmx *= scale; + dmy *= scale; + + fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dmx, b.y - dmy); + fz_add_line(s, b.x - dmx, b.y - dmy, b.x - dlx1, b.y - dly1); + } + + if (linejoin == FZ_LINEJOIN_BEVEL) + { + fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dlx1, b.y - dly1); + } + + if (linejoin == FZ_LINEJOIN_ROUND) + { + fz_add_arc(s, b.x, b.y, -dlx0, -dly0, -dlx1, -dly1); + } +} + +static void +fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, fz_linecap linecap) +{ + float flatness = s->flatness; + float linewidth = s->linewidth; + + float dx = b.x - a.x; + float dy = b.y - a.y; + + float scale = linewidth / sqrtf(dx * dx + dy * dy); + float dlx = dy * scale; + float dly = -dx * scale; + + if (linecap == FZ_LINECAP_BUTT) + fz_add_line(s, b.x - dlx, b.y - dly, b.x + dlx, b.y + dly); + + if (linecap == FZ_LINECAP_ROUND) + { + int i; + int n = ceilf((float)M_PI / (2.0f * (float)M_SQRT2 * sqrtf(flatness / linewidth))); + float ox = b.x - dlx; + float oy = b.y - dly; + for (i = 1; i < n; i++) + { + float theta = (float)M_PI * i / n; + float cth = cosf(theta); + float sth = sinf(theta); + float nx = b.x - dlx * cth - dly * sth; + float ny = b.y - dly * cth + dlx * sth; + fz_add_line(s, ox, oy, nx, ny); + ox = nx; + oy = ny; + } + fz_add_line(s, ox, oy, b.x + dlx, b.y + dly); + } + + if (linecap == FZ_LINECAP_SQUARE) + { + fz_add_line(s, b.x - dlx, b.y - dly, + b.x - dlx - dly, b.y - dly + dlx); + fz_add_line(s, b.x - dlx - dly, b.y - dly + dlx, + b.x + dlx - dly, b.y + dly + dlx); + fz_add_line(s, b.x + dlx - dly, b.y + dly + dlx, + b.x + dlx, b.y + dly); + } + + if (linecap == FZ_LINECAP_TRIANGLE) + { + float mx = -dly; + float my = dlx; + fz_add_line(s, b.x - dlx, b.y - dly, b.x + mx, b.y + my); + fz_add_line(s, b.x + mx, b.y + my, b.x + dlx, b.y + dly); + } +} + +static void +fz_add_line_dot(struct sctx *s, fz_point a) +{ + float flatness = s->flatness; + float linewidth = s->linewidth; + int n = ceilf((float)M_PI / ((float)M_SQRT2 * sqrtf(flatness / linewidth))); + float ox = a.x - linewidth; + float oy = a.y; + int i; + + for (i = 1; i < n; i++) + { + float theta = (float)M_PI * 2 * i / n; + float cth = cosf(theta); + float sth = sinf(theta); + float nx = a.x - cth * linewidth; + float ny = a.y + sth * linewidth; + fz_add_line(s, ox, oy, nx, ny); + ox = nx; + oy = ny; + } + + fz_add_line(s, ox, oy, a.x - linewidth, a.y); +} + +static void +fz_stroke_flush(struct sctx *s, fz_linecap start_cap, fz_linecap end_cap) +{ + if (s->sn == 2) + { + fz_add_line_cap(s, s->beg[1], s->beg[0], start_cap); + fz_add_line_cap(s, s->seg[0], s->seg[1], end_cap); + } + else if (s->dot) + { + fz_add_line_dot(s, s->beg[0]); + } +} + +static void +fz_stroke_moveto(struct sctx *s, fz_point cur) +{ + s->seg[0] = cur; + s->beg[0] = cur; + s->sn = 1; + s->bn = 1; + s->dot = 0; + s->from_bezier = 0; +} + +static void +fz_stroke_lineto(struct sctx *s, fz_point cur, int from_bezier) +{ + float dx = cur.x - s->seg[s->sn-1].x; + float dy = cur.y - s->seg[s->sn-1].y; + + if (dx * dx + dy * dy < FLT_EPSILON) + { + if (s->cap == FZ_LINECAP_ROUND || s->dash_list) + s->dot = 1; + return; + } + + fz_add_line_stroke(s, s->seg[s->sn-1], cur); + + if (s->sn == 2) + { + fz_add_line_join(s, s->seg[0], s->seg[1], cur, s->from_bezier & from_bezier); + s->seg[0] = s->seg[1]; + s->seg[1] = cur; + } + s->from_bezier = from_bezier; + + if (s->sn == 1) + s->seg[s->sn++] = cur; + if (s->bn == 1) + s->beg[s->bn++] = cur; +} + +static void +fz_stroke_closepath(struct sctx *s) +{ + if (s->sn == 2) + { + fz_stroke_lineto(s, s->beg[0], 0); + if (s->seg[1].x == s->beg[0].x && s->seg[1].y == s->beg[0].y) + fz_add_line_join(s, s->seg[0], s->beg[0], s->beg[1], 0); + else + fz_add_line_join(s, s->seg[1], s->beg[0], s->beg[1], 0); + } + else if (s->dot) + { + fz_add_line_dot(s, s->beg[0]); + } + + s->seg[0] = s->beg[0]; + s->bn = 1; + s->sn = 1; + s->dot = 0; + s->from_bezier = 0; +} + +static void +fz_stroke_bezier(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_point p; + p.x = xd; + p.y = yd; + fz_stroke_lineto(s, p, 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(s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1); + fz_stroke_bezier(s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1); +} + +void +fz_flatten_stroke_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth) +{ + struct sctx s; + fz_point p0, p1, p2, p3; + int i; + + s.gel = gel; + 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.bn = 0; + s.dot = 0; + + s.dash_list = NULL; + s.dash_phase = 0; + s.dash_len = 0; + s.toggle = 0; + s.offset = 0; + s.phase = 0; + + s.cap = stroke->start_cap; + + i = 0; + + if (path->len > 0 && path->items[0].k != FZ_MOVETO) + return; + + p0.x = p0.y = 0; + + while (i < path->len) + { + switch (path->items[i++].k) + { + case FZ_MOVETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + fz_stroke_flush(&s, stroke->start_cap, stroke->end_cap); + fz_stroke_moveto(&s, p1); + p0 = p1; + break; + + case FZ_LINETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + fz_stroke_lineto(&s, p1, 0); + p0 = p1; + break; + + case FZ_CURVETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + p2.x = path->items[i++].v; + p2.y = path->items[i++].v; + p3.x = path->items[i++].v; + p3.y = path->items[i++].v; + fz_stroke_bezier(&s, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 0); + p0 = p3; + break; + + case FZ_CLOSE_PATH: + fz_stroke_closepath(&s); + break; + } + } + + fz_stroke_flush(&s, stroke->start_cap, stroke->end_cap); +} + +static void +fz_dash_moveto(struct sctx *s, fz_point a, fz_linecap start_cap, fz_linecap end_cap) +{ + s->toggle = 1; + s->offset = 0; + s->phase = s->dash_phase; + + while (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->cur = a; + + if (s->toggle) + { + fz_stroke_flush(s, s->cap, end_cap); + s->cap = start_cap; + fz_stroke_moveto(s, a); + } +} + +static void +fz_dash_lineto(struct sctx *s, fz_point b, int dash_cap, int from_bezier) +{ + float dx, dy; + float total, used, ratio; + fz_point a; + fz_point m; + + a = s->cur; + dx = b.x - a.x; + dy = b.y - a.y; + total = sqrtf(dx * dx + dy * dy); + used = 0; + + while (total - used > s->dash_list[s->offset] - s->phase) + { + used += s->dash_list[s->offset] - s->phase; + ratio = used / total; + m.x = a.x + ratio * dx; + m.y = a.y + ratio * dy; + + if (s->toggle) + { + fz_stroke_lineto(s, m, from_bezier); + } + else + { + fz_stroke_flush(s, s->cap, dash_cap); + s->cap = dash_cap; + fz_stroke_moveto(s, m); + } + + s->toggle = !s->toggle; + s->phase = 0; + s->offset ++; + if (s->offset == s->dash_len) + s->offset = 0; + } + + s->phase += total - used; + + s->cur = b; + + if (s->toggle) + { + fz_stroke_lineto(s, b, from_bezier); + } +} + +static void +fz_dash_bezier(struct sctx *s, + float xa, float ya, + float xb, float yb, + float xc, float yc, + float xd, float yd, int depth, + int dash_cap) +{ + 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_point p; + p.x = xd; + p.y = yd; + fz_dash_lineto(s, p, dash_cap, 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(s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1, dash_cap); + fz_dash_bezier(s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1, dash_cap); +} + +void +fz_flatten_dash_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth) +{ + struct sctx s; + fz_point p0, p1, p2, p3, beg; + float phase_len, max_expand; + int i; + + s.gel = gel; + s.ctm = ctm; + s.flatness = flatness; + + s.linejoin = stroke->linejoin; + s.linewidth = linewidth * 0.5f; + s.miterlimit = stroke->miterlimit; + s.sn = 0; + s.bn = 0; + s.dot = 0; + + s.dash_list = stroke->dash_list; + s.dash_phase = stroke->dash_phase; + s.dash_len = stroke->dash_len; + s.toggle = 0; + s.offset = 0; + s.phase = 0; + + s.cap = stroke->start_cap; + + if (path->len > 0 && path->items[0].k != FZ_MOVETO) + return; + + phase_len = 0; + for (i = 0; i < stroke->dash_len; i++) + phase_len += stroke->dash_list[i]; + max_expand = fz_matrix_max_expansion(ctm); + if (phase_len < 0.01f || phase_len * max_expand < 0.5f) + { + fz_flatten_stroke_path(gel, path, stroke, ctm, flatness, linewidth); + return; + } + + p0.x = p0.y = 0; + i = 0; + + while (i < path->len) + { + switch (path->items[i++].k) + { + case FZ_MOVETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + fz_dash_moveto(&s, p1, stroke->start_cap, stroke->end_cap); + beg = p0 = p1; + break; + + case FZ_LINETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + fz_dash_lineto(&s, p1, stroke->dash_cap, 0); + p0 = p1; + break; + + case FZ_CURVETO: + p1.x = path->items[i++].v; + p1.y = path->items[i++].v; + p2.x = path->items[i++].v; + p2.y = path->items[i++].v; + p3.x = path->items[i++].v; + p3.y = path->items[i++].v; + fz_dash_bezier(&s, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 0, stroke->dash_cap); + p0 = p3; + break; + + case FZ_CLOSE_PATH: + fz_dash_lineto(&s, beg, stroke->dash_cap, 0); + p0 = p1 = beg; + break; + } + } + + fz_stroke_flush(&s, s.cap, stroke->end_cap); +} |