Add options to redaction for line art removal.
authorRobin Watts <[email protected]>
Wed, 17 Jan 2024 19:17:23 +0000 (19:17 +0000)
committerSebastian Rasmussen <[email protected]>
Thu, 8 Feb 2024 12:39:18 +0000 (20:39 +0800)
include/mupdf/pdf/page.h
platform/gl/gl-annotate.c
platform/java/jni/pdfannotation.c
platform/java/jni/pdfpage.c
platform/java/mupdf_native.h
platform/java/src/com/artifex/mupdf/fitz/PDFAnnotation.java
platform/java/src/com/artifex/mupdf/fitz/PDFPage.java
source/pdf/pdf-clean.c
source/pdf/pdf-op-filter.c
source/pdf/pdf-outline.c
source/tools/murun.c

index e52f1bd07e5a2fa5bc680fbfd51f1262b3c7a6f9..37a723c21f2317fe839ae5ef7a8cedbaa649b7d3 100644 (file)
@@ -163,10 +163,17 @@ enum {
        PDF_REDACT_IMAGE_PIXELS,
 };
 
+enum {
+       PDF_REDACT_LINE_ART_NONE,
+       PDF_REDACT_LINE_ART_REMOVE_IF_COVERED,
+       PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED
+};
+
 typedef struct
 {
        int black_boxes;
        int image_method;
+       int line_art;
 } pdf_redact_options;
 
 int pdf_redact_page(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_redact_options *opts);
index 93743d2e92277de1d6012062899dcd9ab310ee30..76165e26f9682f4f50b750966439aed7af7a4213 100644 (file)
@@ -1434,7 +1434,7 @@ void do_redact_panel(void)
        int i;
 
        int num_redact = 0;
-       static pdf_redact_options redact_opts = { 1, PDF_REDACT_IMAGE_PIXELS };
+       static pdf_redact_options redact_opts = { 1, PDF_REDACT_IMAGE_PIXELS, PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED };
        int search_valid;
 
        if (pdf_has_redactions_doc != pdf)
@@ -1481,9 +1481,10 @@ void do_redact_panel(void)
        if (ui_button_aux("Redact Page", num_redact == 0))
        {
                ui_select_annot(NULL);
-               trace_action("page.applyRedactions(%s, %d);\n",
+               trace_action("page.applyRedactions(%s, %d, %d);\n",
                        redact_opts.black_boxes ? "true" : "false",
-                       redact_opts.image_method);
+                       redact_opts.image_method,
+                       redact_opts.line_art);
                pdf_redact_page(ctx, pdf, page, &redact_opts);
                trace_page_update();
                load_page();
index 4caca5236f56f14bb2a1e5c5fcfb27ee84a7e456..f8f1b2849e2153cd081765bbeb02489fb52248e7 100644 (file)
@@ -1599,11 +1599,11 @@ FUN(PDFAnnotation_getHiddenForEditing)(JNIEnv *env, jobject self)
 }
 
 JNIEXPORT jboolean JNICALL
-FUN(PDFAnnotation_applyRedaction)(JNIEnv *env, jobject self, jboolean blackBoxes, jint imageMethod)
+FUN(PDFAnnotation_applyRedaction)(JNIEnv *env, jobject self, jboolean blackBoxes, jint imageMethod, jint lineArt)
 {
        fz_context *ctx = get_context(env);
        pdf_annot *annot = from_PDFAnnotation_safe(env, self);
-       pdf_redact_options opts = { blackBoxes, imageMethod };
+       pdf_redact_options opts = { blackBoxes, imageMethod, lineArt };
        jboolean redacted = JNI_FALSE;
 
        if (!ctx || !annot) return JNI_FALSE;
index cbce8ec1ed2f27334e9b980facbb9d1320f85cd2..c8f04cf7bcde9ca9dd4fb61a180fe90043481ae9 100644 (file)
@@ -72,12 +72,12 @@ FUN(PDFPage_update)(JNIEnv *env, jobject self)
 }
 
 JNIEXPORT jboolean JNICALL
-FUN(PDFPage_applyRedactions)(JNIEnv *env, jobject self, jboolean blackBoxes, jint imageMethod)
+FUN(PDFPage_applyRedactions)(JNIEnv *env, jobject self, jboolean blackBoxes, jint imageMethod, jint lineArt)
 {
        fz_context *ctx = get_context(env);
        pdf_page *page = from_PDFPage(env, self);
        jboolean redacted = JNI_FALSE;
-       pdf_redact_options opts = { blackBoxes, imageMethod };
+       pdf_redact_options opts = { blackBoxes, imageMethod, lineArt };
 
        if (!ctx || !page) return JNI_FALSE;
 
index c903263c3f635a6d30590c0ed607e7455eb31ab7..edfa464e32c5fb0989d7e51907ef141246ff5708 100644 (file)
@@ -3535,10 +3535,10 @@ JNIEXPORT void JNICALL Java_com_artifex_mupdf_fitz_PDFAnnotation_setHiddenForEdi
 /*
  * Class:     com_artifex_mupdf_fitz_PDFAnnotation
  * Method:    applyRedaction
- * Signature: (ZI)Z
+ * Signature: (ZII)Z
  */
 JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_PDFAnnotation_applyRedaction
-  (JNIEnv *, jobject, jboolean, jint);
+  (JNIEnv *, jobject, jboolean, jint, jint);
 
 #ifdef __cplusplus
 }
@@ -4766,6 +4766,12 @@ extern "C" {
 #define com_artifex_mupdf_fitz_PDFPage_REDACT_IMAGE_REMOVE 1L
 #undef com_artifex_mupdf_fitz_PDFPage_REDACT_IMAGE_PIXELS
 #define com_artifex_mupdf_fitz_PDFPage_REDACT_IMAGE_PIXELS 2L
+#undef com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_NONE
+#define com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_NONE 0L
+#undef com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_IF_TOUCHED
+#define com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_IF_TOUCHED 1L
+#undef com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_IF_COVERED
+#define com_artifex_mupdf_fitz_PDFPage_REDACT_LINEART_IF_COVERED 2L
 /*
  * Class:     com_artifex_mupdf_fitz_PDFPage
  * Method:    getObject
@@ -4801,10 +4807,10 @@ JNIEXPORT void JNICALL Java_com_artifex_mupdf_fitz_PDFPage_deleteAnnotation
 /*
  * Class:     com_artifex_mupdf_fitz_PDFPage
  * Method:    applyRedactions
- * Signature: (ZI)Z
+ * Signature: (ZII)Z
  */
 JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_PDFPage_applyRedactions
-  (JNIEnv *, jobject, jboolean, jint);
+  (JNIEnv *, jobject, jboolean, jint, jint);
 
 /*
  * Class:     com_artifex_mupdf_fitz_PDFPage
index bfe45ad550fe207d3d6d4480747ce46dd70266de..37c1069bd6e75ec47a59ae8ae061c5d469606f5c 100644 (file)
@@ -340,5 +340,10 @@ public class PDFAnnotation
        public native boolean getHiddenForEditing();
        public native void setHiddenForEditing(boolean hidden);
 
-       public native boolean applyRedaction(boolean blackBoxes, int imageMethod);
+       public boolean applyRedaction(boolean blackBoxes, int imageMethod)
+       {
+               return applyRedaction(blackBoxes, imageMethod, 0);
+       }
+
+       public native boolean applyRedaction(boolean blackBoxes, int imageMethod, int lineArt);
 }
index d9ece0f8b4099a7a637b4e12f376bd98ddae9800..969175ec7a1daf21ebc54b8a9b9c15ebae2c351a 100644 (file)
@@ -39,10 +39,18 @@ public class PDFPage extends Page
        public static final int REDACT_IMAGE_REMOVE = 1;
        public static final int REDACT_IMAGE_PIXELS = 2;
 
-       public native boolean applyRedactions(boolean blackBoxes, int imageMethod);
+       public static final int REDACT_LINEART_NONE = 0;
+       public static final int REDACT_LINEART_IF_TOUCHED = 1;
+       public static final int REDACT_LINEART_IF_COVERED = 2;
+
+       public native boolean applyRedactions(boolean blackBoxes, int imageMethod, int lineArt);
 
        public boolean applyRedactions() {
-               return applyRedactions(true, REDACT_IMAGE_PIXELS);
+               return applyRedactions(true, REDACT_IMAGE_PIXELS, REDACT_LINEART_NONE);
+       }
+
+       public boolean applyRedactions(boolean blackBoxes, int imageMethod) {
+               return applyRedactions(blackBoxes, imageMethod, REDACT_LINEART_NONE);
        }
 
        public native boolean update();
index 180ddf86e1b1ff2d851c5e39d32bfcc0e5967740..281a50bc8103a3731cf53ea652da5c1924cf6bf5 100644 (file)
@@ -460,6 +460,7 @@ struct redact_filter_state {
        pdf_filter_factory filter_list[2];
        pdf_page *page;
        pdf_annot *target; // NULL if all
+       int line_art;
 };
 
 static void
@@ -822,14 +823,19 @@ pdf_redact_image_filter_pixels(fz_context *ctx, void *opaque, fz_matrix ctm, con
        return fz_keep_image(ctx, image);
 }
 
+/* Returns 0 if area does not intersect with any of our redactions.
+ * Returns 2 if area is completely included within one of our redactions.
+ * Returns 1 otherwise. */
 static int
-rect_touches_redactions(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_rect area, struct redact_filter_state *red)
+rect_touches_redactions(fz_context *ctx, fz_rect area, struct redact_filter_state *red)
 {
        pdf_annot *annot;
        pdf_obj *qp;
        fz_quad q;
-       fz_rect r;
+       fz_rect r, s;
        int i, n;
+       pdf_page *page = red->page;
+       pdf_document *doc = red->page->doc;
 
        for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
        {
@@ -845,17 +851,25 @@ rect_touches_redactions(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_r
                                {
                                        q = pdf_to_quad(ctx, qp, i);
                                        r = fz_rect_from_quad(q);
-                                       r = fz_intersect_rect(r, area);
-                                       if (!fz_is_empty_rect(r))
+                                       s = fz_intersect_rect(r, area);
+                                       if (!fz_is_empty_rect(s))
+                                       {
+                                               if (fz_contains_rect(r, area))
+                                                       return 2;
                                                return 1;
+                                       }
                                }
                        }
                        else
                        {
                                r = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
-                               r = fz_intersect_rect(r, area);
-                               if (!fz_is_empty_rect(r))
+                               s = fz_intersect_rect(r, area);
+                               if (!fz_is_empty_rect(s))
+                               {
+                                       if (fz_contains_rect(r, area))
+                                               return 2;
                                        return 1;
+                               }
                        }
                }
        }
@@ -863,14 +877,14 @@ rect_touches_redactions(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_r
 }
 
 static void
-pdf_redact_page_links(fz_context *ctx, pdf_document *doc, pdf_page *page, struct redact_filter_state *red)
+pdf_redact_page_links(fz_context *ctx, struct redact_filter_state *red)
 {
        pdf_obj *annots;
        pdf_obj *link;
        fz_rect area;
        int k;
 
-       annots = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
+       annots = pdf_dict_get(ctx, red->page->obj, PDF_NAME(Annots));
        k = 0;
        while (k < pdf_array_len(ctx, annots))
        {
@@ -878,7 +892,7 @@ pdf_redact_page_links(fz_context *ctx, pdf_document *doc, pdf_page *page, struct
                if (pdf_dict_get(ctx, link, PDF_NAME(Subtype)) == PDF_NAME(Link))
                {
                        area = pdf_dict_get_rect(ctx, link, PDF_NAME(Rect));
-                       if (rect_touches_redactions(ctx, doc, page, area, red))
+                       if (rect_touches_redactions(ctx, area, red))
                        {
                                pdf_array_delete(ctx, annots, k);
                                continue;
@@ -889,31 +903,51 @@ pdf_redact_page_links(fz_context *ctx, pdf_document *doc, pdf_page *page, struct
 }
 
 static void
-pdf_redact_page_annotations(fz_context *ctx, pdf_document *doc, pdf_page *page, struct redact_filter_state *red)
+pdf_redact_page_annotations(fz_context *ctx, struct redact_filter_state *red)
 {
        pdf_annot *annot;
        fz_rect area;
 
 restart:
-       for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+       for (annot = pdf_first_annot(ctx, red->page); annot; annot = pdf_next_annot(ctx, annot))
        {
                if (pdf_annot_type(ctx, annot) == PDF_ANNOT_FREE_TEXT)
                {
                        area = pdf_dict_get_rect(ctx, pdf_annot_obj(ctx, annot), PDF_NAME(Rect));
-                       if (rect_touches_redactions(ctx, doc, page, area, red))
+                       if (rect_touches_redactions(ctx, area, red))
                        {
-                               pdf_delete_annot(ctx, page, annot);
+                               pdf_delete_annot(ctx, red->page, annot);
                                goto restart;
                        }
                }
        }
 }
 
+static int culler(fz_context *ctx, void *opaque, fz_rect bbox, fz_cull_type type)
+{
+       struct redact_filter_state *red = opaque;
+
+       switch (type)
+       {
+       case FZ_CULL_PATH_FILL:
+       case FZ_CULL_PATH_STROKE:
+       case FZ_CULL_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)
+                       return (rect_touches_redactions(ctx, bbox, red) != 0);
+               return 0;
+       default:
+               return 0;
+       }
+}
+
 static
 void init_redact_filter(fz_context *ctx, pdf_redact_options *redact_opts, struct redact_filter_state *red, pdf_page *page, pdf_annot *target)
 {
        int black_boxes = redact_opts ? redact_opts->black_boxes : 0;
        int image_method = redact_opts ? redact_opts->image_method : PDF_REDACT_IMAGE_PIXELS;
+       int line_art = redact_opts ? redact_opts->line_art : PDF_REDACT_LINE_ART_NONE;
 
        memset(&red->filter_opts, 0, sizeof red->filter_opts);
        memset(&red->sanitize_opts, 0, sizeof red->sanitize_opts);
@@ -925,6 +959,7 @@ void init_redact_filter(fz_context *ctx, pdf_redact_options *redact_opts, struct
        red->filter_opts.filters = red->filter_list;
        if (black_boxes)
                red->filter_opts.complete = pdf_redact_end_page;
+       red->line_art = line_art;
 
        red->sanitize_opts.opaque = red;
        red->sanitize_opts.text_filter = pdf_redact_text_filter;
@@ -932,6 +967,7 @@ void init_redact_filter(fz_context *ctx, pdf_redact_options *redact_opts, struct
                red->sanitize_opts.image_filter = pdf_redact_image_filter_pixels;
        if (image_method == PDF_REDACT_IMAGE_REMOVE)
                red->sanitize_opts.image_filter = pdf_redact_image_filter_remove;
+       red->sanitize_opts.culler = culler;
 
        red->filter_list[0].filter = pdf_new_sanitize_filter;
        red->filter_list[0].options = &red->sanitize_opts;
@@ -943,11 +979,12 @@ void init_redact_filter(fz_context *ctx, pdf_redact_options *redact_opts, struct
 }
 
 static int
-pdf_apply_redaction_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_annot *target, pdf_redact_options *redact_opts)
+pdf_apply_redaction_imp(fz_context *ctx, pdf_page *page, pdf_annot *target, pdf_redact_options *redact_opts)
 {
        pdf_annot *annot;
        int has_redactions = 0;
        struct redact_filter_state red;
+       pdf_document *doc = page->doc;
 
        for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot)) {
                if (target != NULL && target != annot)
@@ -968,8 +1005,8 @@ pdf_apply_redaction_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_
        fz_try(ctx)
        {
                pdf_filter_page_contents(ctx, doc, page, &red.filter_opts);
-               pdf_redact_page_links(ctx, doc, page, &red);
-               pdf_redact_page_annotations(ctx, doc, page, &red);
+               pdf_redact_page_links(ctx, &red);
+               pdf_redact_page_annotations(ctx, &red);
 
                annot = pdf_first_annot(ctx, page);
                while (annot)
@@ -1001,11 +1038,13 @@ pdf_apply_redaction_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_
 int
 pdf_redact_page(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_redact_options *redact_opts)
 {
-       return pdf_apply_redaction_imp(ctx, doc, page, NULL, redact_opts);
+       if (page == NULL || page->doc != doc)
+               fz_throw(ctx, FZ_ERROR_GENERIC, "Can't redact a page not from the doc");
+       return pdf_apply_redaction_imp(ctx, page, NULL, redact_opts);
 }
 
 int
 pdf_apply_redaction(fz_context *ctx, pdf_annot *annot, pdf_redact_options *redact_opts)
 {
-       return pdf_apply_redaction_imp(ctx, annot->page->doc, annot->page, annot, redact_opts);
+       return pdf_apply_redaction_imp(ctx, annot->page, annot, redact_opts);
 }
index 5f69324247181d3129fbeae81130ac96ef301cfc..2e100aad103a68f9d3f44d6b9c7b6265957fff86 100644 (file)
@@ -1442,7 +1442,7 @@ cull_replay_rectto(fz_context *ctx, void *arg, float x1, float y1, float x2, flo
        pdf_sanitize_processor *p = (pdf_sanitize_processor *)arg;
 
        if (p->chain->op_re)
-               p->chain->op_re(ctx, p->chain, x1, y1, x2, y2);
+               p->chain->op_re(ctx, p->chain, x1, y1, x2-x1, y2-y1);
 }
 
 typedef struct
@@ -1469,13 +1469,15 @@ end_segment(fz_context *ctx, segmenter_data_t *sd)
                cull_replay_rectto
        };
        fz_rect r;
+       const fz_stroke_state *st;
 
        if (sd->segment == NULL)
                return;
 
-       r = fz_bound_path(ctx, sd->segment, (sd->type == FZ_CULL_PATH_STROKE || sd->type == FZ_CULL_PATH_FILL_STROKE) ? &sd->sstate : NULL, sd->ctm);
+       st = (sd->type == FZ_CULL_PATH_STROKE || sd->type == FZ_CULL_PATH_FILL_STROKE) ? &sd->sstate : NULL;
+       r = fz_bound_path(ctx, sd->segment, st, sd->ctm);
 
-       if (sd->p->options->culler(ctx, sd->p->options->opaque, r, sd->type))
+       if (sd->p->options->culler && sd->p->options->culler(ctx, sd->p->options->opaque, r, sd->type))
        {
                /* This segment can be skipped */
        }
index 689476f7d38368bcf0427d181a09990e3fdefdee..393708b8092ec9974e4f2e21b86db151a880cc2d 100644 (file)
@@ -56,7 +56,7 @@ pdf_test_outline(fz_context *ctx, pdf_document *doc, pdf_obj *dict, pdf_mark_bit
 
                parent_diff = pdf_objcmp(ctx, parent, expected_parent);
                prev_diff = pdf_objcmp(ctx, prev, expected_prev);
-               last_diff = next == NULL && pdf_objcmp(ctx, last, dict);
+               last_diff = next == NULL && pdf_objcmp_resolve(ctx, last, dict);
 
                if (fixed == NULL)
                {
index d7666d6a8189eaa9ad0739508b295e6908ea9f16..004ee971ba4405a77e7995d5f107c21b86afeef5 100644 (file)
@@ -7833,9 +7833,10 @@ static void ffi_PDFPage_applyRedactions(js_State *J)
 {
        fz_context *ctx = js_getcontext(J);
        pdf_page *page = js_touserdata(J, 0, "pdf_page");
-       pdf_redact_options opts = { 1, PDF_REDACT_IMAGE_PIXELS };
+       pdf_redact_options opts = { 1, PDF_REDACT_IMAGE_PIXELS, 0 };
        if (js_isdefined(J, 1)) opts.black_boxes = js_toboolean(J, 1);
        if (js_isdefined(J, 2)) opts.image_method = js_tointeger(J, 2);
+       if (js_isdefined(J, 3)) opts.line_art = js_tointeger(J, 3);
        fz_try(ctx)
                pdf_redact_page(ctx, page->doc, page, &opts);
        fz_catch(ctx)
@@ -9142,9 +9143,10 @@ static void ffi_PDFAnnotation_applyRedaction(js_State *J)
 {
        fz_context *ctx = js_getcontext(J);
        pdf_annot *annot = ffi_toannot(J, 0);
-       pdf_redact_options opts = { 1, PDF_REDACT_IMAGE_PIXELS };
+       pdf_redact_options opts = { 1, PDF_REDACT_IMAGE_PIXELS, 0 };
        if (js_isdefined(J, 1)) opts.black_boxes = js_toboolean(J, 1);
        if (js_isdefined(J, 2)) opts.image_method = js_tointeger(J, 2);
+       if (js_isdefined(J, 3)) opts.line_art = js_tointeger(J, 3);
        fz_try(ctx)
                pdf_apply_redaction(ctx, annot, &opts);
        fz_catch(ctx)
@@ -10316,7 +10318,7 @@ int murun_main(int argc, char **argv)
                jsB_propfun(J, "PDFPage.createAnnotation", ffi_PDFPage_createAnnotation, 1);
                jsB_propfun(J, "PDFPage.deleteAnnotation", ffi_PDFPage_deleteAnnotation, 1);
                jsB_propfun(J, "PDFPage.update", ffi_PDFPage_update, 0);
-               jsB_propfun(J, "PDFPage.applyRedactions", ffi_PDFPage_applyRedactions, 2);
+               jsB_propfun(J, "PDFPage.applyRedactions", ffi_PDFPage_applyRedactions, 3);
                jsB_propfun(J, "PDFPage.process", ffi_PDFPage_process, 1);
                jsB_propfun(J, "PDFPage.toPixmap", ffi_PDFPage_toPixmap, 6);
                jsB_propfun(J, "PDFPage.getTransform", ffi_PDFPage_getTransform, 0);
@@ -10419,7 +10421,7 @@ int murun_main(int argc, char **argv)
                jsB_propfun(J, "PDFAnnotation.getIsOpen", ffi_PDFAnnotation_getIsOpen, 0);
                jsB_propfun(J, "PDFAnnotation.setIsOpen", ffi_PDFAnnotation_setIsOpen, 1);
 
-               jsB_propfun(J, "PDFAnnotation.applyRedaction", ffi_PDFAnnotation_applyRedaction, 2);
+               jsB_propfun(J, "PDFAnnotation.applyRedaction", ffi_PDFAnnotation_applyRedaction, 3);
                jsB_propfun(J, "PDFAnnotation.process", ffi_PDFAnnotation_process, 1);
 
                jsB_propfun(J, "PDFAnnotation.getHiddenForEditing", ffi_PDFAnnotation_getHiddenForEditing, 0);