Skip to content

Commit

Permalink
Bug 708128: Fix sanitisation of clipping paths.
Browse files Browse the repository at this point in the history
The PDF operator stream in this file does:

 W <path> n /Image Do

The filter code therefore currently tries to deal with the 'W'
without a path being defined. Currently it bounds it, finds it
is an empty rect, and therefore suppresses the image.

According to the PDF spec though, W and W* are not supposed to
look at the path at the point they are called. Instead they
merely set it up so that a side effect of the next 'path painting
operator' (in this case 'n') is to intersect the clip path
AFTER than operator has taken place.

This commit therefore changes the behaviour of the filter to
follow this. All we do when we get a 'W' or a 'W*' is to set a
new clip_op field. Whenever we then process a path painting
operator, we look at this field, and process W or W* as
appropriate.

We are at pains to forward the W or W* operator AFTER the path
has been processed, but before the path painting operator
is sent. This should keep bugged renderers such as Preview
happy.

The has meant that the culler handling has changed slightly
in that the culler now doesn't deal with CLIP paths separately
to FILL or STROKE paths, but rather now as a combination (e.g.
CLIP_FILL_PATH). Trying to deal differently with CLIP and FILL
paths (for example) would probably have gone wrong in the past
so there is no real downside here.
  • Loading branch information
robinwatts committed Dec 10, 2024
1 parent 6b34a6a commit af5df0b
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 42 deletions.
14 changes: 9 additions & 5 deletions include/mupdf/pdf/interpret.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,15 @@ struct pdf_filter_options

typedef enum
{
FZ_CULL_PATH_FILL,
FZ_CULL_PATH_STROKE,
FZ_CULL_PATH_FILL_STROKE,
FZ_CULL_CLIP_PATH,
FZ_CULL_GLYPH,
FZ_CULL_PATH_DROP = 0,
FZ_CULL_PATH_FILL = 1,
FZ_CULL_PATH_STROKE = 2,
FZ_CULL_PATH_FILL_STROKE = 3,
FZ_CULL_CLIP_PATH_DROP = 4,
FZ_CULL_CLIP_PATH_FILL = 5,
FZ_CULL_CLIP_PATH_STROKE = 6,
FZ_CULL_CLIP_PATH_FILL_STROKE = 7,
FZ_CULL_GLYPH = 8,
FZ_CULL_IMAGE,
FZ_CULL_SHADING
} fz_cull_type;
Expand Down
7 changes: 6 additions & 1 deletion source/pdf/pdf-clean.c
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,9 @@ static int culler(fz_context *ctx, void *opaque, fz_rect bbox, fz_cull_type type
case FZ_CULL_PATH_FILL:
case FZ_CULL_PATH_STROKE:
case FZ_CULL_PATH_FILL_STROKE:
case FZ_CULL_CLIP_PATH_FILL:
case FZ_CULL_CLIP_PATH_STROKE:
case FZ_CULL_CLIP_PATH_FILL_STROKE:
if (red->line_art == PDF_REDACT_LINE_ART_REMOVE_IF_COVERED)
return (rect_touches_redactions(ctx, bbox, red) == 2);
else if (red->line_art == PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED)
Expand Down Expand Up @@ -1118,7 +1121,9 @@ static int clip_culler(fz_context *ctx, void *opaque, fz_rect bbox, fz_cull_type
case FZ_CULL_PATH_FILL:
case FZ_CULL_PATH_STROKE:
case FZ_CULL_PATH_FILL_STROKE:
case FZ_CULL_CLIP_PATH:
case FZ_CULL_CLIP_PATH_FILL:
case FZ_CULL_CLIP_PATH_STROKE:
case FZ_CULL_CLIP_PATH_FILL_STROKE:
case FZ_CULL_GLYPH:
case FZ_CULL_IMAGE:
case FZ_CULL_SHADING:
Expand Down
96 changes: 60 additions & 36 deletions source/pdf/pdf-op-filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,21 @@ typedef struct pdf_filter_gstate
pdf_text_state text;
} pdf_filter_gstate;

typedef enum
{
NO_CLIP_OP,
CLIP_W,
CLIP_Wstar
} clip_op_t;

typedef struct filter_gstate
{
struct filter_gstate *next;
int pushed;
fz_rect clip_rect;
pdf_filter_gstate pending;
pdf_filter_gstate sent;
clip_op_t clip_op;
} filter_gstate;

typedef struct
Expand Down Expand Up @@ -1505,7 +1513,6 @@ cull_segmenter_rectto(fz_context *ctx, void *arg, float x1, float y1, float x2,
fz_rectto(ctx, sd->segment, x1, y1, x2, y2);
}


static int
cull_path(fz_context *ctx, pdf_sanitize_processor *p, int type, int flush)
{
Expand All @@ -1525,10 +1532,27 @@ cull_path(fz_context *ctx, pdf_sanitize_processor *p, int type, int flush)
{
filter_flush(ctx, p, flush);
fz_walk_path(ctx, p->path, &cull_replay_walker, p);
/* Even in the case of a clip path, we want to drop the path here.
* This is because we've sent it already. The only thing that's
* supposed to follow a clipping operator is either a path painting
* operator, or 'n'. */

if (p->gstate->clip_op != NO_CLIP_OP)
{
fz_rect r;

/* ctm has always been flushed by now. */
r = fz_bound_path(ctx, p->path, NULL, p->gstate->sent.ctm);
p->gstate->clip_rect = fz_intersect_rect(p->gstate->clip_rect, r);
if (p->gstate->clip_op == CLIP_W)
{
if (p->chain->op_W)
p->chain->op_W(ctx, p->chain);
}
else
{
if (p->chain->op_Wstar)
p->chain->op_Wstar(ctx, p->chain);
}
p->gstate->clip_op = NO_CLIP_OP;
}

fz_drop_path(ctx, p->path);
p->path = NULL;
p->path = fz_new_path(ctx);
Expand All @@ -1555,6 +1579,8 @@ cull_path(fz_context *ctx, pdf_sanitize_processor *p, int type, int flush)
sd.p = p;
sd.segment = NULL;
sd.type = type;
if (p->gstate->clip_op != NO_CLIP_OP)
sd.type += FZ_CULL_CLIP_PATH_FILL - FZ_CULL_PATH_FILL;
sd.flush = flush;
fz_try(ctx)
{
Expand All @@ -1566,6 +1592,26 @@ cull_path(fz_context *ctx, pdf_sanitize_processor *p, int type, int flush)
fz_catch(ctx)
fz_rethrow(ctx);

if (sd.any_sent != 0 && p->gstate->clip_op != NO_CLIP_OP)
{
fz_rect r;

/* ctm has always been flushed by now. */
r = fz_bound_path(ctx, p->path, NULL, p->gstate->sent.ctm);
p->gstate->clip_rect = fz_intersect_rect(p->gstate->clip_rect, r);
if (p->gstate->clip_op == CLIP_W)
{
if (p->chain->op_W)
p->chain->op_W(ctx, p->chain);
}
else
{
if (p->chain->op_Wstar)
p->chain->op_Wstar(ctx, p->chain);
}
p->gstate->clip_op = NO_CLIP_OP;
}

fz_drop_path(ctx, p->path);
p->path = NULL;
p->path = fz_new_path(ctx);
Expand Down Expand Up @@ -1717,9 +1763,8 @@ pdf_filter_n(fz_context *ctx, pdf_processor *proc)
if (fz_is_empty_rect(p->gstate->clip_rect))
return;

fz_drop_path(ctx, p->path);
p->path = NULL;
p->path = fz_new_path(ctx);
if (cull_path(ctx, p, FZ_CULL_PATH_DROP, FLUSH_ALL))
return;

if (p->chain->op_n)
p->chain->op_n(ctx, p->chain);
Expand All @@ -1731,48 +1776,26 @@ static void
pdf_filter_W(fz_context *ctx, pdf_processor *proc)
{
pdf_sanitize_processor *p = (pdf_sanitize_processor*)proc;
fz_rect r;
fz_matrix ctm;

if (fz_is_empty_rect(p->gstate->clip_rect))
return;

ctm = fz_concat(p->gstate->pending.ctm, p->gstate->sent.ctm);
r = fz_bound_path(ctx, p->path, NULL, ctm);
p->gstate->clip_rect = fz_intersect_rect(p->gstate->clip_rect, r);

if (fz_is_empty_rect(p->gstate->clip_rect))
return;

if (cull_path(ctx, p, FZ_CULL_CLIP_PATH, FLUSH_CTM))
return;

if (p->chain->op_W)
p->chain->op_W(ctx, p->chain);
/* This operator does nothing when it is seen. It just affects the
* behaviour of the subsequent path drawing operation. */
p->gstate->clip_op = CLIP_W;
}

static void
pdf_filter_Wstar(fz_context *ctx, pdf_processor *proc)
{
pdf_sanitize_processor *p = (pdf_sanitize_processor*)proc;
fz_rect r;
fz_matrix ctm;

if (fz_is_empty_rect(p->gstate->clip_rect))
return;

ctm = fz_concat(p->gstate->pending.ctm, p->gstate->sent.ctm);
r = fz_bound_path(ctx, p->path, NULL, ctm);
p->gstate->clip_rect = fz_intersect_rect(p->gstate->clip_rect, r);

if (fz_is_empty_rect(p->gstate->clip_rect))
return;

if (cull_path(ctx, p, FZ_CULL_CLIP_PATH, FLUSH_CTM))
return;

if (p->chain->op_Wstar)
p->chain->op_Wstar(ctx, p->chain);
/* This operator does nothing when it is seen. It just affects the
* behaviour of the subsequent path drawing operation. */
p->gstate->clip_op = CLIP_Wstar;
}

/* text objects */
Expand Down Expand Up @@ -2961,6 +2984,7 @@ pdf_new_sanitize_filter(
proc->gstate->sent.stroke.linewidth = 1;
proc->gstate->sent.stroke.miterlimit = 10;
proc->gstate->clip_rect = fz_infinite_rect;
proc->gstate->clip_op = NO_CLIP_OP;
}
fz_catch(ctx)
{
Expand Down

0 comments on commit af5df0b

Please sign in to comment.