diff options
author | Robin Watts <robin.watts@artifex.com> | 2012-01-12 15:34:30 +0000 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2012-01-12 20:15:09 +0000 |
commit | f0d427993c1b9b40f1bc0b77ed9c0433099f2a5d (patch) | |
tree | c89144197a2aafec4dac4a05506dad93e098ebdc | |
parent | 269a44d92e2fc15631b86cc0c7135baf2023f312 (diff) | |
download | mupdf-f0d427993c1b9b40f1bc0b77ed9c0433099f2a5d.tar.xz |
Support proper XPS mitering. (And stroke fixes).
XPS differs from PS/PDF/etc in the way it handles miters; rather than
simply converting a miter that's overly long to a bevel, it truncates
it at the miter limit. As such it needs to be handled correctly.
For clarity, expose new enumerated types for linejoins and linecaps,
and use these throughout code.
When we upgrade our freetype, we can move to using proper xps mitering
in that too.
Add new fz_matrix_max_expansion function to return a safer expansion
value that works in the case where we scale up in one direction and
down in another.
In the xps path drawing code, avoid generating unnecessary linetos.
Thanks to Zeniko for spotting these and providing implementations.
-rw-r--r-- | draw/draw_path.c | 78 | ||||
-rw-r--r-- | fitz/base_geometry.c | 16 | ||||
-rw-r--r-- | fitz/fitz.h | 21 | ||||
-rw-r--r-- | fitz/res_font.c | 15 | ||||
-rw-r--r-- | fitz/res_path.c | 8 | ||||
-rw-r--r-- | pdf/pdf_interpret.c | 8 | ||||
-rw-r--r-- | xps/xps_path.c | 41 |
7 files changed, 121 insertions, 66 deletions
diff --git a/draw/draw_path.c b/draw/draw_path.c index d6f9934b..29614ec5 100644 --- a/draw/draw_path.c +++ b/draw/draw_path.c @@ -2,8 +2,6 @@ #define MAX_DEPTH 8 -enum { BUTT = 0, ROUND = 1, SQUARE = 2, TRIANGLE = 3, MITER = 0, BEVEL = 2 }; - static void line(fz_gel *gel, fz_matrix *ctm, float x0, float y0, float x1, float y1) { @@ -218,7 +216,7 @@ fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c) { float miterlimit = s->miterlimit; float linewidth = s->linewidth; - int linejoin = s->linejoin; + fz_linejoin linejoin = s->linejoin; float dx0, dy0; float dx1, dy1; float dlx0, dly0; @@ -234,10 +232,20 @@ fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c) 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; + } + if (dx0 * dx0 + dy0 * dy0 < FLT_EPSILON) - linejoin = BEVEL; + linejoin = FZ_LINEJOIN_BEVEL; if (dx1 * dx1 + dy1 * dy1 < FLT_EPSILON) - linejoin = BEVEL; + linejoin = FZ_LINEJOIN_BEVEL; scale = linewidth / sqrtf(dx0 * dx0 + dy0 * dy0); dlx0 = dy0 * scale; @@ -247,36 +255,46 @@ fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c) dlx1 = dy1 * scale; dly1 = -dx1 * scale; - cross = dx1 * dy0 - dx0 * dy1; - 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 = BEVEL; + linejoin = FZ_LINEJOIN_BEVEL; - if (linejoin == MITER) + if (linejoin == FZ_LINEJOIN_MITER) if (dmr2 * miterlimit * miterlimit < linewidth * linewidth) - linejoin = BEVEL; + linejoin = FZ_LINEJOIN_BEVEL; - if (linejoin == BEVEL) + if (linejoin == FZ_LINEJOIN_BEVEL) { fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dlx1, b.y - dly1); fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); } - if (linejoin == MITER) + if (linejoin == FZ_LINEJOIN_MITER) + { + scale = linewidth * linewidth / dmr2; + dmx *= scale; + dmy *= scale; + + fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); + 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); + } + + /* XPS miter joins are clipped at miterlength, rather than simply + * being converted to bevelled joins. */ + if (linejoin == FZ_LINEJOIN_MITER_XPS) { scale = linewidth * linewidth / dmr2; dmx *= scale; dmy *= scale; - if (cross < 0) + if (cross == 0) { + fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dlx1, b.y - dly1); - fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dmx, b.y + dmy); - fz_add_line(s, b.x + dmx, b.y + dmy, b.x + dlx0, b.y + dly0); } else { @@ -286,23 +304,15 @@ fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c) } } - if (linejoin == ROUND) + if (linejoin == FZ_LINEJOIN_ROUND) { - if (cross < 0) - { - fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dlx1, b.y - dly1); - fz_add_arc(s, b.x, b.y, dlx1, dly1, dlx0, dly0); - } - else - { - fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); - fz_add_arc(s, b.x, b.y, -dlx0, -dly0, -dlx1, -dly1); - } + fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0); + 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, int linecap) +fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, fz_linecap linecap) { float flatness = s->flatness; float linewidth = s->linewidth; @@ -314,10 +324,10 @@ fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, int linecap) float dlx = dy * scale; float dly = -dx * scale; - if (linecap == BUTT) + if (linecap == FZ_LINECAP_BUTT) fz_add_line(s, b.x - dlx, b.y - dly, b.x + dlx, b.y + dly); - if (linecap == ROUND) + if (linecap == FZ_LINECAP_ROUND) { int i; int n = ceilf((float)M_PI / (2.0f * (float)M_SQRT2 * sqrtf(flatness / linewidth))); @@ -337,7 +347,7 @@ fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, int linecap) fz_add_line(s, ox, oy, b.x + dlx, b.y + dly); } - if (linecap == SQUARE) + if (linecap == FZ_LINECAP_SQUARE) { fz_add_line(s, b.x - dlx, b.y - dly, b.x - dlx - dly, b.y - dly + dlx); @@ -347,7 +357,7 @@ fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, int linecap) b.x + dlx, b.y + dly); } - if (linecap == TRIANGLE) + if (linecap == FZ_LINECAP_TRIANGLE) { float mx = -dly; float my = dlx; @@ -382,7 +392,7 @@ fz_add_line_dot(struct sctx *s, fz_point a) } static void -fz_stroke_flush(struct sctx *s, int start_cap, int end_cap) +fz_stroke_flush(struct sctx *s, fz_linecap start_cap, fz_linecap end_cap) { if (s->sn == 2) { @@ -413,7 +423,7 @@ fz_stroke_lineto(struct sctx *s, fz_point cur) if (dx * dx + dy * dy < FLT_EPSILON) { - if (s->cap == ROUND || s->dash_list) + if (s->cap == FZ_LINECAP_ROUND || s->dash_list) s->dot = 1; return; } @@ -586,7 +596,7 @@ fz_flatten_stroke_path(fz_gel *gel, fz_path *path, fz_stroke_state *stroke, fz_m } static void -fz_dash_moveto(struct sctx *s, fz_point a, int start_cap, int end_cap) +fz_dash_moveto(struct sctx *s, fz_point a, fz_linecap start_cap, fz_linecap end_cap) { s->toggle = 1; s->offset = 0; diff --git a/fitz/base_geometry.c b/fitz/base_geometry.c index 3269605d..e8d9032f 100644 --- a/fitz/base_geometry.c +++ b/fitz/base_geometry.c @@ -121,6 +121,22 @@ fz_matrix_expansion(fz_matrix m) return sqrtf(fabsf(m.a * m.d - m.b * m.c)); } +float +fz_matrix_max_expansion(fz_matrix m) +{ + float max = fabsf(m.a); + float x = fabsf(m.b); + if (max < x) + max = x; + x = fabsf(m.c); + if (max < x) + max = x; + x = fabsf(m.d); + if (max < x) + max = x; + return max; +} + fz_point fz_transform_point(fz_matrix m, fz_point p) { diff --git a/fitz/fitz.h b/fitz/fitz.h index 6aebdf61..bfc4723a 100644 --- a/fitz/fitz.h +++ b/fitz/fitz.h @@ -520,6 +520,7 @@ fz_matrix fz_translate(float tx, float ty); fz_matrix fz_invert_matrix(fz_matrix m); int fz_is_rectilinear(fz_matrix m); float fz_matrix_expansion(fz_matrix m); +float fz_matrix_max_expansion(fz_matrix m); fz_bbox fz_round_rect(fz_rect r); fz_bbox fz_intersect_bbox(fz_bbox a, fz_bbox b); @@ -1102,6 +1103,22 @@ typedef enum fz_path_item_kind_e FZ_CLOSE_PATH } fz_path_item_kind; +typedef enum fz_linecap_e +{ + FZ_LINECAP_BUTT = 0, + FZ_LINECAP_ROUND = 1, + FZ_LINECAP_SQUARE = 2, + FZ_LINECAP_TRIANGLE = 3 +} fz_linecap; + +typedef enum fz_linejoin_e +{ + FZ_LINEJOIN_MITER = 0, + FZ_LINEJOIN_ROUND = 1, + FZ_LINEJOIN_BEVEL = 2, + FZ_LINEJOIN_MITER_XPS = 3 +} fz_linejoin; + union fz_path_item_s { fz_path_item_kind k; @@ -1116,8 +1133,8 @@ struct fz_path_s struct fz_stroke_state_s { - int start_cap, dash_cap, end_cap; - int linejoin; + fz_linecap start_cap, dash_cap, end_cap; + fz_linejoin linejoin; float linewidth; float miterlimit; float dash_phase; diff --git a/fitz/res_font.c b/fitz/res_font.c index c3e2e0bd..4a1c9dfa 100644 --- a/fitz/res_font.c +++ b/fitz/res_font.c @@ -449,6 +449,7 @@ fz_render_ft_stroked_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix tr FT_Glyph glyph; FT_BitmapGlyph bitmap; fz_pixmap *pixmap; + FT_Stroker_LineJoin line_join; trm = fz_adjust_ft_glyph_width(ctx, font, gid, trm); @@ -485,7 +486,19 @@ fz_render_ft_stroked_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix tr return NULL; } - FT_Stroker_Set(stroker, linewidth, state->start_cap, state->linejoin, state->miterlimit * 65536); +#if 0 + line_join = state->linejoin == FZ_LINEJOIN_MITER ? FT_STROKER_LINEJOIN_MITER_FIXED : + state->linejoin == FZ_LINEJOIN_ROUND ? FT_STROKER_LINEJOIN_ROUND : + state->linejoin == FZ_LINEJOIN_BEVEL ? FT_STROKER_LINEJOIN_BEVEL : + FT_STROKER_LINEJOIN_MITER_VARIABLE; +#else + /* Until we upgrade freetype */ + line_join = state->linejoin == FZ_LINEJOIN_MITER ? FT_STROKER_LINEJOIN_MITER : + state->linejoin == FZ_LINEJOIN_ROUND ? FT_STROKER_LINEJOIN_ROUND : + state->linejoin == FZ_LINEJOIN_BEVEL ? FT_STROKER_LINEJOIN_BEVEL : + FT_STROKER_LINEJOIN_MITER; +#endif + FT_Stroker_Set(stroker, linewidth, state->start_cap, line_join, state->miterlimit * 65536); fterr = FT_Get_Glyph(face->glyph, &glyph); if (fterr) diff --git a/fitz/res_path.c b/fitz/res_path.c index d8838899..a062f813 100644 --- a/fitz/res_path.c +++ b/fitz/res_path.c @@ -189,9 +189,11 @@ fz_bound_path(fz_path *path, fz_stroke_state *stroke, fz_matrix ctm) if (stroke) { - float miterlength = stroke->miterlimit; - float linewidth = stroke->linewidth; - float expand = MAX(miterlength, linewidth) * 0.5f; + float expand = stroke->linewidth; + if (expand == 0) + expand = 1.0f; + expand *= fz_matrix_max_expansion(ctm); + expand *= stroke->miterlimit; r.x0 -= expand; r.y0 -= expand; r.x1 += expand; diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index fe0e1ce8..0976912e 100644 --- a/pdf/pdf_interpret.c +++ b/pdf/pdf_interpret.c @@ -818,10 +818,10 @@ pdf_init_gstate(pdf_gstate *gs, fz_matrix ctm) gs->ctm = ctm; gs->clip_depth = 0; - gs->stroke_state.start_cap = 0; - gs->stroke_state.dash_cap = 0; - gs->stroke_state.end_cap = 0; - gs->stroke_state.linejoin = 0; + gs->stroke_state.start_cap = FZ_LINECAP_BUTT; + gs->stroke_state.dash_cap = FZ_LINECAP_BUTT; + gs->stroke_state.end_cap = FZ_LINECAP_BUTT; + gs->stroke_state.linejoin = FZ_LINEJOIN_MITER; gs->stroke_state.linewidth = 1; gs->stroke_state.miterlimit = 10; gs->stroke_state.dash_phase = 0; diff --git a/xps/xps_path.c b/xps/xps_path.c index 402ca532..8b66e1ff 100644 --- a/xps/xps_path.c +++ b/xps/xps_path.c @@ -39,6 +39,9 @@ fz_currentpoint(fz_path *path) * line segments. We cannot use the fz_arc function because they only draw * circular arcs, we need to transform the line to make them elliptical but * without transforming the line width. + * + * We are guaranteed that on entry the point is at the point that would be + * calculated by th0, and on exit, a point is generated for us at th0. */ static void xps_draw_arc_segment(fz_context *doc, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw) @@ -53,40 +56,24 @@ xps_draw_arc_segment(fz_context *doc, fz_path *path, fz_matrix mtx, float th0, f if (iscw) { - p.x = cosf(th0); - p.y = sinf(th0); - p = fz_transform_point(mtx, p); - fz_lineto(doc, path, p.x, p.y); - for (t = th0; t < th1; t += d) + for (t = th0 + d; t < th1 - d/2; t += d) { p.x = cosf(t); p.y = sinf(t); p = fz_transform_point(mtx, p); fz_lineto(doc, path, p.x, p.y); } - p.x = cosf(th1); - p.y = sinf(th1); - p = fz_transform_point(mtx, p); - fz_lineto(doc, path, p.x, p.y); } else { th0 += (float)M_PI * 2; - p.x = cosf(th0); - p.y = sinf(th0); - p = fz_transform_point(mtx, p); - fz_lineto(doc, path, p.x, p.y); - for (t = th0; t > th1; t -= d) + for (t = th0 - d; t > th1 + d/2; t -= d) { p.x = cosf(t); p.y = sinf(t); p = fz_transform_point(mtx, p); fz_lineto(doc, path, p.x, p.y); } - p.x = cosf(th1); - p.y = sinf(th1); - p = fz_transform_point(mtx, p); - fz_lineto(doc, path, p.x, p.y); } } @@ -106,6 +93,16 @@ angle_between(const fz_point u, const fz_point v) return sign * acosf(t); } +/* Some explaination of the parameters here is warranted. See: + * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + * Add an arc segment to path, that describes a section of an elliptical arc + * from the current point of path to (point_x,point_y), such that: + * the arc segment is taken from an elliptical arc of semi major radius + * size_x, semi minor radius size_y, where the semi major axis of the + * ellipse is rotated by rotation_angle. + * if is_large_arc, then the arc segment is selected to be > 180 degrees. + * if is_clockwise, then the arc sweeps clockwise. + */ static void xps_draw_arc(fz_context *doc, fz_path *path, float size_x, float size_y, float rotation_angle, @@ -884,12 +881,12 @@ xps_parse_path(xps_document *doc, fz_matrix ctm, char *base_uri, xps_resource *d stroke.dash_cap = xps_parse_line_cap(stroke_dash_cap_att); stroke.end_cap = xps_parse_line_cap(stroke_end_line_cap_att); - stroke.linejoin = 0; + stroke.linejoin = FZ_LINEJOIN_MITER_XPS; if (stroke_line_join_att) { - if (!strcmp(stroke_line_join_att, "Miter")) stroke.linejoin = 0; - if (!strcmp(stroke_line_join_att, "Round")) stroke.linejoin = 1; - if (!strcmp(stroke_line_join_att, "Bevel")) stroke.linejoin = 2; + if (!strcmp(stroke_line_join_att, "Miter")) stroke.linejoin = FZ_LINEJOIN_MITER_XPS; + if (!strcmp(stroke_line_join_att, "Round")) stroke.linejoin = FZ_LINEJOIN_ROUND; + if (!strcmp(stroke_line_join_att, "Bevel")) stroke.linejoin = FZ_LINEJOIN_BEVEL; } stroke.miterlimit = 10; |