Interceptor-Beispiel: Annotations einbrennen ("Flatten annotations")

Ähnlich wie Interceptor-Beispiel: Annotationen vor dem Speichern sperren in der Wirkung, geht dieser Interceptor noch etwas weiter: Wenn der Einbrennen-Button gedrückt wird, werden alle Annotationen im PDF dauerhaft in das PDF integriert:

class EDAnnotationFlattener {
    // code fragements taken from https://docs.apryse.com/documentation/web/guides/annotation/flatten-annotations/
    buttonDataElement = 'custom_annotation_flattener_button';
    dialogDataElement = 'custom_annotation_flattener_dialog';
  
    async flattenAllAnnotations(instance) {
      const { documentViewer, PDFNet, annotationManager } = instance.Core;
  
      await PDFNet.initialize();
      const theDocument = await documentViewer.getDocument().getPDFDoc();
  
      // export annotations from the document
      const allAnnotations = await annotationManager.exportAnnotations();
  
      // Run PDFNet methods with memory management
      await PDFNet.runWithCleanup(async () => {
        // lock the document before a write operation
        // runWithCleanup will auto unlock when complete
        theDocument.lock();
  
        // import annotations to PDFNet
        const fdfDocument = await PDFNet.FDFDoc.createFromXFDF(allAnnotations);
        await theDocument.fdfUpdate(fdfDocument);
  
        // flatten all annotations in the document
        // see https://docs.apryse.com/api/web/Core.PDFNet.PDFDoc.html#flattenAnnotations__anchor
        await theDocument.flattenAnnotations();
  
        // clear the original annotations
        annotationManager.deleteAnnotations(annotationManager.getAnnotationsList());
      });
  
      // clear the cache (rendered) data with the newly updated document
      documentViewer.refreshAll();
  
      // Update viewer to render with the new document
      documentViewer.updateView();
  
      // Refresh searchable and selectable text data with the new document
      documentViewer.getDocument().refreshTextData();
    }
  
    initEditorConfiguration(instance) {
      // Create a new action button
      const aButton = {
        dataElement: this.buttonDataElement,
        type: 'actionButton',
        img: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fire" viewBox="0 0 16 16"><path d="M8 16c3.314 0 6-2 6-5.5 0-1.5-.5-4-2.5-6 .25 1.5-1.25 2-1.25 2C11 4 9 .5 6 0c.357 2 .5 4-2 6-1.25 1-2 2.729-2 4.5C2 14 4.686 16 8 16Zm0-1c-1.657 0-3-1-3-2.75 0-.75.25-2 1.25-3C6.125 10 7 10.5 7 10.5c-.375-1.25.5-3.25 2-3.5-.179 1-.25 2 1 3 .625.5 1 1.364 1 2.25C11 14 9.657 15 8 15Z"/></svg>',
        title: 'Alle Anmerkungen einbrennen',
        onClick: () => instance.UI.openElements([this.dialogDataElement]),
      };
  
      // Add the new button to the header
      instance.UI.setHeaderItems((header) => {
        header.push(aButton);
      });
  
      const body = document.createElement("div");
      body.innerHTML = "Alle Anmerkungen werden unwiderruflich in das PDF integriert. Sind Sie sicher, dass Sie dies möchten?";
  
      const modal = {
          dataElement: this.dialogDataElement,
          header: {
            title: 'Annotationen einbrennen',
          },
          body: {
            className: 'myCustomModal-body',
            style: {textAlign: "center"}, // optional inline styles
            children: [body], // HTML dom elements
          },
          footer: {
            className: 'myCustomModal-footer footer',
            style: {}, // optional inline styles
            children: [
              {
                title: 'Abbrechen',
                button: true,
                style: {},
                className: 'modal-button cancel-form-field-button',
                onClick: async (e) => {
                  instance.UI.closeElements([this.dialogDataElement]);
                },
              },
              {
                title: 'Einbrennen',
                button: true,
                style: {},
                className: 'modal-button confirm ok-btn',
                onClick: async (e) => {
                  await this.flattenAllAnnotations(instance);
                  instance.UI.closeElements([this.dialogDataElement]);
                },
              },
            ],
          },
        };
        instance.UI.addCustomModal(modal);
    }
  
    async updateEditorConfiguration(instance, info, config) {
      if (info.mode != 'edit') {
        instance.UI.disableElements([this.buttonDataElement]);
      } else {
        instance.UI.enableElements([this.buttonDataElement]);
      }
    }
  }
  
  window.ed.registerInterceptor(new EDAnnotationFlattener());

„Installiert“ werden kann dies wie üblich durch einfaches Abspeichern im Interceptors-Verzeichnis als .js-Datei:

Hinweis: Natürlich schützt dies nicht vor technisch versierter Manipulation des PDFs. So etwas lässt sich nur durch digitale Signaturen oder der Ablage in einem revisionssicheren Archiv verhindern.

4 „Gefällt mir“

Sehr gute Erweiterung.

Unsere Mitarbeiter hätten den Wunsch, wenn man das Einbrennen verwendet, dass vorab die letzte Version als Variante abgespeichert wird.

Wäre dies möglich?

1 „Gefällt mir“

Hallo @SDonath,
habt ihr diese Frage kürzlich über Optimal Systems Hannover an uns herangetragen? Wir sind derzeit mit OSVH im Austausch. Gerne frage ich den Kollegen dort zum aktuellen Stand nochmal an.

1 „Gefällt mir“

Ich hatte bei einem Telefonat darauf verwiesen. Schön das daraus schon eine Anfrage an euch erfolgte. Wenn es etwas neues gibt würden wir uns sehr freuen.

Hallo @rk,

beim bisherigen Einbrennen von Annotationen lassen sich diese in einem externen Programm zwar nicht löschen, jedoch kopieren, sodass diese anderweitig direkt wieder eingefügt werden können.
Lässt sich de Interceptor so anpassen, dass die Annotationen weder gelöscht noch kopiert bzw. ausgewählt werden können, sondern wirklich fest mit dem Dokument verschmolzen sind?

Hallo @hesslinl, ich weiss nicht, ob ich die Frage richtig verstehe.

PDFs sind digitale Dokumente, das Kopieren und Einfügen ist somit immer möglich, allenfalls als Screenshot.

(Ziel-) Dokumente wirklich zu schützen würde eine digitale Signatur, die Archivierung in enaio ähnliches erfordern.

Wenn es lediglich darum geht, das Kopieren von Elemente komplizierter zu machen, könnte das (Quell-) Dokument in ein Bild umgewandelt werden, welches dann wieder als PDF gespeichert werden könnte. Das Kopieren von Stempeln, Anmerkungen und Texten wäre dann erheblich erschwert, die Zugänglichkeit würde aber auch stark leiden. Dies würde ich vermutlich Server-seitig in einem enaio-Job implementieren.

Ich könnte mir auch vorstellen, alle Stempel und übrigen Bildelemente, die nicht zur Textebene gehören, in ein Hintergrundbild zu verschmelzen und somit etwas schwieriger extrahierbar zu machen.

1 „Gefällt mir“