Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Lessons Learned: Extracting Page Numbers from PDF.js

Tech Oct 7 15

When working on a project that required retrieving the current page number from an embedded PDF viewer, I encountered several obstacles that took considerable time to resolve. This article documents the challenges I faced and the solutions I eventually implemented.

Initial Setup

The PDF viewer was hosted at an endpoint configured by the backend team, which deployed PDF.js on the server. I won't go into detail about the server-side configuration since I'm primarily a frontend developer.

My initial approach was to download PDF.js and serve it from the frontend. This quickly revealed a critical issue: cross-origin resource sharing (CORS) restrictions prevented communication between the embedded viewer and the parent page.

Problem 1: Accessing the iframe Context

After embedding the PDF using an iframe, I attempted to access the contentWindow and retrieve the PDFViewerApplication instance. However, the initial method failed because standard iframe paths don't establish the necessary connection with the PDF.js application layer. While the PDF displayed correctly, I coulnd't interact with the underlying API.

Problem 2: CORS Errors When Self-Hosting

I then tried hosting PDF.js locally by placing it in the public static directory and using absolute paths in the source code. This approach successfully exposed the PDFViewerApplication object, but triggered CORS errors. Despite extensive searching, I couldn't find a satisfactory workaround for this issue.

Problem 3: Timing Issues with Async Loading

After deploying to production, a new problem emerged: the iframe became unreachable, even though it worked perfectly in the local environment. The root cause was a race condition between code execution and page rendering. The script attempted to access the iframe before it finished loading, resulting in an undefined reference.

Adding a setInterval timer to poll for the iframe's readiness seemed like a reasonable solution. However, another issue surfaced: the timer variable was being cleared before the entire code block executed, causing the interval to terminate prematurely.

Final Working Solution

The following implementation handles these timing complexities effectively:

<iframe
  v-else-if="resourceType === 'pdf'"
  id="previewPDF"
  ref="pdfPreview"
  :src="`https://example.com/pdf/${resource_id}`"
  width="100%"
  height="100%"
  class="iframe-content"
></iframe>

retrievePdfPages() {
  if (this.pollingTimer) {
    clearInterval(this.pollingTimer);
  }
  
  this.currentPageIndex = 1;
  const pdfContainer = this.$refs.pdfPreview.contentWindow;
  console.log('PDF frame reference:', pdfContainer);
  console.log('PDFViewerApplication available:', pdfContainer.PDFViewerApplication);
  
  this.pollingTimer = setInterval(() => {
    this.iterationCount++;
    console.log('Attempt:', this.iterationCount);
    
    if (this.iterationCount > 24) {
      clearInterval(this.pollingTimer);
      return;
    }
    
    if (pdfContainer && pdfContainer.PDFViewerApplication) {
      clearInterval(this.pollingTimer);
      const totalPages = pdfContainer.PDFViewerApplication.pagesCount;
      
      if (totalPages === 0 || totalPages === undefined) {
        console.info("Document loading in progress...");
      } else {
        if (totalPages < 2) {
          console.log(`Page 1 of 1, progress: 100%`);
        } else {
          this.currentPageIndex = pdfContainer.PDFViewerApplication.page;
          console.log("Initial load complete, page:", this.currentPageIndex);
          
          pdfContainer.document
            .getElementById("viewerContainer")
            .addEventListener("scroll", event => {
              const pdfData = pdfContainer.PDFViewerApplication;
              if (this.currentPageIndex !== pdfData.page) {
                this.currentPageIndex = pdfData.page;
                console.log("Scroll detected, current page:", this.currentPageIndex);
                this.fetchPageInfo(this.resource_id, this.currentPageIndex);
              }
            });
        }
      }
    }
  }, 500);
}

The key improvements in this implementation include proper variable scoping for the timer and a polling mechanism that gracefully handles the asynchronous nature of iframe loading.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.