Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/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/path.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1898 @@ +// 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 <string.h> +#include <assert.h> + +// Thoughts for further optimisations: +// All paths start with MoveTo. We could probably avoid most cases where +// we store that. The next thing after a close must be a move. +// Commands are MOVE, LINE, HORIZ, VERT, DEGEN, CURVE, CURVEV, CURVEY, QUAD, RECT. +// We'd need to drop 2 to get us down to 3 bits. +// Commands can be followed by CLOSE. Use 1 bit for close. +// PDF 'RECT' implies close according to the spec, but I suspect +// we can ignore this as filling closes implicitly. +// We use a single bit in the path header to tell us whether we have +// a trailing move. Trailing moves can always be stripped when path +// construction completes. + +typedef enum +{ + FZ_MOVETO = 'M', + FZ_LINETO = 'L', + FZ_DEGENLINETO = 'D', + FZ_CURVETO = 'C', + FZ_CURVETOV = 'V', + FZ_CURVETOY = 'Y', + FZ_HORIZTO = 'H', + FZ_VERTTO = 'I', + FZ_QUADTO = 'Q', + FZ_RECTTO = 'R', + FZ_MOVETOCLOSE = 'm', + FZ_LINETOCLOSE = 'l', + FZ_DEGENLINETOCLOSE = 'd', + FZ_CURVETOCLOSE = 'c', + FZ_CURVETOVCLOSE = 'v', + FZ_CURVETOYCLOSE = 'y', + FZ_HORIZTOCLOSE = 'h', + FZ_VERTTOCLOSE = 'i', + FZ_QUADTOCLOSE = 'q', +} fz_path_item_kind; + +struct fz_path +{ + int8_t refs; + uint8_t packed; + int cmd_len, cmd_cap; + unsigned char *cmds; + int coord_len, coord_cap; + float *coords; + fz_point current; + fz_point begin; +}; + +typedef struct +{ + int8_t refs; + uint8_t packed; + uint8_t coord_len; + uint8_t cmd_len; +} fz_packed_path; + +/* + Paths are created UNPACKED. That means we have a fz_path + structure with coords and cmds pointing to malloced blocks. + + After they have been completely constructed, callers may choose + to 'pack' them into some target block of memory. If if coord_len + and cmd_len are both < 256, then they are PACKED_FLAT into an + fz_packed_path with the coords and cmds in the bytes afterwards, + all inside the target block. If they cannot be accommodated in + that way, then they are PACKED_OPEN, where an fz_path is put + into the target block, and cmds and coords remain pointers to + allocated blocks. +*/ +enum +{ + FZ_PATH_UNPACKED = 0, + FZ_PATH_PACKED_FLAT = 1, + FZ_PATH_PACKED_OPEN = 2 +}; + +#define LAST_CMD(path) ((path)->cmd_len > 0 ? (path)->cmds[(path)->cmd_len-1] : 0) + +fz_path * +fz_new_path(fz_context *ctx) +{ + fz_path *path; + + path = fz_malloc_struct(ctx, fz_path); + path->refs = 1; + path->packed = FZ_PATH_UNPACKED; + path->current.x = 0; + path->current.y = 0; + path->begin.x = 0; + path->begin.y = 0; + + return path; +} + +/* + Take an additional reference to + a path. + + No modifications should be carried out on a path + to which more than one reference is held, as + this can cause race conditions. +*/ +fz_path * +fz_keep_path(fz_context *ctx, const fz_path *pathc) +{ + fz_path *path = (fz_path *)pathc; /* Explicit cast away of const */ + int trimmable = 0; + + if (path == NULL) + return NULL; + fz_lock(ctx, FZ_LOCK_ALLOC); + /* Technically, we should only access ->refs with the lock held, + * so do that here. We can't actually do the trimming here, because + * to do so would do memory accesses with the ALLOC lock held. */ + if (path->refs == 1 && path->packed == FZ_PATH_UNPACKED) + trimmable = 1; + fz_keep_imp8_locked(ctx, path, &path->refs); + fz_unlock(ctx, FZ_LOCK_ALLOC); + + /* This is thread safe, because we know that the only person + * holding a reference to this thread is us. */ + if (trimmable) + fz_trim_path(ctx, path); + + return path; +} + +void +fz_drop_path(fz_context *ctx, const fz_path *pathc) +{ + fz_path *path = (fz_path *)pathc; /* Explicit cast away of const */ + + if (fz_drop_imp8(ctx, path, &path->refs)) + { + if (path->packed != FZ_PATH_PACKED_FLAT) + { + fz_free(ctx, path->cmds); + fz_free(ctx, path->coords); + } + if (path->packed == FZ_PATH_UNPACKED) + fz_free(ctx, path); + } +} + +int fz_packed_path_size(const fz_path *path) +{ + switch (path->packed) + { + case FZ_PATH_UNPACKED: + if (path->cmd_len > 255 || path->coord_len > 255) + return sizeof(fz_path); + return sizeof(fz_packed_path) + sizeof(float) * path->coord_len + sizeof(uint8_t) * path->cmd_len; + case FZ_PATH_PACKED_OPEN: + return sizeof(fz_path); + case FZ_PATH_PACKED_FLAT: + { + fz_packed_path *pack = (fz_packed_path *)path; + return sizeof(fz_packed_path) + sizeof(float) * pack->coord_len + sizeof(uint8_t) * pack->cmd_len; + } + default: + assert("This never happens" == NULL); + return 0; + } +} + +size_t +fz_pack_path(fz_context *ctx, uint8_t *pack_, const fz_path *path) +{ + uint8_t *ptr; + size_t size; + + if (path->packed == FZ_PATH_PACKED_FLAT) + { + fz_packed_path *pack = (fz_packed_path *)path; + fz_packed_path *out = (fz_packed_path *)pack_; + size = sizeof(fz_packed_path) + sizeof(float) * pack->coord_len + sizeof(uint8_t) * pack->cmd_len; + + if (out) + { + out->refs = 1; + out->packed = FZ_PATH_PACKED_FLAT; + out->coord_len = pack->coord_len; + out->cmd_len = pack->cmd_len; + memcpy(&out[1], &pack[1], size - sizeof(*out)); + } + return size; + } + + size = sizeof(fz_packed_path) + sizeof(float) * path->coord_len + sizeof(uint8_t) * path->cmd_len; + + /* If the path can't be packed flat, then pack it open */ + if (path->cmd_len > 255 || path->coord_len > 255) + { + fz_path *pack = (fz_path *)pack_; + + if (pack != NULL) + { + pack->refs = 1; + pack->packed = FZ_PATH_PACKED_OPEN; + pack->current.x = 0; + pack->current.y = 0; + pack->begin.x = 0; + pack->begin.y = 0; + pack->coord_cap = path->coord_len; + pack->coord_len = path->coord_len; + pack->cmd_cap = path->cmd_len; + pack->cmd_len = path->cmd_len; + pack->coords = Memento_label(fz_malloc_array(ctx, path->coord_len, float), "path_packed_coords"); + fz_try(ctx) + { + pack->cmds = Memento_label(fz_malloc_array(ctx, path->cmd_len, uint8_t), "path_packed_cmds"); + } + fz_catch(ctx) + { + fz_free(ctx, pack->coords); + fz_rethrow(ctx); + } + memcpy(pack->coords, path->coords, sizeof(float) * path->coord_len); + memcpy(pack->cmds, path->cmds, sizeof(uint8_t) * path->cmd_len); + } + return sizeof(fz_path); + } + else + { + fz_packed_path *pack = (fz_packed_path *)pack_; + + if (pack != NULL) + { + pack->refs = 1; + pack->packed = FZ_PATH_PACKED_FLAT; + pack->cmd_len = path->cmd_len; + pack->coord_len = path->coord_len; + ptr = (uint8_t *)&pack[1]; + memcpy(ptr, path->coords, sizeof(float) * path->coord_len); + ptr += sizeof(float) * path->coord_len; + memcpy(ptr, path->cmds, sizeof(uint8_t) * path->cmd_len); + } + + return size; + } +} + +static void +push_cmd(fz_context *ctx, fz_path *path, int cmd) +{ + if (path->refs != 1) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot modify shared paths"); + + if (path->cmd_len + 1 >= path->cmd_cap) + { + int new_cmd_cap = fz_maxi(16, path->cmd_cap * 2); + path->cmds = fz_realloc_array(ctx, path->cmds, new_cmd_cap, unsigned char); + path->cmd_cap = new_cmd_cap; + } + + path->cmds[path->cmd_len++] = cmd; +} + +static void +push_coord(fz_context *ctx, fz_path *path, float x, float y) +{ + if (path->coord_len + 2 >= path->coord_cap) + { + int new_coord_cap = fz_maxi(32, path->coord_cap * 2); + path->coords = fz_realloc_array(ctx, path->coords, new_coord_cap, float); + path->coord_cap = new_coord_cap; + } + + path->coords[path->coord_len++] = x; + path->coords[path->coord_len++] = y; + + path->current.x = x; + path->current.y = y; +} + +static void +push_ord(fz_context *ctx, fz_path *path, float xy, int isx) +{ + if (path->coord_len + 1 >= path->coord_cap) + { + int new_coord_cap = fz_maxi(32, path->coord_cap * 2); + path->coords = fz_realloc_array(ctx, path->coords, new_coord_cap, float); + path->coord_cap = new_coord_cap; + } + + path->coords[path->coord_len++] = xy; + + if (isx) + path->current.x = xy; + else + path->current.y = xy; +} + +fz_point +fz_currentpoint(fz_context *ctx, fz_path *path) +{ + return path->current; +} + +void +fz_moveto(fz_context *ctx, fz_path *path, float x, float y) +{ + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + if (path->cmd_len > 0 && LAST_CMD(path) == FZ_MOVETO) + { + /* Collapse moveto followed by moveto. */ + path->coords[path->coord_len-2] = x; + path->coords[path->coord_len-1] = y; + path->current.x = x; + path->current.y = y; + path->begin = path->current; + return; + } + + push_cmd(ctx, path, FZ_MOVETO); + push_coord(ctx, path, x, y); + + path->begin = path->current; +} + +void +fz_lineto(fz_context *ctx, fz_path *path, float x, float y) +{ + float x0, y0; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + x0 = path->current.x; + y0 = path->current.y; + + if (path->cmd_len == 0) + { + fz_warn(ctx, "lineto with no current point"); + return; + } + + /* (Anything other than MoveTo) followed by (LineTo the same place) is a nop */ + if (LAST_CMD(path) != FZ_MOVETO && x0 == x && y0 == y) + return; + + if (x0 == x) + { + if (y0 == y) + { + if (LAST_CMD(path) != FZ_MOVETO) + return; + push_cmd(ctx, path, FZ_DEGENLINETO); + } + else + { + push_cmd(ctx, path, FZ_VERTTO); + push_ord(ctx, path, y, 0); + } + } + else if (y0 == y) + { + push_cmd(ctx, path, FZ_HORIZTO); + push_ord(ctx, path, x, 1); + } + else + { + push_cmd(ctx, path, FZ_LINETO); + push_coord(ctx, path, x, 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->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + x0 = path->current.x; + y0 = path->current.y; + + if (path->cmd_len == 0) + { + fz_warn(ctx, "curveto with no current point"); + return; + } + + /* 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 && LAST_CMD(path) != FZ_MOVETO) + return; + /* Otherwise a line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + else if (x1 == x2 && y1 == y2) + { + /* A line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + else + fz_curvetov(ctx, path, x2, y2, x3, y3); + return; + } + else if (x2 == x3 && y2 == y3) + { + if (x1 == x2 && y1 == y2) + { + /* A line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + else + fz_curvetoy(ctx, path, x1, y1, x3, y3); + return; + } + + push_cmd(ctx, path, FZ_CURVETO); + push_coord(ctx, path, x1, y1); + push_coord(ctx, path, x2, y2); + push_coord(ctx, path, x3, y3); +} + +void +fz_quadto(fz_context *ctx, fz_path *path, + float x1, float y1, + float x2, float y2) +{ + float x0, y0; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + x0 = path->current.x; + y0 = path->current.y; + + if (path->cmd_len == 0) + { + fz_warn(ctx, "quadto with no current point"); + return; + } + + /* Check for degenerate cases: */ + if ((x0 == x1 && y0 == y1) || (x1 == x2 && y1 == y2)) + { + if (x0 == x2 && y0 == y2 && LAST_CMD(path) != FZ_MOVETO) + return; + /* A line will suffice */ + fz_lineto(ctx, path, x2, y2); + return; + } + + push_cmd(ctx, path, FZ_QUADTO); + push_coord(ctx, path, x1, y1); + push_coord(ctx, path, x2, y2); +} + +void +fz_curvetov(fz_context *ctx, fz_path *path, float x2, float y2, float x3, float y3) +{ + float x0, y0; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + x0 = path->current.x; + y0 = path->current.y; + + if (path->cmd_len == 0) + { + fz_warn(ctx, "curveto with no current point"); + return; + } + + /* Check for degenerate cases: */ + if (x2 == x3 && y2 == y3) + { + /* If (x0,y0)==(x2,y2) and prev wasn't a moveto, then skip */ + if (x0 == x2 && y0 == y2 && LAST_CMD(path) != FZ_MOVETO) + return; + /* Otherwise a line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + else if (x0 == x2 && y0 == y2) + { + /* A line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + + push_cmd(ctx, path, FZ_CURVETOV); + push_coord(ctx, path, x2, y2); + push_coord(ctx, path, x3, y3); +} + +void +fz_curvetoy(fz_context *ctx, fz_path *path, float x1, float y1, float x3, float y3) +{ + float x0, y0; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + x0 = path->current.x; + y0 = path->current.y; + + if (path->cmd_len == 0) + { + fz_warn(ctx, "curveto with no current point"); + return; + } + + /* Check for degenerate cases: */ + if (x1 == x3 && y1 == y3) + { + /* If (x0,y0)==(x1,y1) and prev wasn't a moveto, then skip */ + if (x0 == x1 && y0 == y1 && LAST_CMD(path) != FZ_MOVETO) + return; + /* Otherwise a line will suffice */ + fz_lineto(ctx, path, x3, y3); + } + + push_cmd(ctx, path, FZ_CURVETOY); + push_coord(ctx, path, x1, y1); + push_coord(ctx, path, x3, y3); +} + +void +fz_closepath(fz_context *ctx, fz_path *path) +{ + uint8_t rep; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + if (path->cmd_len == 0) + { + fz_warn(ctx, "closepath with no current point"); + return; + } + + switch(LAST_CMD(path)) + { + case FZ_MOVETO: + rep = FZ_MOVETOCLOSE; + break; + case FZ_LINETO: + rep = FZ_LINETOCLOSE; + break; + case FZ_DEGENLINETO: + rep = FZ_DEGENLINETOCLOSE; + break; + case FZ_CURVETO: + rep = FZ_CURVETOCLOSE; + break; + case FZ_CURVETOV: + rep = FZ_CURVETOVCLOSE; + break; + case FZ_CURVETOY: + rep = FZ_CURVETOYCLOSE; + break; + case FZ_HORIZTO: + rep = FZ_HORIZTOCLOSE; + break; + case FZ_VERTTO: + rep = FZ_VERTTOCLOSE; + break; + case FZ_QUADTO: + rep = FZ_QUADTOCLOSE; + break; + case FZ_RECTTO: + /* RectTo implies close */ + return; + case FZ_MOVETOCLOSE: + case FZ_LINETOCLOSE: + case FZ_DEGENLINETOCLOSE: + case FZ_CURVETOCLOSE: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + case FZ_QUADTOCLOSE: + /* CLOSE following a CLOSE is a NOP */ + return; + default: /* default never happens */ + case 0: + /* Closing an empty path is a NOP */ + return; + } + + path->cmds[path->cmd_len-1] = rep; + + path->current = path->begin; +} + +void +fz_rectto(fz_context *ctx, fz_path *path, float x1, float y1, float x2, float y2) +{ + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot modify a packed path"); + + if (path->cmd_len > 0 && LAST_CMD(path) == FZ_MOVETO) + { + /* Collapse moveto followed by rectto. */ + path->coord_len -= 2; + path->cmd_len--; + } + + push_cmd(ctx, path, FZ_RECTTO); + push_coord(ctx, path, x1, y1); + push_coord(ctx, path, x2, y2); + + path->current = path->begin; +} + +static inline void bound_expand(fz_rect *r, 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; +} + +void fz_walk_path(fz_context *ctx, const fz_path *path, const fz_path_walker *proc, void *arg) +{ + int i, k, cmd_len; + float x=0, y=0, sx=0, sy=0; + uint8_t *cmds; + float *coords; + + switch (path->packed) + { + case FZ_PATH_UNPACKED: + case FZ_PATH_PACKED_OPEN: + cmd_len = path->cmd_len; + coords = path->coords; + cmds = path->cmds; + break; + case FZ_PATH_PACKED_FLAT: + cmd_len = ((fz_packed_path *)path)->cmd_len; + coords = (float *)&((fz_packed_path *)path)[1]; + cmds = (uint8_t *)&coords[((fz_packed_path *)path)->coord_len]; + break; + default: + assert("This never happens" == NULL); + return; + } + + if (cmd_len == 0) + return; + + for (k=0, i = 0; i < cmd_len; i++) + { + uint8_t cmd = cmds[i]; + + switch (cmd) + { + case FZ_CURVETO: + case FZ_CURVETOCLOSE: + proc->curveto(ctx, arg, + coords[k], + coords[k+1], + coords[k+2], + coords[k+3], + x = coords[k+4], + y = coords[k+5]); + k += 6; + if (cmd == FZ_CURVETOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_CURVETOV: + case FZ_CURVETOVCLOSE: + if (proc->curvetov) + proc->curvetov(ctx, arg, + coords[k], + coords[k+1], + x = coords[k+2], + y = coords[k+3]); + else + { + proc->curveto(ctx, arg, + x, + y, + coords[k], + coords[k+1], + coords[k+2], + coords[k+3]); + x = coords[k+2]; + y = coords[k+3]; + } + k += 4; + if (cmd == FZ_CURVETOVCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_CURVETOY: + case FZ_CURVETOYCLOSE: + if (proc->curvetoy) + proc->curvetoy(ctx, arg, + coords[k], + coords[k+1], + x = coords[k+2], + y = coords[k+3]); + else + proc->curveto(ctx, arg, + coords[k], + coords[k+1], + coords[k+2], + coords[k+3], + x = coords[k+2], + y = coords[k+3]); + k += 4; + if (cmd == FZ_CURVETOYCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_QUADTO: + case FZ_QUADTOCLOSE: + if (proc->quadto) + proc->quadto(ctx, arg, + coords[k], + coords[k+1], + x = coords[k+2], + y = coords[k+3]); + else + { + float c2x = coords[k] * 2; + float c2y = coords[k+1] * 2; + float c1x = (x + c2x) / 3; + float c1y = (y + c2y) / 3; + x = coords[k+2]; + y = coords[k+3]; + c2x = (c2x + x) / 3; + c2y = (c2y + y) / 3; + + proc->curveto(ctx, arg, + c1x, + c1y, + c2x, + c2y, + x, + y); + } + k += 4; + if (cmd == FZ_QUADTOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_MOVETO: + case FZ_MOVETOCLOSE: + proc->moveto(ctx, arg, + x = coords[k], + y = coords[k+1]); + k += 2; + sx = x; + sy = y; + if (cmd == FZ_MOVETOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_LINETO: + case FZ_LINETOCLOSE: + proc->lineto(ctx, arg, + x = coords[k], + y = coords[k+1]); + k += 2; + if (cmd == FZ_LINETOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_HORIZTO: + case FZ_HORIZTOCLOSE: + proc->lineto(ctx, arg, + x = coords[k], + y); + k += 1; + if (cmd == FZ_HORIZTOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_VERTTO: + case FZ_VERTTOCLOSE: + proc->lineto(ctx, arg, + x, + y = coords[k]); + k += 1; + if (cmd == FZ_VERTTOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_DEGENLINETO: + case FZ_DEGENLINETOCLOSE: + proc->lineto(ctx, arg, + x, + y); + if (cmd == FZ_DEGENLINETOCLOSE) + { + if (proc->closepath) + proc->closepath(ctx, arg); + x = sx; + y = sy; + } + break; + case FZ_RECTTO: + if (proc->rectto) + { + proc->rectto(ctx, arg, + x = coords[k], + y = coords[k+1], + coords[k+2], + coords[k+3]); + } + else + { + proc->moveto(ctx, arg, + x = coords[k], + y = coords[k+1]); + proc->lineto(ctx, arg, + coords[k+2], + coords[k+1]); + proc->lineto(ctx, arg, + coords[k+2], + coords[k+3]); + proc->lineto(ctx, arg, + coords[k], + coords[k+3]); + if (proc->closepath) + proc->closepath(ctx, arg); + } + sx = x; + sy = y; + k += 4; + break; + } + } +} + +/* + A couple of notes about the path bounding algorithm. + + Firstly, we don't expand the bounds immediately on a move, because + a sequence of moves together will only actually use the last one, + and trailing moves are ignored. This is achieved using 'trailing_move'. + + Secondly, we watch for paths that are entirely rectilinear (all segments + move left/right/up/down only, with no curves). Such "only_right_angles" + paths can be bounded with us ignoring any mitre limit. This is a really + common case that can otherwise bloats simple boxes far more than is + useful. This is particular annoying during table recognition! +*/ +typedef struct +{ + fz_matrix ctm; + fz_rect rect; + fz_point move; + int trailing_move; + int first; + int only_right_angles; + fz_point prev; +} bound_path_arg; + +static void +bound_moveto(fz_context *ctx, void *arg_, float x, float y) +{ + bound_path_arg *arg = (bound_path_arg *)arg_; + arg->move = arg->prev = fz_transform_point_xy(x, y, arg->ctm); + arg->trailing_move = 1; +} + +static inline int +eq0(float x) +{ + return x >= -0.001 && x <= 0.001; +} + +static void +bound_lineto(fz_context *ctx, void *arg_, float x, float y) +{ + bound_path_arg *arg = (bound_path_arg *)arg_; + fz_point p = fz_transform_point_xy(x, y, arg->ctm); + if (arg->first) + { + arg->rect.x0 = arg->rect.x1 = p.x; + arg->rect.y0 = arg->rect.y1 = p.y; + arg->first = 0; + } + else + bound_expand(&arg->rect, p); + if (arg->trailing_move) + { + arg->trailing_move = 0; + bound_expand(&arg->rect, arg->move); + } + if (arg->only_right_angles && !eq0(arg->prev.x - p.x) && !eq0(arg->prev.y - p.y)) + arg->only_right_angles = 0; + arg->prev = p; +} + +static void +bound_curveto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + bound_path_arg *arg = (bound_path_arg *)arg_; + fz_point p = fz_transform_point_xy(x1, y1, arg->ctm); + if (arg->first) + { + arg->rect.x0 = arg->rect.x1 = p.x; + arg->rect.y0 = arg->rect.y1 = p.y; + arg->first = 0; + } + else + bound_expand(&arg->rect, p); + bound_expand(&arg->rect, fz_transform_point_xy(x2, y2, arg->ctm)); + bound_expand(&arg->rect, fz_transform_point_xy(x3, y3, arg->ctm)); + if (arg->trailing_move) + { + arg->trailing_move = 0; + bound_expand(&arg->rect, arg->move); + } + arg->only_right_angles = 0; + arg->prev = p; +} + +static const fz_path_walker bound_path_walker = +{ + bound_moveto, + bound_lineto, + bound_curveto, + NULL +}; + +static fz_rect +adjust_rect_for_stroke(fz_context *ctx, fz_rect r, const fz_stroke_state *stroke, fz_matrix ctm, int no_mitre) +{ + float expand; + + if (!stroke) + return r; + + expand = stroke->linewidth/2; + if (expand == 0) + expand = 0.5f; + if (r.x1 == r.x0 || r.y1 == r.y0) + { + /* Mitring can't apply in this case. */ + } + else if (!no_mitre && stroke->linejoin == FZ_LINEJOIN_MITER && stroke->miterlimit > 0.5f) + { + /* miter limit is expressed in terms of the linewidth, not half the line width. */ + expand *= stroke->miterlimit * 2; + } + else if (!no_mitre && stroke->linejoin == FZ_LINEJOIN_MITER_XPS && stroke->miterlimit > 1.0f) + { + /* for xps, miter limit is expressed in terms of half the linewidth. */ + expand *= stroke->miterlimit; + } + + expand *= fz_matrix_max_expansion(ctm); + + r.x0 -= expand; + r.y0 -= expand; + r.x1 += expand; + r.y1 += expand; + return r; +} + +fz_rect +fz_bound_path(fz_context *ctx, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm) +{ + bound_path_arg arg; + + arg.ctm = ctm; + arg.rect = fz_empty_rect; + arg.trailing_move = 0; + arg.first = 1; + arg.only_right_angles = 1; + + fz_walk_path(ctx, path, &bound_path_walker, &arg); + + if (!arg.first && stroke) + { + arg.rect = adjust_rect_for_stroke(ctx, arg.rect, stroke, ctm, arg.only_right_angles); + } + + return arg.rect; +} + +fz_rect +fz_adjust_rect_for_stroke(fz_context *ctx, fz_rect r, const fz_stroke_state *stroke, fz_matrix ctm) +{ + return adjust_rect_for_stroke(ctx, r, stroke, ctm, 0); +} + +void +fz_transform_path(fz_context *ctx, fz_path *path, fz_matrix ctm) +{ + int i, k, n; + fz_point p, p1, p2, p3, q, s; + + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot transform a packed path"); + + if (ctm.b == 0 && ctm.c == 0) + { + /* Simple, in place transform */ + i = 0; + k = 0; + while (i < path->cmd_len) + { + uint8_t cmd = path->cmds[i]; + + switch (cmd) + { + case FZ_MOVETO: + case FZ_LINETO: + case FZ_MOVETOCLOSE: + case FZ_LINETOCLOSE: + n = 1; + break; + case FZ_DEGENLINETO: + case FZ_DEGENLINETOCLOSE: + n = 0; + break; + case FZ_CURVETO: + case FZ_CURVETOCLOSE: + n = 3; + break; + case FZ_RECTTO: + s.x = path->coords[k]; + s.y = path->coords[k+1]; + n = 2; + break; + case FZ_CURVETOV: + case FZ_CURVETOY: + case FZ_QUADTO: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + n = 2; + break; + case FZ_HORIZTO: + case FZ_HORIZTOCLOSE: + q.x = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.x; + n = 0; + break; + case FZ_VERTTO: + case FZ_VERTTOCLOSE: + q.y = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.y; + n = 0; + break; + default: + assert("Unknown path cmd" == NULL); + } + while (n > 0) + { + q.x = path->coords[k]; + q.y = path->coords[k+1]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.x; + path->coords[k++] = p.y; + n--; + } + switch (cmd) + { + case FZ_MOVETO: + case FZ_MOVETOCLOSE: + s = q; + break; + case FZ_LINETOCLOSE: + case FZ_DEGENLINETOCLOSE: + case FZ_CURVETOCLOSE: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + case FZ_RECTTO: + q = s; + break; + } + i++; + } + } + else if (ctm.a == 0 && ctm.d == 0) + { + /* In place transform with command rewriting */ + i = 0; + k = 0; + while (i < path->cmd_len) + { + uint8_t cmd = path->cmds[i]; + + switch (cmd) + { + case FZ_MOVETO: + case FZ_LINETO: + case FZ_MOVETOCLOSE: + case FZ_LINETOCLOSE: + n = 1; + break; + case FZ_DEGENLINETO: + case FZ_DEGENLINETOCLOSE: + n = 0; + break; + case FZ_CURVETO: + case FZ_CURVETOCLOSE: + n = 3; + break; + case FZ_RECTTO: + s.x = path->coords[k]; + s.y = path->coords[k+1]; + n = 2; + break; + case FZ_CURVETOV: + case FZ_CURVETOY: + case FZ_QUADTO: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + n = 2; + break; + case FZ_HORIZTO: + q.x = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.y; + path->cmds[i] = FZ_VERTTO; + n = 0; + break; + case FZ_HORIZTOCLOSE: + q.x = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.y; + path->cmds[i] = FZ_VERTTOCLOSE; + n = 0; + break; + case FZ_VERTTO: + q.y = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.x; + path->cmds[i] = FZ_HORIZTO; + n = 0; + break; + case FZ_VERTTOCLOSE: + q.y = path->coords[k]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.x; + path->cmds[i] = FZ_HORIZTOCLOSE; + n = 0; + break; + default: + assert("Unknown path cmd" == NULL); + } + while (n > 0) + { + q.x = path->coords[k]; + q.y = path->coords[k+1]; + p = fz_transform_point(q, ctm); + path->coords[k++] = p.x; + path->coords[k++] = p.y; + n--; + } + switch (cmd) + { + case FZ_MOVETO: + case FZ_MOVETOCLOSE: + s = q; + break; + case FZ_LINETOCLOSE: + case FZ_DEGENLINETOCLOSE: + case FZ_CURVETOCLOSE: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + case FZ_RECTTO: + q = s; + break; + } + i++; + } + } + else + { + int extra_coord = 0; + int extra_cmd = 0; + int coord_read, coord_write, cmd_read, cmd_write; + + /* General case. Have to allow for rects/horiz/verts + * becoming non-rects/horiz/verts. */ + for (i = 0; i < path->cmd_len; i++) + { + uint8_t cmd = path->cmds[i]; + switch (cmd) + { + case FZ_HORIZTO: + case FZ_VERTTO: + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + extra_coord += 1; + break; + case FZ_RECTTO: + extra_coord += 2; + extra_cmd += 3; + break; + default: + /* Do nothing */ + break; + } + } + if (path->cmd_len + extra_cmd < path->cmd_cap) + { + path->cmds = fz_realloc_array(ctx, path->cmds, path->cmd_len + extra_cmd, unsigned char); + path->cmd_cap = path->cmd_len + extra_cmd; + } + if (path->coord_len + extra_coord < path->coord_cap) + { + path->coords = fz_realloc_array(ctx, path->coords, path->coord_len + extra_coord, float); + path->coord_cap = path->coord_len + extra_coord; + } + memmove(path->cmds + extra_cmd, path->cmds, path->cmd_len * sizeof(unsigned char)); + path->cmd_len += extra_cmd; + memmove(path->coords + extra_coord, path->coords, path->coord_len * sizeof(float)); + path->coord_len += extra_coord; + + for (cmd_write = 0, cmd_read = extra_cmd, coord_write = 0, coord_read = extra_coord; cmd_read < path->cmd_len; i += 2) + { + uint8_t cmd = path->cmds[cmd_write++] = path->cmds[cmd_read++]; + + switch (cmd) + { + case FZ_MOVETO: + case FZ_LINETO: + case FZ_MOVETOCLOSE: + case FZ_LINETOCLOSE: + n = 1; + break; + case FZ_DEGENLINETO: + case FZ_DEGENLINETOCLOSE: + n = 0; + break; + case FZ_CURVETO: + case FZ_CURVETOCLOSE: + n = 3; + break; + case FZ_CURVETOV: + case FZ_CURVETOY: + case FZ_QUADTO: + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + n = 2; + break; + case FZ_RECTTO: + p.x = path->coords[coord_read++]; + p.y = path->coords[coord_read++]; + p2.x = path->coords[coord_read++]; + p2.y = path->coords[coord_read++]; + p1.x = p2.x; + p1.y = p.y; + p3.x = p.x; + p3.y = p2.y; + s = p; + p = fz_transform_point(p, ctm); + p1 = fz_transform_point(p1, ctm); + p2 = fz_transform_point(p2, ctm); + p3 = fz_transform_point(p3, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + path->coords[coord_write++] = p1.x; + path->coords[coord_write++] = p1.y; + path->coords[coord_write++] = p2.x; + path->coords[coord_write++] = p2.y; + path->coords[coord_write++] = p3.x; + path->coords[coord_write++] = p3.y; + path->cmds[cmd_write-1] = FZ_MOVETO; + path->cmds[cmd_write++] = FZ_LINETO; + path->cmds[cmd_write++] = FZ_LINETO; + path->cmds[cmd_write++] = FZ_LINETOCLOSE; + n = 0; + break; + case FZ_HORIZTO: + q.x = path->coords[coord_read++]; + p = fz_transform_point(q, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + path->cmds[cmd_write-1] = FZ_LINETO; + n = 0; + break; + case FZ_HORIZTOCLOSE: + p.x = path->coords[coord_read++]; + p.y = q.y; + p = fz_transform_point(p, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + path->cmds[cmd_write-1] = FZ_LINETOCLOSE; + q = s; + n = 0; + break; + case FZ_VERTTO: + q.y = path->coords[coord_read++]; + p = fz_transform_point(q, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + path->cmds[cmd_write-1] = FZ_LINETO; + n = 0; + break; + case FZ_VERTTOCLOSE: + p.x = q.x; + p.y = path->coords[coord_read++]; + p = fz_transform_point(p, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + path->cmds[cmd_write-1] = FZ_LINETOCLOSE; + q = s; + n = 0; + break; + default: + assert("Unknown path cmd" == NULL); + } + while (n > 0) + { + q.x = path->coords[coord_read++]; + q.y = path->coords[coord_read++]; + p = fz_transform_point(q, ctm); + path->coords[coord_write++] = p.x; + path->coords[coord_write++] = p.y; + n--; + } + switch (cmd) + { + case FZ_MOVETO: + case FZ_MOVETOCLOSE: + s = q; + break; + case FZ_LINETOCLOSE: + case FZ_DEGENLINETOCLOSE: + case FZ_CURVETOCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_CURVETOVCLOSE: + case FZ_QUADTOCLOSE: + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + case FZ_RECTTO: + q = s; + break; + } + } + } +} + +void fz_trim_path(fz_context *ctx, fz_path *path) +{ + if (path->packed) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Can't trim a packed path"); + if (path->cmd_cap > path->cmd_len) + { + path->cmds = fz_realloc_array(ctx, path->cmds, path->cmd_len, unsigned char); + path->cmd_cap = path->cmd_len; + } + if (path->coord_cap > path->coord_len) + { + path->coords = fz_realloc_array(ctx, path->coords, path->coord_len, float); + path->coord_cap = path->coord_len; + } +} + +const fz_stroke_state fz_default_stroke_state = { + -2, /* -2 is the magic number we use when we have stroke states stored on the stack */ + FZ_LINECAP_BUTT, FZ_LINECAP_BUTT, FZ_LINECAP_BUTT, + FZ_LINEJOIN_MITER, + 1, 10, + 0, 0, { 0 } +}; + +fz_stroke_state * +fz_keep_stroke_state(fz_context *ctx, const fz_stroke_state *strokec) +{ + fz_stroke_state *stroke = (fz_stroke_state *)strokec; /* Explicit cast away of const */ + + if (!stroke) + return NULL; + + /* -2 is the magic number we use when we have stroke states stored on the stack */ + if (stroke->refs == -2) + return fz_clone_stroke_state(ctx, stroke); + + return fz_keep_imp(ctx, stroke, &stroke->refs); +} + +int +fz_stroke_state_eq(fz_context *ctx, const fz_stroke_state *a, const fz_stroke_state *b) +{ + int i; + + if (a == NULL && b == NULL) return 1; + + if (a == NULL && b != NULL) return 0; + if (a != NULL && b == NULL) return 0; + + if (a->start_cap != b->start_cap) return 0; + if (a->dash_cap != b->dash_cap) return 0; + if (a->end_cap != b->end_cap) return 0; + if (a->linejoin != b->linejoin) return 0; + if (a->linewidth != b->linewidth) return 0; + if (a->miterlimit != b->miterlimit) return 0; + if (a->dash_phase != b->dash_phase) return 0; + if (a->dash_len != b->dash_len) return 0; + + for (i = 0; i < a->dash_len; i++) + if (a->dash_list[i] != b->dash_list[i]) return 0; + + return 1; +} + +void +fz_drop_stroke_state(fz_context *ctx, const fz_stroke_state *strokec) +{ + fz_stroke_state *stroke = (fz_stroke_state *)strokec; /* Explicit cast away of const */ + + if (fz_drop_imp(ctx, stroke, &stroke->refs)) + fz_free(ctx, stroke); +} + +fz_stroke_state * +fz_new_stroke_state_with_dash_len(fz_context *ctx, int len) +{ + fz_stroke_state *state; + + if (len < 0) + len = 0; + + state = fz_malloc_flexible(ctx, fz_stroke_state, dash_list, len); + 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; + + return state; +} + +fz_stroke_state * +fz_new_stroke_state(fz_context *ctx) +{ + return fz_new_stroke_state_with_dash_len(ctx, 0); +} + +fz_linecap +fz_linecap_from_string(const char *str) +{ + if (!strcmp(str, "Round")) + return FZ_LINECAP_ROUND; + if (!strcmp(str, "Square")) + return FZ_LINECAP_SQUARE; + if (!strcmp(str, "Triangle")) + return FZ_LINECAP_TRIANGLE; + return FZ_LINECAP_BUTT; +} + +const char * +fz_string_from_linecap(fz_linecap cap) +{ + switch (cap) { + default: + case FZ_LINECAP_BUTT: return "Butt"; + case FZ_LINECAP_ROUND: return "Round"; + case FZ_LINECAP_SQUARE: return "Square"; + case FZ_LINECAP_TRIANGLE: return "Triangle"; + } +} + +fz_linejoin +fz_linejoin_from_string(const char *str) +{ + if (!strcmp(str, "Round")) + return FZ_LINEJOIN_ROUND; + if (!strcmp(str, "Bevel")) + return FZ_LINEJOIN_BEVEL; + if (!strcmp(str, "MiterXPS")) + return FZ_LINEJOIN_MITER_XPS; + return FZ_LINEJOIN_MITER; +} + +const char * +fz_string_from_linejoin(fz_linejoin join) +{ + switch (join) { + default: + case FZ_LINEJOIN_MITER: return "Miter"; + case FZ_LINEJOIN_ROUND: return "Round"; + case FZ_LINEJOIN_BEVEL: return "Bevel"; + case FZ_LINEJOIN_MITER_XPS: return "MiterXPS"; + } +} + +fz_stroke_state * +fz_clone_stroke_state(fz_context *ctx, const fz_stroke_state *stroke) +{ + fz_stroke_state *clone = fz_new_stroke_state_with_dash_len(ctx, stroke->dash_len); + size_t size = offsetof(fz_stroke_state, dash_list) + sizeof(float) * stroke->dash_len; + memcpy(clone, stroke, size); + clone->refs = 1; + return clone; +} + +fz_stroke_state * +fz_unshare_stroke_state_with_dash_len(fz_context *ctx, fz_stroke_state *shared, int len) +{ + int single; + fz_stroke_state *unshared; + + fz_lock(ctx, FZ_LOCK_ALLOC); + single = (shared->refs == 1); + fz_unlock(ctx, FZ_LOCK_ALLOC); + + if (single && len == shared->dash_len) + return shared; + + unshared = fz_new_stroke_state_with_dash_len(ctx, len); + if (shared->dash_len >= len) + memcpy(unshared, shared, offsetof(fz_stroke_state, dash_list) + sizeof(float) * len); + else + memcpy(unshared, shared, offsetof(fz_stroke_state, dash_list)); + unshared->refs = 1; + unshared->dash_len = len; + + if (fz_drop_imp(ctx, shared, &shared->refs)) + 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_dash_len(ctx, shared, shared->dash_len); +} + +static void * +clone_block(fz_context *ctx, void *block, size_t len) +{ + void *target; + + if (len == 0 || block == NULL) + return NULL; + + target = fz_malloc(ctx, len); + memcpy(target, block, len); + return target; +} + +fz_path * +fz_clone_path(fz_context *ctx, fz_path *path) +{ + fz_path *new_path; + + assert(ctx != NULL); + + if (path == NULL) + return NULL; + + new_path = fz_malloc_struct(ctx, fz_path); + new_path->refs = 1; + new_path->packed = FZ_PATH_UNPACKED; + fz_try(ctx) + { + switch(path->packed) + { + case FZ_PATH_UNPACKED: + case FZ_PATH_PACKED_OPEN: + new_path->cmd_len = path->cmd_len; + new_path->cmd_cap = path->cmd_cap; + new_path->cmds = Memento_label(clone_block(ctx, path->cmds, path->cmd_cap), "path_cmds"); + new_path->coord_len = path->coord_len; + new_path->coord_cap = path->coord_cap; + new_path->coords = Memento_label(clone_block(ctx, path->coords, sizeof(float)*path->coord_cap), "path_coords"); + new_path->current = path->current; + new_path->begin = path->begin; + break; + case FZ_PATH_PACKED_FLAT: + { + uint8_t *data; + float *xy; + int i; + fz_packed_path *ppath = (fz_packed_path *)path; + + new_path->cmd_len = ppath->cmd_len; + new_path->cmd_cap = ppath->cmd_len; + new_path->coord_len = ppath->coord_len; + new_path->coord_cap = ppath->coord_len; + data = (uint8_t *)&ppath[1]; + new_path->coords = Memento_label(clone_block(ctx, data, sizeof(float)*path->coord_cap), "path_coords"); + data += sizeof(float) * path->coord_cap; + new_path->cmds = Memento_label(clone_block(ctx, data, path->cmd_cap), "path_cmds"); + xy = new_path->coords; + for (i = 0; i < new_path->cmd_len; i++) + { + switch (new_path->cmds[i]) + { + case FZ_MOVETOCLOSE: + case FZ_MOVETO: + new_path->current.x = *xy++; + new_path->current.y = *xy++; + new_path->begin.x = new_path->current.x; + new_path->begin.y = new_path->current.y; + break; + case FZ_CURVETO: + xy += 2; + /* fallthrough */ + case FZ_CURVETOV: + case FZ_CURVETOY: + case FZ_QUADTO: + /* fallthrough */ + xy += 2; + case FZ_LINETO: + new_path->current.x = *xy++; + new_path->current.y = *xy++; + break; + case FZ_DEGENLINETO: + break; + case FZ_HORIZTO: + new_path->current.x = *xy++; + break; + case FZ_VERTTO: + new_path->current.y = *xy++; + break; + case FZ_RECTTO: + xy += 2; + break; + case FZ_CURVETOCLOSE: + xy += 2; + /* fallthrough */ + case FZ_CURVETOVCLOSE: + case FZ_CURVETOYCLOSE: + case FZ_QUADTOCLOSE: + case FZ_LINETOCLOSE: + xy++; + /* fallthrough */ + case FZ_HORIZTOCLOSE: + case FZ_VERTTOCLOSE: + xy++; + /* fallthrough */ + case FZ_DEGENLINETOCLOSE: + new_path->current.x = new_path->begin.x; + new_path->current.y = new_path->begin.y; + break; + } + } + } + default: + assert(!"Unknown packing method found in path"); + } + } + fz_catch(ctx) + { + fz_free(ctx, new_path->coords); + fz_free(ctx, new_path->cmds); + fz_free(ctx, new_path); + fz_rethrow(ctx); + } + return new_path; +} + +typedef struct +{ + fz_matrix ctm; + fz_point p[4]; + int count; + int trailing_move; +} rect_path_arg; + +static void +rect_moveto(fz_context *ctx, void *arg_, float x, float y) +{ + rect_path_arg *arg = (rect_path_arg *)arg_; + fz_point p = fz_transform_point_xy(x, y, arg->ctm); + + /* If we've already decided that it's not a rectangle. Just exit. */ + if (arg->count < 0) + return; + + /* We should never get multiple successive moves, by construction. */ + + /* If we're starting out... */ + if (arg->count == 0) + { + arg->p[0] = p; + arg->count = 1; + return; + } + + /* Otherwise, any move is fine, as long as it's not followed by another line... */ + arg->trailing_move = 1; +} + +static void +rect_lineto(fz_context *ctx, void *arg_, float x, float y) +{ + rect_path_arg *arg = (rect_path_arg *)arg_; + fz_point p = fz_transform_point_xy(x, y, arg->ctm); + + /* If we've already decided that it's not a rectangle. Just exit. */ + if (arg->count < 0) + return; + + if (arg->trailing_move) + { + arg->count = -1; + return; + } + + /* Watch for pesky lines back to the same place. */ + if (arg->p[arg->count-1].x == p.x && arg->p[arg->count-1].y == p.y) + return; + + if (arg->count < 4) + { + arg->p[arg->count++] = p; + return; + } + + /* Allow for lines back to the start. */ + if (arg->count == 4) + { + if (arg->p[0].x == p.x && arg->p[0].y == p.y) + { + arg->count++; + return; + } + } + + arg->count = -1; +} + +static void +rect_curveto(fz_context *ctx, void *arg_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + rect_path_arg *arg = (rect_path_arg *)arg_; + + arg->count = -1; +} + +static const fz_path_walker rect_path_walker = +{ + rect_moveto, + rect_lineto, + rect_curveto, + NULL +}; + +int +fz_path_is_rect(fz_context *ctx, const fz_path *path, fz_matrix ctm) +{ + return fz_path_is_rect_with_bounds(ctx, path, ctm, NULL); +} + +int +fz_path_is_rect_with_bounds(fz_context *ctx, const fz_path *path, fz_matrix ctm, fz_rect *bounds) +{ + rect_path_arg arg; + + arg.ctm = ctm; + arg.trailing_move = 0; + arg.count = 0; + + fz_walk_path(ctx, path, &rect_path_walker, &arg); + + if (arg.count < 0) + return 0; + + /* 3 entries are bad, unless the last one returns the first. */ + if (arg.count == 3 && (arg.p[0].x != arg.p[2].x || arg.p[0].y != arg.p[2].y)) + { + return 0; + } + if (arg.count == 2 || arg.count == 3) + { + if (arg.p[0].x == arg.p[1].x || arg.p[0].y == arg.p[1].y) + { + if (bounds) + { + bounds->x0 = fz_min(arg.p[0].x, arg.p[1].x); + bounds->x1 = fz_max(arg.p[0].x, arg.p[1].x); + bounds->y0 = fz_min(arg.p[0].y, arg.p[1].y); + bounds->y1 = fz_max(arg.p[0].y, arg.p[1].y); + } + return 1; + } + } + /* All that's left are 4 entry ones */ + if (arg.count != 4) + return 0; + + if (arg.p[0].x == arg.p[1].x) + { + /* p[0] p[3] + * p[1] p[2] + */ + if (arg.p[1].y == arg.p[2].y && arg.p[0].y == arg.p[3].y && arg.p[2].x == arg.p[3].x) + { + if (bounds) + { + bounds->x0 = fz_min(arg.p[0].x, arg.p[3].x); + bounds->x1 = fz_max(arg.p[0].x, arg.p[3].x); + bounds->y0 = fz_min(arg.p[0].y, arg.p[1].y); + bounds->y1 = fz_max(arg.p[0].y, arg.p[1].y); + } + return 1; + } + } + if (arg.p[0].y == arg.p[1].y) + { + /* p[0] p[1] + * p[3] p[2] + */ + if (arg.p[1].x == arg.p[2].x && arg.p[0].x == arg.p[3].x && arg.p[2].y == arg.p[3].y) + { + if (bounds) + { + bounds->x0 = fz_min(arg.p[0].x, arg.p[1].x); + bounds->x1 = fz_max(arg.p[0].x, arg.p[1].x); + bounds->y0 = fz_min(arg.p[0].y, arg.p[3].y); + bounds->y1 = fz_max(arg.p[0].y, arg.p[3].y); + } + return 1; + } + } + return 0; +}
