Executing TextSearchInit multiple times

PDF.js Express Version 7.3.0

Detailed description of issue
I followed the advanced search example of the documentation to annotate texts. However i did not trigger the TextSearchInit from documentLoad but later after a server response. The server response is an array with search terms.

Based on the server response TextSearchInit is then executed multiple times (the onDocumentEnd event handler triggers the search for the next search term).

The onResult event handler is only triggered for the results of the first search term. For each other search term onResult is not triggered.

When i skip the first search term, the second search term is found and its results are highlighted without problems.

Do i have to reset the search somehow or is it not possible to execute multiple searches after another with TextSearchInit?

Expected behaviour
I expect that all searches return results.

Does your issue happen with every document, or just one?
With multiple documents.

Code snippet

 drawNextAnnotation() {
    if(this.annotationsQueue && this.annotationsQueue.length) {
      const nextAnnotation = this.annotationsQueue[0];

      this.drawAnnotation(nextAnnotation.phrase, nextAnnotation.state);

      this.annotationsQueue.splice(0, 1);
    }
  }

  drawAnnotation(phrase, state) {
    var { docViewer, CoreControls, annotManager, Annotations } = this.wvInstance;

    const colorMap = {
      'ok': new Annotations.Color(83, 142, 21, 0.3),
      'danger': new Annotations.Color(194, 10, 10, 0.2),
      'warning': new Annotations.Color(161, 152, 2, 0.3),
      'inactive': new Annotations.Color(166, 166, 166, 0.3)
    }

    const searchMode = CoreControls.Search.Mode.HIGHLIGHT;

    const searchOptions = {
      fullSearch: true,
      onResult: (result) => {
        if (result.resultCode === CoreControls.Search.ResultCode.FOUND) {
          console.log(result);
          if (!result.quads.length) return;

          const color = colorMap[state];
          const textQuad = result.quads[0].getPoints();
          const annot = new Annotations.TextHighlightAnnotation();

          annot.X = textQuad.x1;
          annot.Width = textQuad.x2 - textQuad.x1;
          annot.PageNumber = result.pageNum;
          annot.Y = textQuad.y3;
          annot.Height = textQuad.y1 - textQuad.y3;

          annot.FillColor = color;
          annot.StrokeColor = color;

          annot.Quads = [textQuad];

          annotManager.addAnnotation(annot);
          annotManager.redrawAnnotation(annot);
        }
      },
      onDocumentEnd: (result) => {
        this.drawNextAnnotation();
      }
    }

    docViewer.textSearchInit(phrase, searchMode, searchOptions);
  }

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:

Hey there!

I believe the issue is actually with your drawNextAnnotation function. Since you are calling drawNextAnnotation recursively, you are never splicing annotationsQueue until after all the searches are done.

If you move this.annotationsQueue.splice(0, 1); to be called before this.drawAnnotation, I believe that should fix your problem.

Thanks!
Logan

Hi Logan,

thanks for your reply. Unfortunately that did not solve the issue.

I moved cleaned up the code a bit for better readability and added some debug lines:

   drawNextAnnotation() {
    if(this.annotationsQueue && this.annotationsQueue.length) {
      const nextAnnotation = this.annotationsQueue[0];

      this.annotationsQueue.splice(0, 1);

      console.log('drawNextAnnotation: ' + nextAnnotation.phrase);
      this.drawAnnotation(nextAnnotation.phrase, nextAnnotation.state);
    }
  }

  drawAnnotation(phrase, state) {
    console.log('drawAnnotation(): ' + phrase);
    var { docViewer, CoreControls, annotManager, Annotations } = this.wvInstance;

    const searchMode = CoreControls.Search.Mode.HIGHLIGHT;

    const searchOptions = {
      fullSearch: true,
      onResult: (result) => {
        if (result.resultCode === CoreControls.Search.ResultCode.FOUND) {
          console.log('onResult: ' + result);
        }
      },
      onDocumentEnd: (result) => {
        this.drawNextAnnotation();
      }
    }

    docViewer.textSearchInit(phrase, searchMode, searchOptions);
  }

The output in the console looks like this:

Here “Passivhaus” is the first searchterm and “Plusenergiehaus” would be the second search term but never gets any results although it is in the PDF and if change the order of the searchterm “Plusenergiehaus” would be found (and “Passivhaus” wouldn’t).

Thanks again for your help!

Kind regards

Stefan

Hi again,

i just found out that when i use setTimeout() to delay the second search just by a bit it works fine.

onDocumentEnd: (result) => {
  setTimeout(() => {
    this.drawNextAnnotation();
  }, 100);
},

Not sure why though and i think it’s more like a hacky workaround than a real fix.

Kind regards,

Stefan

Hey there,

I tried a more realistic test case and now I can reproduce this issue. I believe this is due to the JS feature where callback functions are executed synchronously. onDocumentEnd is executed synchronously, and therefore the internal search state is not being reset before the next call to textSearchInit.

Refactoring the code to use a promise chain fixes the issue for me. (This is basically the same fix as your setTimeout, except a bit less “hacky”)

    const queue = ['pdf', 'viewer'];

    function drawAnnotations() {
      if (queue && queue.length) {
        let chain = Promise.resolve();
        for (const nextAnnotation of queue) {
          chain = chain.then(() => {
            return drawAnnotation(nextAnnotation)
          })
        }
      }
    }
  
    function drawAnnotation(phrase) {
      return new Promise(resolve => {
        console.log('drawAnnotation(): ' + phrase);
      
        const searchMode = CoreControls.Search.Mode.HIGHLIGHT;
    
        const searchOptions = {
          fullSearch: true,
          onResult: (result) => {
            if (result.resultCode === CoreControls.Search.ResultCode.FOUND) {
              console.log('onResult: ' + result);
            }
          },
          onDocumentEnd: (result) => {
            resolve();
          }
        }
    
        docViewer.textSearchInit(phrase, searchMode, searchOptions);

      })

    }

    drawAnnotations();

Thanks!
Logan

That work’s like a charm. Thank you very much for your help!

Kind regards,

Stefan

HI Logan,

To copy all the annotaions from version document to another version document,
I used your code to perform the search and create annotaions in the new document.

My document contains nearly 500- 1000 annotaions and mapping them using the the above function taking almost 1hr.
Is there away to make this process fast.