Coverage Report

Created: 2026-08-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/pdf/pdf-clean.c
Line
Count
Source
1
// Copyright (C) 2004-2026 Artifex Software, Inc.
2
//
3
// This file is part of MuPDF.
4
//
5
// MuPDF is free software: you can redistribute it and/or modify it under the
6
// terms of the GNU Affero General Public License as published by the Free
7
// Software Foundation, either version 3 of the License, or (at your option)
8
// any later version.
9
//
10
// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
// details.
14
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
#include "pdf-annot-imp.h"
25
26
#include <string.h>
27
#include <assert.h>
28
29
static void
30
pdf_filter_xobject(fz_context *ctx, pdf_document *doc, pdf_obj *xobj, pdf_obj *page_res, pdf_filter_options *options, pdf_cycle_list *cycle_up);
31
32
static void
33
pdf_filter_type3(fz_context *ctx, pdf_document *doc, pdf_obj *obj, pdf_obj *page_res, pdf_filter_options *options, pdf_cycle_list *cycle_up);
34
35
static void
36
pdf_filter_resources(fz_context *ctx, pdf_document *doc, pdf_obj *in_res, pdf_obj *res, pdf_filter_options *options, pdf_cycle_list *cycle_up)
37
0
{
38
0
  pdf_cycle_list cycle;
39
0
  pdf_obj *obj;
40
0
  int i, n;
41
42
0
  if (!options->recurse)
43
0
    return;
44
45
0
  if (pdf_cycle(ctx, &cycle, cycle_up, in_res))
46
0
    return;
47
48
  /* ExtGState */
49
0
  obj = pdf_dict_get(ctx, res, PDF_NAME(ExtGState));
50
0
  if (obj)
51
0
  {
52
0
    n = pdf_dict_len(ctx, obj);
53
0
    for (i = 0; i < n; i++)
54
0
    {
55
0
      pdf_obj *smask = pdf_dict_get(ctx, pdf_dict_get_val(ctx, obj, i), PDF_NAME(SMask));
56
0
      if (smask)
57
0
      {
58
0
        pdf_obj *g = pdf_dict_get(ctx, smask, PDF_NAME(G));
59
0
        if (g)
60
0
        {
61
          /* Transparency group XObject */
62
0
          pdf_filter_xobject(ctx, doc, g, in_res, options, &cycle);
63
0
        }
64
0
      }
65
0
    }
66
0
  }
67
68
  /* Pattern */
69
0
  obj = pdf_dict_get(ctx, res, PDF_NAME(Pattern));
70
0
  if (obj)
71
0
  {
72
0
    n = pdf_dict_len(ctx, obj);
73
0
    for (i = 0; i < n; i++)
74
0
    {
75
0
      pdf_obj *pat = pdf_dict_get_val(ctx, obj, i);
76
0
      if (pat && pdf_dict_get_int(ctx, pat, PDF_NAME(PatternType)) == 1)
77
0
      {
78
0
        pdf_filter_xobject(ctx, doc, pat, in_res, options, &cycle);
79
0
      }
80
0
    }
81
0
  }
82
83
  /* XObject */
84
0
  if (!options->instance_forms)
85
0
  {
86
0
    obj = pdf_dict_get(ctx, res, PDF_NAME(XObject));
87
0
    if (obj)
88
0
    {
89
0
      n = pdf_dict_len(ctx, obj);
90
0
      for (i = 0; i < n; i++)
91
0
      {
92
0
        pdf_obj *xobj = pdf_dict_get_val(ctx, obj, i);
93
0
        if (xobj && pdf_dict_get(ctx, xobj, PDF_NAME(Subtype)) == PDF_NAME(Form))
94
0
        {
95
0
          pdf_filter_xobject(ctx, doc, xobj, in_res, options, &cycle);
96
0
        }
97
0
      }
98
0
    }
99
0
  }
100
101
  /* Font */
102
0
  obj = pdf_dict_get(ctx, res, PDF_NAME(Font));
103
0
  if (obj)
104
0
  {
105
0
    n = pdf_dict_len(ctx, obj);
106
0
    for (i = 0; i < n; i++)
107
0
    {
108
0
      pdf_obj *font = pdf_dict_get_val(ctx, obj, i);
109
0
      if (font && pdf_dict_get(ctx, font, PDF_NAME(Subtype)) == PDF_NAME(Type3))
110
0
      {
111
0
        pdf_filter_type3(ctx, doc, font, in_res, options, &cycle);
112
0
      }
113
0
    }
114
0
  }
115
116
0
}
117
118
/*
119
  Clean a content stream's rendering operations, with an optional post
120
  processing step.
121
122
  Firstly, this filters the PDF operators used to avoid (some cases of)
123
  repetition, and leaves the content stream in a balanced state with an
124
  unchanged top level matrix etc. At the same time, the resources actually
125
  used are collected into a new resource dictionary.
126
127
  Next, the resources themselves are recursively cleaned (as appropriate)
128
  in the same way, if the 'recurse' flag is set.
129
*/
130
static void
131
pdf_filter_content_stream(
132
  fz_context *ctx,
133
  pdf_document *doc,
134
  pdf_obj *in_stm,
135
  pdf_obj *in_res,
136
  fz_matrix transform,
137
  pdf_filter_options *options,
138
  int struct_parents,
139
  fz_buffer **out_buf,
140
  pdf_obj **out_res,
141
  pdf_cycle_list *cycle_up)
142
0
{
143
0
  pdf_processor *proc_buffer = NULL;
144
0
  pdf_processor *top = NULL;
145
0
  pdf_processor **list = NULL;
146
0
  int num_filters = 0;
147
0
  int i;
148
149
0
  fz_var(proc_buffer);
150
151
0
  *out_buf = NULL;
152
0
  *out_res = NULL;
153
154
0
  if (options->filters)
155
0
    for (; options->filters[num_filters].filter != NULL; num_filters++);
156
157
0
  if (num_filters > 0)
158
0
    list = fz_calloc(ctx, num_filters, sizeof(pdf_processor *));
159
160
0
  fz_try(ctx)
161
0
  {
162
0
    *out_buf = fz_new_buffer(ctx, 1024);
163
0
    top = proc_buffer = pdf_new_buffer_processor(ctx, *out_buf, options->ascii, options->newlines);
164
0
    if (num_filters > 0)
165
0
    {
166
0
      for (i = num_filters - 1; i >= 0; i--)
167
0
        top = list[i] = options->filters[i].filter(ctx, doc, top, struct_parents, transform, options, options->filters[i].options);
168
0
    }
169
170
0
    pdf_process_contents(ctx, top, doc, in_res, in_stm, NULL, out_res);
171
0
    pdf_close_processor(ctx, top);
172
173
0
    pdf_filter_resources(ctx, doc, in_res, *out_res, options, cycle_up);
174
0
  }
175
0
  fz_always(ctx)
176
0
  {
177
0
    for (i = 0; i < num_filters; i++)
178
0
      pdf_drop_processor(ctx, list[i]);
179
0
    pdf_drop_processor(ctx, proc_buffer);
180
0
    fz_free(ctx, list);
181
0
  }
182
0
  fz_catch(ctx)
183
0
  {
184
0
    fz_drop_buffer(ctx, *out_buf);
185
0
    *out_buf = NULL;
186
0
    pdf_drop_obj(ctx, *out_res);
187
0
    *out_res = NULL;
188
0
    fz_rethrow(ctx);
189
0
  }
190
0
}
191
192
/*
193
  Clean a Type 3 font's CharProcs content streams. This works almost
194
  exactly like pdf_filter_content_stream, but the resource dictionary is
195
  shared between all off the CharProcs.
196
*/
197
static void
198
pdf_filter_type3(fz_context *ctx, pdf_document *doc, pdf_obj *obj, pdf_obj *page_res, pdf_filter_options *options, pdf_cycle_list *cycle_up)
199
0
{
200
0
  pdf_cycle_list cycle;
201
0
  pdf_processor *proc_buffer = NULL;
202
0
  pdf_processor *proc_filter = NULL;
203
0
  pdf_obj *in_res;
204
0
  pdf_obj *out_res = NULL;
205
0
  pdf_obj *charprocs;
206
0
  int i, n;
207
0
  int num_filters = 0;
208
0
  pdf_processor **list = NULL;
209
0
  fz_buffer *buffer = NULL;
210
0
  pdf_processor *top = NULL;
211
0
  pdf_obj *res = NULL;
212
0
  fz_buffer *new_buf = NULL;
213
214
0
  fz_var(out_res);
215
0
  fz_var(proc_buffer);
216
0
  fz_var(proc_filter);
217
0
  fz_var(buffer);
218
0
  fz_var(res);
219
0
  fz_var(new_buf);
220
0
  fz_var(top);
221
222
  /* We cannot combine instancing with type3 fonts. The new names for
223
   * instanced form/image resources would clash, since they start over for
224
   * each content stream. This is not a problem for now, because we only
225
   * use instancing with redaction, and redaction doesn't clean type3
226
   * fonts.
227
   */
228
0
  assert(!options->instance_forms);
229
230
  /* Avoid recursive cycles! */
231
0
  if (pdf_cycle(ctx, &cycle, cycle_up, obj))
232
0
    return;
233
234
0
  if (options->filters)
235
0
    for (; options->filters[num_filters].filter != NULL; num_filters++);
236
237
0
  if (num_filters > 0)
238
0
    list = fz_calloc(ctx, num_filters, sizeof(pdf_processor *));
239
240
0
  fz_try(ctx)
241
0
  {
242
0
    in_res = pdf_dict_get(ctx, obj, PDF_NAME(Resources));
243
0
    if (!in_res)
244
0
      in_res = page_res;
245
246
0
    buffer = fz_new_buffer(ctx, 1024);
247
0
    top = proc_buffer = pdf_new_buffer_processor(ctx, buffer, options->ascii, options->newlines);
248
0
    if (num_filters > 0)
249
0
    {
250
0
      for (i = num_filters - 1; i >= 0; i--)
251
0
        top = list[i] = options->filters[i].filter(ctx, doc, top, -1, fz_identity, options, options->filters[i].options);
252
0
    }
253
254
0
    pdf_processor_push_resources(ctx, top, in_res);
255
0
    charprocs = pdf_dict_get(ctx, obj, PDF_NAME(CharProcs));
256
0
    n = pdf_dict_len(ctx, charprocs);
257
0
    for (i = 0; i < n; i++)
258
0
    {
259
0
      pdf_obj *val = pdf_dict_get_val(ctx, charprocs, i);
260
261
0
      if (i > 0)
262
0
      {
263
        // reset all chained processors (and clear the buffer)
264
0
        pdf_reset_processor(ctx, top);
265
0
      }
266
0
      pdf_process_raw_contents(ctx, top, doc, val, NULL);
267
268
0
      pdf_close_processor(ctx, top);
269
270
0
      if (!options->no_update)
271
0
      {
272
0
        new_buf = fz_clone_buffer(ctx, buffer);
273
0
        pdf_update_stream(ctx, doc, val, new_buf, 0);
274
0
        fz_drop_buffer(ctx, new_buf);
275
0
        new_buf = NULL;
276
0
      }
277
0
    }
278
279
0
  }
280
0
  fz_always(ctx)
281
0
  {
282
0
    res = pdf_processor_pop_resources(ctx, top);
283
0
    for (i = 0; i < num_filters; i++)
284
0
      pdf_drop_processor(ctx, list[i]);
285
0
    pdf_drop_processor(ctx, proc_buffer);
286
0
    fz_free(ctx, list);
287
0
    fz_drop_buffer(ctx, new_buf);
288
0
    fz_drop_buffer(ctx, buffer);
289
0
  }
290
0
  fz_catch(ctx)
291
0
  {
292
0
    pdf_drop_obj(ctx, res);
293
0
    fz_rethrow(ctx);
294
0
  }
295
0
  pdf_dict_put_drop(ctx, obj, PDF_NAME(Resources), res);
296
0
}
297
298
static void
299
pdf_filter_xobject(fz_context *ctx, pdf_document *doc, pdf_obj *stm, pdf_obj *page_res, pdf_filter_options *options, pdf_cycle_list *cycle_up)
300
0
{
301
0
  pdf_cycle_list cycle;
302
0
  int struct_parents;
303
0
  pdf_obj *new_res = NULL;
304
0
  fz_buffer *new_buf = NULL;
305
0
  pdf_obj *old_res;
306
307
0
  fz_var(new_buf);
308
0
  fz_var(new_res);
309
310
  // TODO for RJW: XObject can also be a StructParent; how do we handle that case?
311
312
0
  struct_parents = pdf_dict_get_int_default(ctx, stm, PDF_NAME(StructParents), -1);
313
314
0
  old_res = pdf_dict_get(ctx, stm, PDF_NAME(Resources));
315
0
  if (!old_res)
316
0
    old_res = page_res;
317
318
  // TODO: don't clean objects more than once.
319
320
  /* Avoid recursive cycles! */
321
0
  if (pdf_cycle(ctx, &cycle, cycle_up, stm))
322
0
    return;
323
0
  fz_try(ctx)
324
0
  {
325
0
    pdf_filter_content_stream(ctx, doc, stm, old_res, fz_identity, options, struct_parents, &new_buf, &new_res, &cycle);
326
0
    if (!options->no_update)
327
0
    {
328
0
      pdf_update_stream(ctx, doc, stm, new_buf, 0);
329
0
      pdf_dict_put(ctx, stm, PDF_NAME(Resources), new_res);
330
0
    }
331
0
  }
332
0
  fz_always(ctx)
333
0
  {
334
0
    fz_drop_buffer(ctx, new_buf);
335
0
    pdf_drop_obj(ctx, new_res);
336
0
  }
337
0
  fz_catch(ctx)
338
0
    fz_rethrow(ctx);
339
0
}
340
341
pdf_obj *
342
pdf_filter_xobject_instance(fz_context *ctx, pdf_obj *old_xobj, pdf_obj *page_res, fz_matrix transform, pdf_filter_options *options, pdf_cycle_list *cycle_up)
343
0
{
344
0
  pdf_cycle_list cycle;
345
0
  pdf_document *doc = pdf_get_bound_document(ctx, old_xobj);
346
0
  pdf_obj *new_xobj = NULL;
347
0
  pdf_obj *new_res = NULL, *old_res;
348
0
  fz_buffer *new_buf = NULL;
349
0
  int struct_parents;
350
0
  fz_matrix matrix;
351
352
0
  fz_var(new_xobj);
353
0
  fz_var(new_buf);
354
0
  fz_var(new_res);
355
356
  // TODO for RJW: XObject can also be a StructParent; how do we handle that case?
357
  // TODO for RJW: will we run into trouble by duplicating StructParents stuff?
358
359
0
  struct_parents = pdf_dict_get_int_default(ctx, old_xobj, PDF_NAME(StructParents), -1);
360
361
0
  old_res = pdf_dict_get(ctx, old_xobj, PDF_NAME(Resources));
362
0
  if (!old_res)
363
0
    old_res = page_res;
364
365
0
  if (pdf_cycle(ctx, &cycle, cycle_up, old_xobj))
366
0
    return pdf_keep_obj(ctx, old_xobj);
367
368
0
  matrix = pdf_dict_get_matrix(ctx, old_xobj, PDF_NAME(Matrix));
369
0
  transform = fz_concat(matrix, transform);
370
371
0
  fz_try(ctx)
372
0
  {
373
0
    new_xobj = pdf_add_object_drop(ctx, doc, pdf_copy_dict(ctx, old_xobj));
374
0
    pdf_filter_content_stream(ctx, doc, old_xobj, old_res, transform, options, struct_parents, &new_buf, &new_res, &cycle);
375
0
    if (!options->no_update)
376
0
    {
377
0
      pdf_update_stream(ctx, doc, new_xobj, new_buf, 0);
378
0
      pdf_dict_put(ctx, new_xobj, PDF_NAME(Resources), new_res);
379
0
    }
380
0
  }
381
0
  fz_always(ctx)
382
0
  {
383
0
    fz_drop_buffer(ctx, new_buf);
384
0
    pdf_drop_obj(ctx, new_res);
385
0
  }
386
0
  fz_catch(ctx)
387
0
  {
388
0
    pdf_drop_obj(ctx, new_xobj);
389
0
    fz_rethrow(ctx);
390
0
  }
391
392
0
  return new_xobj;
393
0
}
394
395
void pdf_filter_page_contents(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_filter_options *options)
396
0
{
397
0
  pdf_obj *contents, *old_res;
398
0
  pdf_obj *new_res;
399
0
  fz_buffer *buffer;
400
0
  int struct_parents;
401
402
0
  struct_parents = pdf_dict_get_int_default(ctx, page->obj, PDF_NAME(StructParents), -1);
403
404
0
  contents = pdf_page_contents(ctx, page);
405
0
  old_res = pdf_page_resources(ctx, page);
406
407
0
  pdf_filter_content_stream(ctx, doc, contents, old_res, fz_identity, options, struct_parents, &buffer, &new_res, NULL);
408
409
0
  fz_try(ctx)
410
0
  {
411
0
    if (options->complete)
412
0
      options->complete(ctx, buffer, options->opaque);
413
0
    if (!options->no_update)
414
0
    {
415
      /* Always create a new stream object to replace the page contents. This is useful
416
         both if the contents is an array of streams, is entirely missing or if the contents
417
         are shared between pages. */
418
0
      contents = pdf_add_object_drop(ctx, doc, pdf_new_dict(ctx, doc, 1));
419
0
      pdf_dict_put_drop(ctx, page->obj, PDF_NAME(Contents), contents);
420
0
      pdf_update_stream(ctx, doc, contents, buffer, 0);
421
0
      pdf_dict_put(ctx, page->obj, PDF_NAME(Resources), new_res);
422
0
    }
423
0
  }
424
0
  fz_always(ctx)
425
0
  {
426
0
    fz_drop_buffer(ctx, buffer);
427
0
    pdf_drop_obj(ctx, new_res);
428
0
  }
429
0
  fz_catch(ctx)
430
0
    fz_rethrow(ctx);
431
0
}
432
433
void pdf_filter_annot_contents(fz_context *ctx, pdf_document *doc, pdf_annot *annot, pdf_filter_options *options)
434
0
{
435
0
  pdf_obj *ap = pdf_dict_get(ctx, annot->obj, PDF_NAME(AP));
436
0
  if (pdf_is_dict(ctx, ap))
437
0
  {
438
0
    int i, n = pdf_dict_len(ctx, ap);
439
0
    for (i = 0; i < n; i++)
440
0
    {
441
0
      pdf_obj *stm = pdf_dict_get_val(ctx, ap, i);
442
0
      if (pdf_is_stream(ctx, stm))
443
0
      {
444
0
        pdf_filter_xobject(ctx, doc, stm, NULL, options, NULL);
445
0
      }
446
0
    }
447
0
  }
448
0
}
449
450
/* REDACTIONS */
451
452
struct redact_filter_state {
453
  pdf_filter_options filter_opts;
454
  pdf_sanitize_filter_options sanitize_opts;
455
  pdf_filter_factory filter_list[2];
456
  pdf_page *page;
457
  pdf_annot *target; // NULL if all
458
  int line_art;
459
  int text;
460
};
461
462
463
static void pdf_run_obj_to_buf(fz_context *ctx, fz_buffer *buffer, pdf_obj *obj, pdf_page *page)
464
0
{
465
0
  pdf_processor *proc = pdf_new_buffer_processor(ctx, buffer, 0, 0);
466
0
  pdf_obj *res;
467
468
469
0
  fz_try(ctx)
470
0
  {
471
0
    res = pdf_xobject_resources(ctx, obj);
472
0
    if (res == NULL)
473
0
      res = pdf_page_resources(ctx, page);
474
475
0
    pdf_process_contents(ctx, proc, page->doc, res, obj, NULL, NULL);
476
0
    pdf_close_processor(ctx, proc);
477
0
  }
478
0
  fz_always(ctx)
479
0
    pdf_drop_processor(ctx, proc);
480
0
  fz_catch(ctx)
481
0
    fz_rethrow(ctx);
482
0
}
483
484
static void
485
pdf_redact_end_page(fz_context *ctx, fz_buffer *buf, void *opaque)
486
0
{
487
0
  struct redact_filter_state *red = opaque;
488
0
  pdf_page *page = red->page;
489
0
  pdf_annot *annot;
490
0
  pdf_obj *qp;
491
0
  int i, n;
492
493
0
  fz_append_string(ctx, buf, " 0 g\n");
494
495
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
496
0
  {
497
0
    if (red->target != NULL && red->target != annot)
498
0
      continue;
499
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
500
0
    {
501
0
      pdf_obj *ro = pdf_dict_get(ctx, annot->obj, PDF_NAME(RO));
502
0
      if (ro)
503
0
      {
504
0
        pdf_run_obj_to_buf(ctx, buf, ro, page);
505
0
      }
506
0
      else
507
0
      {
508
0
        qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
509
0
        n = pdf_array_len(ctx, qp);
510
0
        if (n > 0)
511
0
        {
512
0
          for (i = 0; i < n; i += 8)
513
0
          {
514
0
            fz_quad q = pdf_to_quad(ctx, qp, i);
515
0
            fz_append_printf(ctx, buf, "%g %g m\n", q.ll.x, q.ll.y);
516
0
            fz_append_printf(ctx, buf, "%g %g l\n", q.lr.x, q.lr.y);
517
0
            fz_append_printf(ctx, buf, "%g %g l\n", q.ur.x, q.ur.y);
518
0
            fz_append_printf(ctx, buf, "%g %g l\n", q.ul.x, q.ul.y);
519
0
            fz_append_string(ctx, buf, "f\n");
520
0
          }
521
0
        }
522
0
        else
523
0
        {
524
0
          fz_rect r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
525
0
          fz_append_printf(ctx, buf, "%g %g m\n", r.x0, r.y0);
526
0
          fz_append_printf(ctx, buf, "%g %g l\n", r.x1, r.y0);
527
0
          fz_append_printf(ctx, buf, "%g %g l\n", r.x1, r.y1);
528
0
          fz_append_printf(ctx, buf, "%g %g l\n", r.x0, r.y1);
529
0
          fz_append_string(ctx, buf, "f\n");
530
0
        }
531
0
      }
532
0
    }
533
0
  }
534
0
}
535
536
static int
537
pdf_redact_text_filter(fz_context *ctx, void *opaque, int *ucsbuf, int ucslen, fz_matrix trm, fz_matrix ctm, fz_rect bbox, int tr, float ca, float CA)
538
0
{
539
0
  struct redact_filter_state *red = opaque;
540
0
  pdf_page *page = red->page;
541
0
  pdf_annot *annot;
542
0
  pdf_obj *qp;
543
0
  fz_rect r;
544
0
  fz_quad q;
545
0
  int i, n;
546
0
  float w, h;
547
548
0
  trm = fz_concat(trm, ctm);
549
0
  bbox = fz_transform_rect(bbox, trm);
550
551
  /* Shrink character bbox a bit */
552
0
  w = bbox.x1 - bbox.x0;
553
0
  h = bbox.y1 - bbox.y0;
554
0
  bbox.x0 += w / 10;
555
0
  bbox.x1 -= w / 10;
556
0
  bbox.y0 += h / 10;
557
0
  bbox.y1 -= h / 10;
558
559
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
560
0
  {
561
0
    if (red->target != NULL && red->target != annot)
562
0
      continue;
563
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
564
0
    {
565
0
      qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
566
0
      n = pdf_array_len(ctx, qp);
567
      /* Note, we test for the intersection being a valid rectangle, NOT
568
       * a non-empty one. This is because we can have 'empty' character
569
       * boxes (say for diacritics), that while 0 width, do have a defined
570
       * position on the plane, and hence inclusion makes sense. */
571
0
      if (n > 0)
572
0
      {
573
0
        for (i = 0; i < n; i += 8)
574
0
        {
575
0
          q = pdf_to_quad(ctx, qp, i);
576
0
          r = fz_rect_from_quad(q);
577
0
          if (fz_is_valid_rect(fz_intersect_rect(bbox, r)))
578
0
            return 1;
579
0
        }
580
0
      }
581
0
      else
582
0
      {
583
0
        r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
584
0
        if (fz_is_valid_rect(fz_intersect_rect(bbox, r)))
585
0
          return 1;
586
0
      }
587
0
    }
588
0
  }
589
590
0
  return 0;
591
0
}
592
593
static int
594
pdf_redact_invisible_text_filter(fz_context *ctx, void *opaque, int *ucsbuf, int ucslen, fz_matrix trm, fz_matrix ctm, fz_rect bbox, int tr, float ca, float CA)
595
0
{
596
0
  int invisible = 0;
597
598
0
  switch (tr)
599
0
  {
600
0
    case 0: /* Fill */
601
0
      invisible = (ca == 0);
602
0
      break;
603
0
    case 1: /* Stroke */
604
0
      invisible = (CA == 0);
605
0
      break;
606
0
    case 2: /* Fill + Stroke */
607
0
      invisible = (ca == 0 && CA == 0);
608
0
      break;
609
0
    case 3: /* Neither Fill nor stroke */
610
0
      invisible = 1;
611
0
      break;
612
0
  }
613
614
0
  if (!invisible)
615
0
    return 0;
616
617
0
  return pdf_redact_text_filter(ctx, opaque, ucsbuf, ucslen, trm, ctm, bbox, tr, ca, CA);
618
0
}
619
620
static fz_pixmap *
621
pdf_redact_image_imp(fz_context *ctx, fz_matrix ctm, fz_image *image, fz_pixmap *pixmap, fz_pixmap **pmask, fz_quad q)
622
0
{
623
0
  fz_matrix inv_ctm;
624
0
  fz_irect r;
625
0
  int x, y, k, n, bpp;
626
0
  unsigned char white;
627
0
  fz_pixmap *mask = *pmask;
628
0
  int pixmap_cloned = 0;
629
630
0
  if (!pixmap)
631
0
  {
632
0
    fz_pixmap *original = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL);
633
0
    int imagemask = image->imagemask;
634
635
0
    fz_try(ctx)
636
0
    {
637
0
      enum fz_colorspace_type type = fz_colorspace_type(ctx, original ? original->colorspace : NULL);
638
0
retry_with_base:
639
0
      switch (type)
640
0
      {
641
0
        case FZ_COLORSPACE_NONE:
642
0
        case FZ_COLORSPACE_RGB:
643
0
        case FZ_COLORSPACE_GRAY:
644
0
        case FZ_COLORSPACE_CMYK:
645
0
        case FZ_COLORSPACE_LAB:
646
0
          pixmap = fz_clone_pixmap(ctx, original);
647
0
          break;
648
0
        case FZ_COLORSPACE_INDEXED:
649
0
          type = original->colorspace->u.indexed.base ? original->colorspace->u.indexed.base->type : FZ_COLORSPACE_NONE;
650
0
          goto retry_with_base;
651
0
        case FZ_COLORSPACE_SEPARATION:
652
0
          pixmap = fz_convert_pixmap(ctx, original, fz_device_cmyk(ctx), NULL, NULL, fz_default_color_params, 1);
653
0
          break;
654
0
        default:
655
0
          pixmap = fz_convert_pixmap(ctx, original, fz_device_rgb(ctx), NULL, NULL, fz_default_color_params, 1);
656
0
          break;
657
0
      }
658
0
      if (imagemask)
659
0
        fz_invert_pixmap_alpha(ctx, pixmap);
660
0
    }
661
0
    fz_always(ctx)
662
0
      fz_drop_pixmap(ctx, original);
663
0
    fz_catch(ctx)
664
0
      fz_rethrow(ctx);
665
0
    pixmap_cloned = 1;
666
0
  }
667
668
0
  if (!mask && image->mask)
669
0
  {
670
0
    fz_pixmap *original = fz_get_pixmap_from_image(ctx, image->mask, NULL, NULL, NULL, NULL);
671
672
0
    fz_try(ctx)
673
0
    {
674
0
      mask = fz_clone_pixmap(ctx, original);
675
0
      *pmask = mask;
676
0
    }
677
0
    fz_always(ctx)
678
0
    {
679
0
      fz_drop_pixmap(ctx, original);
680
0
    }
681
0
    fz_catch(ctx)
682
0
    {
683
0
      if (pixmap_cloned)
684
0
        fz_drop_pixmap(ctx, pixmap);
685
0
      fz_rethrow(ctx);
686
0
    }
687
0
  }
688
689
  /* If we have a 1x1 image, to which a mask is being applied
690
   * then it's the mask we really want to change, not the
691
   * image. We might have just a small section of the image
692
   * being covered, and setting the whole thing to white
693
   * will blank stuff outside the desired area. */
694
0
  if (!mask || pixmap->w > 1 || pixmap->h > 1)
695
0
  {
696
0
    n = pixmap->n - pixmap->alpha;
697
0
    bpp = pixmap->n;
698
0
    if (fz_colorspace_is_subtractive(ctx, pixmap->colorspace))
699
0
      white = 0;
700
0
    else
701
0
      white = 255;
702
703
0
    inv_ctm = fz_post_scale(fz_invert_matrix(ctm), pixmap->w, pixmap->h);
704
0
    r = fz_round_rect(fz_transform_rect(fz_rect_from_quad(q), inv_ctm));
705
0
    r.x0 = fz_clampi(r.x0, 0, pixmap->w);
706
0
    r.x1 = fz_clampi(r.x1, 0, pixmap->w);
707
0
    r.y1 = fz_clampi(pixmap->h - r.y1, 0, pixmap->h);
708
0
    r.y0 = fz_clampi(pixmap->h - r.y0, 0, pixmap->h);
709
0
    for (y = r.y1; y < r.y0; ++y)
710
0
    {
711
0
      for (x = r.x0; x < r.x1; ++x)
712
0
      {
713
0
        unsigned char *s = &pixmap->samples[(size_t)y * pixmap->stride + (size_t)x * bpp];
714
0
        for (k = 0; k < n; ++k)
715
0
          s[k] = white;
716
0
        if (pixmap->alpha)
717
0
          s[k] = 255;
718
0
      }
719
0
    }
720
0
  }
721
722
0
  if (mask)
723
0
  {
724
0
    inv_ctm = fz_post_scale(fz_invert_matrix(ctm), mask->w, mask->h);
725
0
    r = fz_round_rect(fz_transform_rect(fz_rect_from_quad(q), inv_ctm));
726
0
    r.x0 = fz_clampi(r.x0, 0, mask->w);
727
0
    r.x1 = fz_clampi(r.x1, 0, mask->w);
728
0
    r.y1 = fz_clampi(mask->h - r.y1, 0, mask->h);
729
0
    r.y0 = fz_clampi(mask->h - r.y0, 0, mask->h);
730
0
    for (y = r.y1; y < r.y0; ++y)
731
0
    {
732
0
      unsigned char *s = &mask->samples[(size_t)y * mask->stride + (size_t)r.x0];
733
0
      memset(s, 0xff, r.x1-r.x0);
734
0
    }
735
0
  }
736
737
0
  return pixmap;
738
0
}
739
740
static fz_image *
741
pdf_redact_image_filter_remove(fz_context *ctx, void *opaque, fz_matrix ctm, const char *name, fz_image *image, fz_rect clip)
742
0
{
743
0
  fz_pixmap *redacted = NULL;
744
0
  struct redact_filter_state *red = opaque;
745
0
  pdf_page *page = red->page;
746
0
  pdf_annot *annot;
747
0
  pdf_obj *qp;
748
0
  fz_rect area;
749
0
  fz_rect r;
750
0
  int i, n;
751
752
0
  fz_var(redacted);
753
754
0
  area = fz_transform_rect(fz_unit_rect, ctm);
755
756
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
757
0
  {
758
0
    if (red->target != NULL && red->target != annot)
759
0
      continue;
760
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
761
0
    {
762
0
      qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
763
0
      n = pdf_array_len(ctx, qp);
764
0
      if (n > 0)
765
0
      {
766
0
        for (i = 0; i < n; i += 8)
767
0
        {
768
0
          r = fz_rect_from_quad(pdf_to_quad(ctx, qp, i));
769
0
          r = fz_intersect_rect(r, area);
770
0
          if (!fz_is_empty_rect(r))
771
0
            return NULL;
772
0
        }
773
0
      }
774
0
      else
775
0
      {
776
0
        r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
777
0
        r = fz_intersect_rect(r, area);
778
0
        if (!fz_is_empty_rect(r))
779
0
          return NULL;
780
0
      }
781
0
    }
782
0
  }
783
784
0
  return fz_keep_image(ctx, image);
785
0
}
786
787
static fz_image *
788
pdf_redact_image_filter_remove_invisible(fz_context *ctx, void *opaque, fz_matrix ctm, const char *name, fz_image *image, fz_rect clip)
789
0
{
790
0
  fz_pixmap *redacted = NULL;
791
0
  struct redact_filter_state *red = opaque;
792
0
  pdf_page *page = red->page;
793
0
  pdf_annot *annot;
794
0
  pdf_obj *qp;
795
0
  fz_rect area;
796
0
  fz_rect r;
797
0
  int i, n;
798
799
0
  fz_var(redacted);
800
801
0
  area = fz_transform_rect(fz_unit_rect, ctm);
802
803
  /* Restrict the are of the image to that which can actually be seen. */
804
0
  area = fz_intersect_rect(area, clip);
805
806
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
807
0
  {
808
0
    if (red->target != NULL && red->target != annot)
809
0
      continue;
810
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
811
0
    {
812
0
      qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
813
0
      n = pdf_array_len(ctx, qp);
814
0
      if (n > 0)
815
0
      {
816
0
        for (i = 0; i < n; i += 8)
817
0
        {
818
0
          r = fz_rect_from_quad(pdf_to_quad(ctx, qp, i));
819
0
          r = fz_intersect_rect(r, area);
820
0
          if (!fz_is_empty_rect(r))
821
0
            return NULL;
822
0
        }
823
0
      }
824
0
      else
825
0
      {
826
0
        r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
827
0
        r = fz_intersect_rect(r, area);
828
0
        if (!fz_is_empty_rect(r))
829
0
          return NULL;
830
0
      }
831
0
    }
832
0
  }
833
834
0
  return fz_keep_image(ctx, image);
835
0
}
836
837
static fz_image *
838
pdf_redact_image_filter_pixels(fz_context *ctx, void *opaque, fz_matrix ctm, const char *name, fz_image *image, fz_rect clip)
839
0
{
840
0
  fz_pixmap *redacted = NULL;
841
0
  fz_pixmap *mask = NULL;
842
0
  struct redact_filter_state *red = opaque;
843
0
  pdf_page *page = red->page;
844
0
  pdf_annot *annot;
845
0
  pdf_obj *qp;
846
0
  fz_quad area, q;
847
0
  fz_rect r;
848
0
  int i, n;
849
850
0
  fz_var(redacted);
851
0
  fz_var(mask);
852
853
0
  area = fz_transform_quad(fz_quad_from_rect(fz_unit_rect), ctm);
854
855
  /* First see if we can redact the image completely */
856
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
857
0
  {
858
0
    if (red->target != NULL && red->target != annot)
859
0
      continue;
860
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
861
0
    {
862
0
      qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
863
0
      n = pdf_array_len(ctx, qp);
864
0
      if (n > 0)
865
0
      {
866
0
        for (i = 0; i < n; i += 8)
867
0
        {
868
0
          q = pdf_to_quad(ctx, qp, i);
869
0
          if (fz_is_quad_inside_quad(area, q))
870
0
            return NULL;
871
0
        }
872
0
      }
873
0
      else
874
0
      {
875
0
        r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
876
0
        q = fz_quad_from_rect(r);
877
0
        if (fz_is_quad_inside_quad(area, q))
878
0
          return NULL;
879
0
      }
880
0
    }
881
0
  }
882
883
  /* Blank out redacted parts of the image if necessary */
884
0
  fz_try(ctx)
885
0
  {
886
0
    for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
887
0
    {
888
0
      if (red->target != NULL && red->target != annot)
889
0
        continue;
890
0
      if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
891
0
      {
892
0
        qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
893
0
        n = pdf_array_len(ctx, qp);
894
0
        if (n > 0)
895
0
        {
896
0
          for (i = 0; i < n; i += 8)
897
0
          {
898
0
            q = pdf_to_quad(ctx, qp, i);
899
0
            if (fz_is_quad_intersecting_quad(area, q))
900
0
              redacted = pdf_redact_image_imp(ctx, ctm, image, redacted, &mask, q);
901
0
          }
902
0
        }
903
0
        else
904
0
        {
905
0
          r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
906
0
          q = fz_quad_from_rect(r);
907
0
          if (fz_is_quad_intersecting_quad(area, q))
908
0
            redacted = pdf_redact_image_imp(ctx, ctm, image, redacted, &mask, q);
909
0
        }
910
0
      }
911
0
    }
912
0
  }
913
0
  fz_catch(ctx)
914
0
  {
915
0
    fz_drop_pixmap(ctx, redacted);
916
0
    fz_drop_pixmap(ctx, mask);
917
0
    fz_rethrow(ctx);
918
0
  }
919
920
0
  if (redacted)
921
0
  {
922
0
    int imagemask = image->imagemask;
923
0
    fz_image *imask = fz_keep_image(ctx, image->mask);
924
925
0
    fz_var(imask);
926
927
0
    fz_try(ctx)
928
0
    {
929
0
      if (mask)
930
0
      {
931
0
        fz_drop_image(ctx, imask);
932
0
        imask = NULL;
933
0
        imask = fz_new_image_from_pixmap(ctx, mask, NULL);
934
0
      }
935
0
      image = fz_new_image_from_pixmap(ctx, redacted, NULL);
936
0
      image->imagemask = imagemask;
937
0
      image->mask = imask;
938
0
      imask = NULL;
939
0
    }
940
0
    fz_always(ctx)
941
0
    {
942
0
      fz_drop_pixmap(ctx, redacted);
943
0
      fz_drop_pixmap(ctx, mask);
944
0
      fz_drop_image(ctx, imask);
945
0
    }
946
0
    fz_catch(ctx)
947
0
      fz_rethrow(ctx);
948
0
    return image;
949
0
  }
950
951
0
  return fz_keep_image(ctx, image);
952
0
}
953
954
/* Returns 0 if area does not intersect with any of our redactions.
955
 * Returns 2 if area is completely included within one of our redactions.
956
 * Returns 1 otherwise. */
957
static int
958
rect_touches_redactions(fz_context *ctx, fz_rect area, struct redact_filter_state *red)
959
0
{
960
0
  pdf_annot *annot;
961
0
  pdf_obj *qp;
962
0
  fz_quad q;
963
0
  fz_rect r, s;
964
0
  int i, n;
965
0
  pdf_page *page = red->page;
966
967
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
968
0
  {
969
0
    if (red->target != NULL && red->target != annot)
970
0
      continue;
971
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
972
0
    {
973
0
      qp = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
974
0
      n = pdf_array_len(ctx, qp);
975
0
      if (n > 0)
976
0
      {
977
0
        for (i = 0; i < n; i += 8)
978
0
        {
979
0
          q = pdf_to_quad(ctx, qp, i);
980
0
          r = fz_rect_from_quad(q);
981
0
          s = fz_intersect_rect(r, area);
982
0
          if (!fz_is_empty_rect(s))
983
0
          {
984
0
            if (fz_contains_rect(r, area))
985
0
              return 2;
986
0
            return 1;
987
0
          }
988
0
        }
989
0
      }
990
0
      else
991
0
      {
992
0
        r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
993
0
        s = fz_intersect_rect(r, area);
994
0
        if (!fz_is_empty_rect(s))
995
0
        {
996
0
          if (fz_contains_rect(r, area))
997
0
            return 2;
998
0
          return 1;
999
0
        }
1000
0
      }
1001
0
    }
1002
0
  }
1003
0
  return 0;
1004
0
}
1005
1006
static void
1007
remove_page_link(fz_context *ctx, pdf_page *page, pdf_obj *obj)
1008
0
{
1009
0
  pdf_link **linkp = (pdf_link **)&page->links;
1010
0
  pdf_link *link;
1011
1012
0
  while ((link = *linkp) != NULL)
1013
0
  {
1014
0
    if (link->obj == obj)
1015
0
    {
1016
0
      *linkp = (pdf_link *)link->super.next;
1017
0
      link->super.next = NULL;
1018
0
      fz_drop_link(ctx, &link->super);
1019
0
      break;
1020
0
    }
1021
0
    else
1022
0
    {
1023
0
      linkp = (pdf_link **)&link->super.next;
1024
0
    }
1025
0
  }
1026
0
}
1027
1028
static void
1029
pdf_redact_page_links(fz_context *ctx, struct redact_filter_state *red)
1030
0
{
1031
0
  pdf_obj *annots;
1032
0
  pdf_obj *link;
1033
0
  fz_rect area;
1034
0
  int k;
1035
1036
0
  annots = pdf_dict_get(ctx, red->page->obj, PDF_NAME(Annots));
1037
0
  k = 0;
1038
0
  while (k < pdf_array_len(ctx, annots))
1039
0
  {
1040
0
    link = pdf_array_get(ctx, annots, k);
1041
0
    if (pdf_dict_get(ctx, link, PDF_NAME(Subtype)) == PDF_NAME(Link))
1042
0
    {
1043
0
      area = pdf_dict_get_rect(ctx, link, PDF_NAME(Rect));
1044
0
      if (rect_touches_redactions(ctx, area, red))
1045
0
      {
1046
0
        pdf_array_delete(ctx, annots, k);
1047
0
        remove_page_link(ctx, red->page, link);
1048
0
        continue;
1049
0
      }
1050
0
    }
1051
0
    ++k;
1052
0
  }
1053
0
}
1054
1055
static void
1056
pdf_redact_page_annotations(fz_context *ctx, struct redact_filter_state *red)
1057
0
{
1058
0
  pdf_annot *annot;
1059
0
  fz_rect area;
1060
1061
0
restart:
1062
0
  for (annot = pdf_first_annot(ctx, red->page); annot; annot = pdf_next_annot(ctx, annot))
1063
0
  {
1064
0
    if (pdf_annot_type(ctx, annot) == PDF_ANNOT_FREE_TEXT)
1065
0
    {
1066
0
      area = pdf_dict_get_rect(ctx, pdf_annot_obj(ctx, annot), PDF_NAME(Rect));
1067
0
      if (rect_touches_redactions(ctx, area, red))
1068
0
      {
1069
0
        pdf_delete_annot(ctx, red->page, annot);
1070
0
        goto restart;
1071
0
      }
1072
0
    }
1073
0
  }
1074
0
}
1075
1076
static int culler(fz_context *ctx, void *opaque, fz_rect bbox, fz_cull_type type)
1077
0
{
1078
0
  struct redact_filter_state *red = opaque;
1079
1080
0
  switch (type)
1081
0
  {
1082
0
  case FZ_CULL_PATH_FILL:
1083
0
  case FZ_CULL_PATH_STROKE:
1084
0
  case FZ_CULL_PATH_FILL_STROKE:
1085
0
  case FZ_CULL_CLIP_PATH_FILL:
1086
0
  case FZ_CULL_CLIP_PATH_STROKE:
1087
0
  case FZ_CULL_CLIP_PATH_FILL_STROKE:
1088
0
    if (red->line_art == PDF_REDACT_LINE_ART_REMOVE_IF_COVERED)
1089
0
      return (rect_touches_redactions(ctx, bbox, red) == 2);
1090
0
    else if (red->line_art == PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED)
1091
0
      return (rect_touches_redactions(ctx, bbox, red) != 0);
1092
0
    return 0;
1093
0
  default:
1094
0
    return 0;
1095
0
  }
1096
0
}
1097
1098
static
1099
void init_redact_filter(fz_context *ctx, pdf_redact_options *redact_opts, struct redact_filter_state *red, pdf_page *page, pdf_annot *target)
1100
0
{
1101
0
  int black_boxes = redact_opts ? redact_opts->black_boxes : 0;
1102
0
  int image_method = redact_opts ? redact_opts->image_method : PDF_REDACT_IMAGE_PIXELS;
1103
0
  int line_art = redact_opts ? redact_opts->line_art : PDF_REDACT_LINE_ART_NONE;
1104
0
  int text = redact_opts ? redact_opts->text : PDF_REDACT_TEXT_REMOVE;
1105
1106
0
  memset(&red->filter_opts, 0, sizeof red->filter_opts);
1107
0
  memset(&red->sanitize_opts, 0, sizeof red->sanitize_opts);
1108
1109
0
  red->filter_opts.recurse = 0; /* don't redact patterns, softmasks, and type3 fonts */
1110
0
  red->filter_opts.instance_forms = 1; /* redact xobjects with instancing */
1111
0
  red->filter_opts.ascii = 1;
1112
0
  red->filter_opts.opaque = red;
1113
0
  red->filter_opts.filters = red->filter_list;
1114
0
  if (black_boxes)
1115
0
    red->filter_opts.complete = pdf_redact_end_page;
1116
0
  red->line_art = line_art;
1117
0
  red->text = text;
1118
1119
0
  red->sanitize_opts.opaque = red;
1120
0
  if (text == PDF_REDACT_TEXT_REMOVE)
1121
0
    red->sanitize_opts.text_filter = pdf_redact_text_filter;
1122
0
  if (text == PDF_REDACT_TEXT_REMOVE_INVISIBLE)
1123
0
    red->sanitize_opts.text_filter = pdf_redact_invisible_text_filter;
1124
0
  if (image_method == PDF_REDACT_IMAGE_PIXELS)
1125
0
    red->sanitize_opts.image_filter = pdf_redact_image_filter_pixels;
1126
0
  if (image_method == PDF_REDACT_IMAGE_REMOVE)
1127
0
    red->sanitize_opts.image_filter = pdf_redact_image_filter_remove;
1128
0
  if (image_method == PDF_REDACT_IMAGE_REMOVE_UNLESS_INVISIBLE)
1129
0
    red->sanitize_opts.image_filter = pdf_redact_image_filter_remove_invisible;
1130
0
  red->sanitize_opts.culler = culler;
1131
1132
0
  red->filter_list[0].filter = pdf_new_sanitize_filter;
1133
0
  red->filter_list[0].options = &red->sanitize_opts;
1134
0
  red->filter_list[1].filter = NULL;
1135
0
  red->filter_list[1].options = NULL;
1136
1137
0
  red->page = page;
1138
0
  red->target = target;
1139
0
}
1140
1141
static int
1142
pdf_apply_redaction_imp(fz_context *ctx, pdf_page *page, pdf_annot *target, pdf_redact_options *redact_opts)
1143
0
{
1144
0
  pdf_annot *annot;
1145
0
  int has_redactions = 0;
1146
0
  struct redact_filter_state red;
1147
0
  pdf_document *doc = page->doc;
1148
1149
0
  for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot)) {
1150
0
    if (target != NULL && target != annot)
1151
0
      continue;
1152
0
    if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
1153
0
      has_redactions = 1;
1154
0
  }
1155
1156
0
  if (!has_redactions)
1157
0
    return 0;
1158
1159
0
  init_redact_filter(ctx, redact_opts, &red, page, target);
1160
1161
0
  if (target)
1162
0
    pdf_begin_operation(ctx, doc, "Apply redaction");
1163
0
  else
1164
0
    pdf_begin_operation(ctx, doc, "Apply redactions on page");
1165
0
  fz_try(ctx)
1166
0
  {
1167
0
    pdf_filter_page_contents(ctx, doc, page, &red.filter_opts);
1168
0
    pdf_redact_page_links(ctx, &red);
1169
0
    pdf_redact_page_annotations(ctx, &red);
1170
1171
0
    annot = pdf_first_annot(ctx, page);
1172
0
    while (annot)
1173
0
    {
1174
0
      if (target == NULL || annot == target)
1175
0
      {
1176
0
        if (pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)) == PDF_NAME(Redact))
1177
0
        {
1178
0
          pdf_delete_annot(ctx, page, annot);
1179
0
          annot = pdf_first_annot(ctx, page);
1180
0
          continue;
1181
0
        }
1182
0
      }
1183
0
      annot = pdf_next_annot(ctx, annot);
1184
0
    }
1185
1186
0
    doc->redacted = 1;
1187
0
    pdf_end_operation(ctx, doc);
1188
0
  }
1189
0
  fz_catch(ctx)
1190
0
  {
1191
0
    pdf_abandon_operation(ctx, doc);
1192
0
    fz_rethrow(ctx);
1193
0
  }
1194
1195
0
  return 1;
1196
0
}
1197
1198
int
1199
pdf_redact_page(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_redact_options *redact_opts)
1200
0
{
1201
0
  if (page == NULL || page->doc != doc)
1202
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "Can't redact a page not from the doc");
1203
0
  return pdf_apply_redaction_imp(ctx, page, NULL, redact_opts);
1204
0
}
1205
1206
int
1207
pdf_apply_redaction(fz_context *ctx, pdf_annot *annot, pdf_redact_options *redact_opts)
1208
0
{
1209
0
  return pdf_apply_redaction_imp(ctx, annot->page, annot, redact_opts);
1210
0
}
1211
1212
/* Hard clipping of pages */
1213
1214
struct clip_filter_state {
1215
  pdf_filter_options filter_opts;
1216
  pdf_sanitize_filter_options sanitize_opts;
1217
  pdf_filter_factory filter_list[2];
1218
  pdf_page *page;
1219
  fz_rect clip;
1220
};
1221
1222
static int clip_culler(fz_context *ctx, void *opaque, fz_rect bbox, fz_cull_type type)
1223
0
{
1224
0
  struct clip_filter_state *hc = opaque;
1225
1226
0
  switch (type)
1227
0
  {
1228
0
  case FZ_CULL_PATH_FILL:
1229
0
  case FZ_CULL_PATH_STROKE:
1230
0
  case FZ_CULL_PATH_FILL_STROKE:
1231
0
  case FZ_CULL_CLIP_PATH_FILL:
1232
0
  case FZ_CULL_CLIP_PATH_STROKE:
1233
0
  case FZ_CULL_CLIP_PATH_FILL_STROKE:
1234
0
  case FZ_CULL_GLYPH:
1235
0
  case FZ_CULL_IMAGE:
1236
0
  case FZ_CULL_SHADING:
1237
0
    return fz_is_empty_rect(fz_intersect_rect(bbox, hc->clip));
1238
0
  default:
1239
0
    return 0;
1240
0
  }
1241
0
}
1242
1243
static
1244
void init_clip_filter(fz_context *ctx, struct clip_filter_state *hc, pdf_page *page, fz_rect clip)
1245
0
{
1246
0
  memset(&hc->filter_opts, 0, sizeof hc->filter_opts);
1247
0
  memset(&hc->sanitize_opts, 0, sizeof hc->sanitize_opts);
1248
1249
0
  hc->filter_opts.recurse = 0; /* don't redact patterns, softmasks, and type3 fonts */
1250
0
  hc->filter_opts.instance_forms = 1; /* redact xobjects with instancing */
1251
0
  hc->filter_opts.ascii = 0;
1252
0
  hc->filter_opts.opaque = hc;
1253
0
  hc->filter_opts.filters = hc->filter_list;
1254
0
  hc->clip = clip;
1255
1256
0
  hc->sanitize_opts.opaque = hc;
1257
0
  hc->sanitize_opts.culler = clip_culler;
1258
1259
0
  hc->filter_list[0].filter = pdf_new_sanitize_filter;
1260
0
  hc->filter_list[0].options = &hc->sanitize_opts;
1261
0
  hc->filter_list[1].filter = NULL;
1262
0
  hc->filter_list[1].options = NULL;
1263
1264
0
  hc->page = page;
1265
0
}
1266
1267
static void
1268
pdf_clip_page_links(fz_context *ctx, struct clip_filter_state *hc)
1269
0
{
1270
0
  pdf_obj *annots;
1271
0
  pdf_obj *link;
1272
0
  fz_rect area;
1273
0
  int k;
1274
1275
0
  annots = pdf_dict_get(ctx, hc->page->obj, PDF_NAME(Annots));
1276
0
  k = 0;
1277
0
  while (k < pdf_array_len(ctx, annots))
1278
0
  {
1279
0
    link = pdf_array_get(ctx, annots, k);
1280
0
    if (pdf_dict_get(ctx, link, PDF_NAME(Subtype)) == PDF_NAME(Link))
1281
0
    {
1282
0
      area = pdf_dict_get_rect(ctx, link, PDF_NAME(Rect));
1283
0
      if (fz_is_empty_rect(fz_intersect_rect(area, hc->clip)))
1284
0
      {
1285
0
        pdf_array_delete(ctx, annots, k);
1286
0
        continue;
1287
0
      }
1288
0
    }
1289
0
    ++k;
1290
0
  }
1291
0
}
1292
1293
static void
1294
pdf_clip_page_annotations(fz_context *ctx, struct clip_filter_state *hc)
1295
0
{
1296
0
  pdf_annot *annot;
1297
0
  fz_rect area;
1298
1299
0
restart:
1300
0
  for (annot = pdf_first_annot(ctx, hc->page); annot; annot = pdf_next_annot(ctx, annot))
1301
0
  {
1302
0
    if (pdf_annot_type(ctx, annot) == PDF_ANNOT_FREE_TEXT)
1303
0
    {
1304
0
      area = pdf_dict_get_rect(ctx, pdf_annot_obj(ctx, annot), PDF_NAME(Rect));
1305
0
      if (fz_is_empty_rect(fz_intersect_rect(area, hc->clip)))
1306
0
      {
1307
0
        pdf_delete_annot(ctx, hc->page, annot);
1308
0
        goto restart;
1309
0
      }
1310
0
    }
1311
0
  }
1312
0
}
1313
1314
void
1315
pdf_clip_page(fz_context *ctx, pdf_page *page, fz_rect clip)
1316
0
{
1317
0
  pdf_document *doc;
1318
0
  struct clip_filter_state hc;
1319
0
  fz_matrix page_ctm, inv_page_ctm;
1320
1321
0
  if (page == NULL)
1322
0
    return;
1323
1324
0
  doc = page->doc;
1325
1326
0
  pdf_page_transform(ctx, page, NULL, &page_ctm);
1327
0
  inv_page_ctm = fz_invert_matrix(page_ctm);
1328
0
  clip = fz_transform_rect(clip, inv_page_ctm);
1329
1330
0
  init_clip_filter(ctx, &hc, page, clip);
1331
1332
0
  pdf_begin_operation(ctx, doc, "Apply hard clip to page");
1333
0
  fz_try(ctx)
1334
0
  {
1335
0
    pdf_filter_page_contents(ctx, doc, page, &hc.filter_opts);
1336
0
    pdf_clip_page_links(ctx, &hc);
1337
0
    pdf_clip_page_annotations(ctx, &hc);
1338
0
    pdf_end_operation(ctx, doc);
1339
0
  }
1340
0
  fz_catch(ctx)
1341
0
  {
1342
0
    pdf_abandon_operation(ctx, doc);
1343
0
    fz_rethrow(ctx);
1344
0
  }
1345
0
}
1346
1347
/* Vectorisation of pages */
1348
1349
struct vectorize_filter_state {
1350
  pdf_filter_options filter_opts;
1351
  pdf_vectorize_filter_options vectorize_opts;
1352
  pdf_filter_factory filter_list[2];
1353
  pdf_page *page;
1354
};
1355
1356
static
1357
void init_vectorize_filter(fz_context *ctx, struct vectorize_filter_state *hc, pdf_page *page)
1358
0
{
1359
0
  memset(&hc->filter_opts, 0, sizeof hc->filter_opts);
1360
0
  memset(&hc->vectorize_opts, 0, sizeof hc->vectorize_opts);
1361
1362
0
  hc->filter_opts.recurse = 0;
1363
0
  hc->filter_opts.instance_forms = 0;
1364
0
  hc->filter_opts.ascii = 0;
1365
0
  hc->filter_opts.opaque = hc;
1366
0
  hc->filter_opts.filters = hc->filter_list;
1367
0
  hc->filter_opts.recurse = 1;
1368
1369
0
  hc->vectorize_opts.opaque = hc;
1370
1371
0
  hc->filter_list[0].filter = pdf_new_vectorize_filter;
1372
0
  hc->filter_list[0].options = &hc->vectorize_opts;
1373
0
  hc->filter_list[1].filter = NULL;
1374
0
  hc->filter_list[1].options = NULL;
1375
1376
0
  hc->page = page;
1377
0
}
1378
1379
void
1380
pdf_vectorize_page(fz_context *ctx, pdf_page *page)
1381
0
{
1382
0
  pdf_document *doc;
1383
0
  struct vectorize_filter_state hv;
1384
1385
0
  if (page == NULL)
1386
0
    return;
1387
1388
0
  doc = page->doc;
1389
1390
0
  init_vectorize_filter(ctx, &hv, page);
1391
1392
0
  pdf_begin_operation(ctx, doc, "Vectorize text to page");
1393
0
  fz_try(ctx)
1394
0
  {
1395
0
    pdf_filter_page_contents(ctx, doc, page, &hv.filter_opts);
1396
0
    pdf_end_operation(ctx, doc);
1397
0
  }
1398
0
  fz_catch(ctx)
1399
0
  {
1400
0
    pdf_abandon_operation(ctx, doc);
1401
0
    fz_rethrow(ctx);
1402
0
  }
1403
0
}