How to apply Reductions before display the document

WebViewer Version:
10.3.0

Do you have an issue with a specific file(s)?
NO

Can you reproduce using one of our samples or online demos?
NO

Are you using the WebViewer server?
NO

Does the issue only happen on certain browsers?
NO

Is your issue related to a front-end framework?
NO

Is your issue related to annotations?
YES

Please give a brief summary of your issue:
(Think of this as an email subject)
How to apply Reductions before display the document

Please describe your issue and provide steps to reproduce it:
(The more descriptive your answer, the faster we are able to help you)

Dear WebViewer Support Staff:
This is Asahi Hayakawa from FUJIFILM SOFTWARE Co., Ltd.

We are developing an online proofing service using PDFTron SDK,
and the requirement is to hide the specified area before display the whole document on screen for security.

We tried to use the reduction as the following, however all of these seems to have some problems:

【Try 1】
call 「viewerInstance.Core.annotationManager.applyRedactions」method in the 「documentLoaded」 event
For pdf with a lot of pages, it seems that the page rendering is interfered by the redaction. As a result the blank pages are shown.

【Try 2】
call 「viewerInstance.Core.annotationManager.applyRedactions」method in the 「annotationLoaded」 event
It seems work this time but applyRedactions deletes the annotations overlapped with redaction annotations. We tried to redraw these annotations since it is necessary for users but it is not work. However, the redraw logic works perfectly when I did the same coding in documentLoaded event.

【Try 3】
call 「viewerInstance.Core.annotationManager.applyRedactions」method in the 「annotationLoaded」 event and redraw the annotations in 「documentLoaded」 event

Unfortunatly the area need to be hidden shows serval seconds when the pdf is large.

Can you help me about the problem?
if you need any further information please reply.

Thank you very much.

Please provide a link to a minimal sample where the issue is reproducible:
NO

1 Like

Hi there,

Thank you for contacting Support. We will provide you with more information on how to do this as soon as possible.

Thank you.

1 Like

Hi there,

Could you please provide the code and files you used for each attempt? Any videos or screenshots of console errors and the issue itself would also be helpful.

Best Regards,
Darian

1 Like

Dear darian:

Thank you for your reply.

Could you please provide the code and files you used for each attempt? Any videos or screenshots of console errors and the issue itself would also be helpful.

【Attempt 1】

Code:

    documentViewer.addEventListener('documentLoaded', () => {
      this.myMethod();
    });

  protected myMethod(): void {
    this.isDocumentLoadead = true;

    this.annotationListLogicService
      .callAnnotationListApi(this.manuscriptModel.$id)
      .subscribe({
        next: async (annotationInfoModels: AnnotationInfoModel[]) => {
          this.responseAnnotationInfoModels = annotationInfoModels;

          const redactInfoModels = annotationInfoModels.filter(
            (annotationInfoModel) =>
              annotationInfoModel.$manuscriptId === this.manuscriptModel.$id &&
              annotationInfoModel.$annotationContent.indexOf('</redact>') > -1
          );
          
          await this.redrawAnnotationFromXfdf(redactInfoModels);

          setTimeout(async () => {
            const redactionList = this.viewerInstance.Core.annotationManager
              .getAnnotationsList()
              .filter(
                (annot) =>
                  annot instanceof
                  this.viewerInstance.Core.Annotations.RedactionAnnotation
              );

            this.viewerInstance.Core.annotationManager.applyRedactions(
              redactionList
            );

            setTimeout(async () => {
              const notRedactAnnots = annotationInfoModels.filter(
                (annotationInfoModel) =>
                  annotationInfoModel.$annotationContent.indexOf(
                    '</redact>'
                  ) === -1
              );

              await this.redrawAnnotationFromXfdf(notRedactAnnots);

              this.setAnnotationReadOnlyAndNoReply();
            }, 800);
          }, 500);
        },
        error: (error) => {
          this.commonErrorHandleService.handleError(error);
        },
      });
  }

Video Capture:

【Attempt 2】

    documentViewer.addEventListener('annotationsLoaded', () => {
      this.myMethod();
    });

Video Capture:


The annotations overlapped with redaction (3 circles which are shown in attempt 1) are not shown.

【Attempt 3】

    documentViewer.addEventListener('annotationsLoaded', () => {
      setTimeout(async () => {
        const redactionList = this.viewerInstance.Core.annotationManager
          .getAnnotationsList()
          .filter(
            (annot) =>
              annot instanceof
              this.viewerInstance.Core.Annotations.RedactionAnnotation
          );
        await this.viewerInstance.Core.annotationManager.applyRedactions(
          redactionList
        );
        setTimeout(async () => {
          const notRedactAnnots = this.responseAnnotationInfoModels.filter(
            (annotationInfoModel) =>
              annotationInfoModel.$annotationContent.indexOf('</redact>') === -1
          );
          await this.redrawAnnotationFromXfdf(notRedactAnnots);
          this.setAnnotationReadOnlyAndNoReply();
        }, 800);
      }, 500);
    });
    
    documentViewer.addEventListener('documentLoaded', () => {
      this.myMethod();
    });
    
  protected myMethod(): void {
    this.isDocumentLoadead = true;

    this.annotationListLogicService
      .callAnnotationListApi(this.manuscriptModel.$id)
      .subscribe({
        next: async (annotationInfoModels: AnnotationInfoModel[]) => {
          this.responseAnnotationInfoModels = annotationInfoModels;

          const redactInfoModels = annotationInfoModels.filter(
            (annotationInfoModel) =>
              annotationInfoModel.$manuscriptId === this.manuscriptModel.$id &&
              annotationInfoModel.$annotationContent.indexOf('</redact>') > -1
          );
          
          await this.redrawAnnotationFromXfdf(redactInfoModels);
        },
        error: (error) => {
          this.commonErrorHandleService.handleError(error);
        },
      });
  }

Best Regards,
Asahi Hayakawa

1 Like

【Attempt 3】

Video Capture

1 Like

Hello,

We are currently looking into how to do this and will provide an update when we have more information.

Thank you.

2 Likes

Hello Asahi,

After discussing with my team, I think the best solution would be to apply the redactions before loading the document.

instance.Core.createDocument('/files/file.pdf', { extension: 'pdf' }).then(async doc => {
  const xfdf = (await doc.extractXFDF()).xfdfString;
  console.log(xfdf);

  // redact the document

  instance.UI.loadDocument(doc);
});

Once the document is fully loaded, you can try redrawing the annotations that were redacted with the XFDF string.

Note fullAPI must be set to true in the constructor when programattically applying redactions.
Redaction Guide: Apryse Documentation | Documentation

Thank you.

Best Regards,
Darian

2 Likes

Dear Darian:

Thank you for your reply.

I am sorry but could you give me a sample to redact a document using xfdf string or convert xfdf string to Core.Annotations.Annotation before loading the document?

I tried as the following

            this.viewerInstance.Core.annotationManager
              .importAnnotations(annotationInfo.$annotationContent)
              .then(
                (redactionList: Core.Annotations.Annotation[] | undefined) => {
                    await this.viewerInstance.Core.annotationManager.applyRedactions(
                      redactionList
                    );
                }
              )
              .catch((reason) => reject(reason));

but I got an error since importAnnotations is used before loading the document.

Error: Error: importAnnotations was called before the document was loaded. Please wait for the 'documentLoaded' event before calling 'importAnnotations'. See https://docs.apryse.com/documentation/web/guides/documentviewer/ for more info.

Best Regards,
Asahi Hayakawa

1 Like

Hello Asahi,

Thank you for the response.

It looks like we need to utilize ‘PDFNet’ in this case. Just to clarify a few things: you have a PDF document with annotations but no redactions, and you want to load it onto WebViewer to redact specific areas while preserving the annotations that were deleted by the redaction and the user should not see the redacted areas at all, is that correct?

This link shows how to search a document for specific keywords and redact those words before loading the document. It also contains sample code.

Here is the sample code for your use case. It should preserve all the annotations including the ones that were redacted before loading the document. You can import the annotations during the documentLoaded event.

fullAPI must be set to true in the WebViewer constructor.

// fullAPI: true
const { documentViewer, annotationManager, PDFNet } = instance.Core;
const { UI } = instance;

let xfdf;
const runScript = () => {

  const runRedaction = async doc => {
    // Relative path to the folder containing test files.

    const inputFilePath = '/files/document.pdf';

    try {

      //create document
      doc = await PDFNet.PDFDoc.createFromURL(inputFilePath);
      doc.initSecurityHandler();
      doc.lock();

      // save the XFDF string
      let fdfDoc = await doc.fdfExtract(PDFNet.PDFDoc.ExtractFlag.e_both);
      xfdf = await fdfDoc.saveAsXFDFAsString();
      console.log(xfdf);

      //the array holding all the redactions
      const redactionArray = [];

      // create redaction on page 1
      redactionArray.push(await PDFNet.Redactor.redactionCreate(1, (await PDFNet.Rect.init(0, 24.4, 371.03, 792)), false, ''));

      //sets the appearance of the redaction
      const app = {};
      app.redaction_overlay = true;
      app.border = false;
      app.show_redacted_content_regions = true;
      app.positive_overlay_color = await new PDFNet.ColorPt.init(0, 0, 0);
      app.redacted_content_color = await new PDFNet.ColorPt.init(0, 0, 0);
      await PDFNet.Redactor.redact(doc, redactionArray, app, false, false);
      return doc;

    } catch (err) {
      console.log(err);
    }
  };

  //calls the function to preprocess the document
  const main = async () => {
    let doc = null;

    try {
      return await runRedaction(doc);
    } catch (err) {
      console.log(err.stack);
    } finally {
      if (doc) {
        doc.unlock();
      }
    }
  };

  return PDFNet.runWithoutCleanup(main);
}

// loads webviewer and runs scripts after it's loaded
UI.addEventListener('viewerLoaded', () => {
  PDFNet.initialize()
    .then(() => runScript())
    .then(async doc => {
      UI.loadDocument(doc);
      console.log('finished script');
    });
});

// import annotations
documentViewer.addEventListener('documentLoaded', async () => {
  await annotationManager.importAnnotations(xfdf);
  console.log('annotations imported');
});

Annotations should be preserved with this method.

PDFNet: Apryse WebViewer Namespace: PDFNet
Create Redaction PDFNet: Apryse WebViewer Class: Redactor
PDFNet Create Redaction Sample Code: Apryse Documentation | Documentation

Best Regards,
Darian

2 Likes

Dear Darian:

Thank you for your support.

I am trying this with my colleague and I will contract you when the result comes out.

Best Regards,
Asahi Hayakawa

1 Like

Hello Asahi,

Yes, please let me know how it goes.

Best Regards,
Darian

1 Like

Dear Darian:

Sorry for the late reply.

I tried to do the coding referring to your sample these days,
and it seems that this is a good solution to my problem.

Thank you very much.

By the way, since our service also support document formatted in office(docx, xlsx, etc…) and pictures,
in this situation I got an error when doing

doc = await PDFNet.PDFDoc.createFromURL(downloadUrl);
{
  type: "PDFWorkerError",
  message: "Exception: \n\t Message: PDF header not found. The file is not a valid PDF document.\n\t Filename: Parser.cpp\n\t Function: SkipHeader\n\t Linenumber: �ֿѴ�\u000bI��?",
}

Is there any methods to resolve this problem?

Best Regards,
Asahi Hayakawa

1 Like

Hello Asahi,

You can convert the file to a PDF in this case.

In this example, we are creating an array buffer and then passing it into our runScript function.

Please look at this API page for more information.
https://docs.apryse.com/api/web/Core.html#.officeToPDFBuffer

 // loads webviewer and runs scripts after it's loaded
      UI.addEventListener('viewerLoaded', async () => {
          const buf = await Core.officeToPDFBuffer('https://pdftron.s3.amazonaws.com/downloads/pl/report.docx',{l: 'your_license_key'});
          runScript(buf)
          .then(async doc => {
            UI.loadDocument(doc);
            console.log('finished script');
          });
      });

In that function we can use createFromBuffer which returns an object of type: “PDFNet.PDFDoc”.

 let doc = await PDFNet.PDFDoc.createFromBuffer(buf);

Best Regards,
Darian

2 Likes