summaryrefslogtreecommitdiff
path: root/source/fitz/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/fitz/path.c')
-rw-r--r--source/fitz/path.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/source/fitz/path.c b/source/fitz/path.c
new file mode 100644
index 00000000..cbdfc7bc
--- /dev/null
+++ b/source/fitz/path.c
@@ -0,0 +1,507 @@
+#include <assert.h>
+#include "mupdf/fitz.h"
+
+fz_path *
+fz_new_path(fz_context *ctx)
+{
+ fz_path *path;
+
+ path = fz_malloc_struct(ctx, fz_path);
+ path->len = 0;
+ path->cap = 0;
+ path->items = NULL;
+ path->last = -1;
+
+ return path;
+}
+
+fz_path *
+fz_clone_path(fz_context *ctx, fz_path *old)
+{
+ fz_path *path;
+
+ assert(old);
+ path = fz_malloc_struct(ctx, fz_path);
+ fz_try(ctx)
+ {
+ path->len = old->len;
+ path->cap = old->len;
+ path->items = fz_malloc_array(ctx, path->cap, sizeof(fz_path_item));
+ memcpy(path->items, old->items, sizeof(fz_path_item) * path->len);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, path);
+ fz_rethrow(ctx);
+ }
+
+ return path;
+}
+
+void
+fz_free_path(fz_context *ctx, fz_path *path)
+{
+ if (path == NULL)
+ return;
+ fz_free(ctx, path->items);
+ fz_free(ctx, path);
+}
+
+static void
+grow_path(fz_context *ctx, fz_path *path, int n)
+{
+ int newcap = path->cap;
+ if (path->len + n <= path->cap)
+ {
+ path->last = path->len;
+ return;
+ }
+ while (path->len + n > newcap)
+ newcap = newcap + 36;
+ path->items = fz_resize_array(ctx, path->items, newcap, sizeof(fz_path_item));
+ path->cap = newcap;
+ path->last = path->len;
+}
+
+fz_point
+fz_currentpoint(fz_context *ctx, fz_path *path)
+{
+ fz_point c, m;
+ int i;
+
+ c.x = c.y = m.x = m.y = 0;
+ i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ m.x = c.x = path->items[i++].v;
+ m.y = c.y = path->items[i++].v;
+ break;
+ case FZ_LINETO:
+ c.x = path->items[i++].v;
+ c.y = path->items[i++].v;
+ break;
+ case FZ_CURVETO:
+ i += 4;
+ c.x = path->items[i++].v;
+ c.y = path->items[i++].v;
+ break;
+ case FZ_CLOSE_PATH:
+ c = m;
+ }
+ }
+
+ return c;
+}
+
+void
+fz_moveto(fz_context *ctx, fz_path *path, float x, float y)
+{
+ if (path->last >= 0 && path->items[path->last].k == FZ_MOVETO)
+ {
+ /* No point in having MOVETO then MOVETO */
+ path->len = path->last;
+ }
+ grow_path(ctx, path, 3);
+ path->items[path->len++].k = FZ_MOVETO;
+ path->items[path->len++].v = x;
+ path->items[path->len++].v = y;
+}
+
+void
+fz_lineto(fz_context *ctx, fz_path *path, float x, float y)
+{
+ float x0, y0;
+
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "lineto with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x0 = path->items[path->last-2].v;
+ y0 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x0 = path->items[path->len-2].v;
+ y0 = path->items[path->len-1].v;
+ }
+ /* Anything other than MoveTo followed by LineTo the same place is a nop */
+ if (path->items[path->last].k != FZ_MOVETO && x0 == x && y0 == y)
+ return;
+ grow_path(ctx, path, 3);
+ path->items[path->len++].k = FZ_LINETO;
+ path->items[path->len++].v = x;
+ path->items[path->len++].v = y;
+}
+
+void
+fz_curveto(fz_context *ctx, fz_path *path,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3)
+{
+ float x0, y0;
+
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "curveto with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x0 = path->items[path->last-2].v;
+ y0 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x0 = path->items[path->len-2].v;
+ y0 = path->items[path->len-1].v;
+ }
+
+ /* Check for degenerate cases: */
+ if (x0 == x1 && y0 == y1)
+ {
+ if (x2 == x3 && y2 == y3)
+ {
+ /* If (x1,y1)==(x2,y2) and prev wasn't a moveto, then skip */
+ if (x1 == x2 && y1 == y2 && path->items[path->last].k != FZ_MOVETO)
+ return;
+ /* Otherwise a line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+ if (x1 == x2 && y1 == y2)
+ {
+ /* A line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+ }
+ else if (x1 == x2 && y1 == y2 && x2 == x3 && y2 == y3)
+ {
+ /* A line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+
+ grow_path(ctx, path, 7);
+ path->items[path->len++].k = FZ_CURVETO;
+ path->items[path->len++].v = x1;
+ path->items[path->len++].v = y1;
+ path->items[path->len++].v = x2;
+ path->items[path->len++].v = y2;
+ path->items[path->len++].v = x3;
+ path->items[path->len++].v = y3;
+}
+
+void
+fz_curvetov(fz_context *ctx, fz_path *path, float x2, float y2, float x3, float y3)
+{
+ float x1, y1;
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "curvetov with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x1 = path->items[path->last-2].v;
+ y1 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x1 = path->items[path->len-2].v;
+ y1 = path->items[path->len-1].v;
+ }
+ fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
+}
+
+void
+fz_curvetoy(fz_context *ctx, fz_path *path, float x1, float y1, float x3, float y3)
+{
+ fz_curveto(ctx, path, x1, y1, x3, y3, x3, y3);
+}
+
+void
+fz_closepath(fz_context *ctx, fz_path *path)
+{
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "closepath with no current point");
+ return;
+ }
+ /* CLOSE following a CLOSE is a NOP */
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ return;
+ grow_path(ctx, path, 1);
+ path->items[path->len++].k = FZ_CLOSE_PATH;
+}
+
+static inline fz_rect *bound_expand(fz_rect *r, const fz_point *p)
+{
+ if (p->x < r->x0) r->x0 = p->x;
+ if (p->y < r->y0) r->y0 = p->y;
+ if (p->x > r->x1) r->x1 = p->x;
+ if (p->y > r->y1) r->y1 = p->y;
+ return r;
+}
+
+fz_rect *
+fz_bound_path(fz_context *ctx, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, fz_rect *r)
+{
+ fz_point p;
+ int i = 0;
+
+ /* If the path is empty, return the empty rectangle here - don't wait
+ * for it to be expanded in the stroked case below. */
+ if (path->len == 0)
+ {
+ *r = fz_empty_rect;
+ return r;
+ }
+ /* A path must start with a moveto - and if that's all there is
+ * then the path is empty. */
+ if (path->len == 3)
+ {
+ *r = fz_empty_rect;
+ return r;
+ }
+
+ p.x = path->items[1].v;
+ p.y = path->items[2].v;
+ fz_transform_point(&p, ctm);
+ r->x0 = r->x1 = p.x;
+ r->y0 = r->y1 = p.y;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_CURVETO:
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ break;
+ case FZ_MOVETO:
+ if (i + 2 == path->len)
+ {
+ /* Trailing Moveto - cannot affect bbox */
+ i += 2;
+ break;
+ }
+ /* fallthrough */
+ case FZ_LINETO:
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ break;
+ case FZ_CLOSE_PATH:
+ break;
+ }
+ }
+
+ if (stroke)
+ {
+ fz_adjust_rect_for_stroke(r, stroke, ctm);
+ }
+
+ return r;
+}
+
+fz_rect *
+fz_adjust_rect_for_stroke(fz_rect *r, const fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ float expand;
+
+ if (!stroke)
+ return r;
+
+ expand = stroke->linewidth;
+ if (expand == 0)
+ expand = 1.0f;
+ expand *= fz_matrix_max_expansion(ctm);
+ if ((stroke->linejoin == FZ_LINEJOIN_MITER || stroke->linejoin == FZ_LINEJOIN_MITER_XPS) && stroke->miterlimit > 1)
+ expand *= stroke->miterlimit;
+
+ r->x0 -= expand;
+ r->y0 -= expand;
+ r->x1 += expand;
+ r->y1 += expand;
+ return r;
+}
+
+void
+fz_transform_path(fz_context *ctx, fz_path *path, const fz_matrix *ctm)
+{
+ int k, i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_CURVETO:
+ for (k = 0; k < 3; k++)
+ {
+ fz_transform_point((fz_point *)(void *)&path->items[i].v, ctm);
+ i += 2;
+ }
+ break;
+ case FZ_MOVETO:
+ case FZ_LINETO:
+ fz_transform_point((fz_point *)(void *)&path->items[i].v, ctm);
+ i += 2;
+ break;
+ case FZ_CLOSE_PATH:
+ break;
+ }
+ }
+}
+
+#ifndef NDEBUG
+void
+fz_print_path(fz_context *ctx, FILE *out, fz_path *path, int indent)
+{
+ float x, y;
+ int i = 0;
+ int n;
+ while (i < path->len)
+ {
+ for (n = 0; n < indent; n++)
+ fputc(' ', out);
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g m\n", x, y);
+ break;
+ case FZ_LINETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g l\n", x, y);
+ break;
+ case FZ_CURVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g c\n", x, y);
+ break;
+ case FZ_CLOSE_PATH:
+ fprintf(out, "h\n");
+ break;
+ }
+ }
+}
+#endif
+
+fz_stroke_state *
+fz_keep_stroke_state(fz_context *ctx, fz_stroke_state *stroke)
+{
+ if (!stroke)
+ return NULL;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (stroke->refs > 0)
+ stroke->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return stroke;
+}
+
+void
+fz_drop_stroke_state(fz_context *ctx, fz_stroke_state *stroke)
+{
+ int drop;
+
+ if (!stroke)
+ return;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = (stroke->refs > 0 ? --stroke->refs == 0 : 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ fz_free(ctx, stroke);
+}
+
+fz_stroke_state *
+fz_new_stroke_state_with_len(fz_context *ctx, int len)
+{
+ fz_stroke_state *state;
+
+ len -= nelem(state->dash_list);
+ if (len < 0)
+ len = 0;
+
+ state = Memento_label(fz_malloc(ctx, sizeof(*state) + sizeof(state->dash_list[0]) * len), "fz_stroke_state");
+ state->refs = 1;
+ state->start_cap = FZ_LINECAP_BUTT;
+ state->dash_cap = FZ_LINECAP_BUTT;
+ state->end_cap = FZ_LINECAP_BUTT;
+ state->linejoin = FZ_LINEJOIN_MITER;
+ state->linewidth = 1;
+ state->miterlimit = 10;
+ state->dash_phase = 0;
+ state->dash_len = 0;
+ memset(state->dash_list, 0, sizeof(state->dash_list[0]) * (len + nelem(state->dash_list)));
+
+ return state;
+}
+
+fz_stroke_state *
+fz_new_stroke_state(fz_context *ctx)
+{
+ return fz_new_stroke_state_with_len(ctx, 0);
+}
+
+fz_stroke_state *
+fz_unshare_stroke_state_with_len(fz_context *ctx, fz_stroke_state *shared, int len)
+{
+ int single, unsize, shsize, shlen, drop;
+ fz_stroke_state *unshared;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ single = (shared->refs == 1);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ shlen = shared->dash_len - nelem(shared->dash_list);
+ if (shlen < 0)
+ shlen = 0;
+ shsize = sizeof(*shared) + sizeof(shared->dash_list[0]) * shlen;
+ len -= nelem(shared->dash_list);
+ if (len < 0)
+ len = 0;
+ if (single && shlen >= len)
+ return shared;
+ unsize = sizeof(*unshared) + sizeof(unshared->dash_list[0]) * len;
+ unshared = Memento_label(fz_malloc(ctx, unsize), "fz_stroke_state");
+ memcpy(unshared, shared, (shsize > unsize ? unsize : shsize));
+ unshared->refs = 1;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = (shared->refs > 0 ? --shared->refs == 0 : 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ fz_free(ctx, shared);
+ return unshared;
+}
+
+fz_stroke_state *
+fz_unshare_stroke_state(fz_context *ctx, fz_stroke_state *shared)
+{
+ return fz_unshare_stroke_state_with_len(ctx, shared, shared->dash_len);
+}