📝 Architecture Specification of alfviewer
This project was initiated because I was not happy with how my PDF reader worked. I decided to make some customizations on top of PDFjs (open source, the Firefox pdf-viewer). I wanted to be able to integrate views of pdfs containing images into html pages and show a pdf page one at a time and also control how it looked on my iPhone..
A sample of this implementation can be viewed at Gallery Docs Presentation or a bit more dynamic version at Photo Tips
Technical Architecture Specification: alfviewer & PDF.js Integration
1. Architectural Philosophy and System Overview
The alfviewer framework was architected as a defensive solution to the performance and usability limitations inherent in standard browser-based PDF readers. Specifically, the project was initiated to address sub-optimal rendering of image-heavy documents on mobile hardware, such as the iPhone, where traditional viewers often fail to maintain layout integrity or responsiveness. The strategic goal was to transform the robust but monolithic PDF.js library into a modular, lightweight orchestration layer optimized for single-page rendering and granular mobile control.
The system operates as a three-tier architectural stack:
Web Page (UI Layer): The entry point containing the
#viewerContainerand#pageWrapper, managing the structural context for the HTML5 canvas and floating UI controls.alfviewer.js Engine (Logic Wrapper): An intermediary orchestration layer that abstracts the complexity of PDF.js, handling responsive scaling calculations, state management, and command routing.
PDF.js (Core Rendering Library): The foundational engine that performs heavy-duty document parsing and manages the worker-thread lifecycle.
This decoupled approach ensures high system maintainability. By isolating the UI logic from the core rendering engine, developers can iterate on the interface—implementing themes or custom navigation—without destabilizing the underlying document processing logic. This separation is physically enforced through a strict directory hierarchy.
2. Physical Architecture and Directory Structure
A clean file hierarchy is critical to preventing dependency bloat
and ensuring that library updates do not overwrite custom viewer
configurations. The project structure isolates the alfviewer
logic from the vendor library, facilitating independent version
control and simplified deployment.
The physical file structure is organized as follows:
index.html: The document root that initializes the viewer container.pdfjs/alfviewer/alfviewer.js: The primary logic engine for loading, rendering, and dynamic scaling.pdfjs/alfviewer/alfviewer.css: The presentation layer governing floating controls and mobile-optimized layouts.pdfjs/pdfjs/pdf.js: The core PDF.js library responsible for document processing.pdfjs/pdfjs/pdf.worker.js: The worker script that enables off-thread parsing to maintain UI thread availability.
Separating the alfviewer/ custom
assets from the pdfjs/ vendor directory
is an intentional architectural guardrail. It allows the core PDF.js
library to be updated to newer versions without requiring a rewrite
of the specialized scaling and UI synchronization logic.
3. Core Execution Logic: The JavaScript Engine
The rendering lifecycle is inherently resource-intensive,
particularly with image-dense PDFs. To prevent UI degradation, the
alfviewer engine employs an asynchronous
execution model that delegates document parsing to a dedicated worker
thread.
The execution begins with loadPDF(url),
which handles:
Promise-Based Fetching: Invokes
pdfjsLib.getDocument(url).promiseto retrieve the document object.Global State Initialization: Stores the document in a global
pdfDocvariable and resetscurrentPageto 1.Pipeline Trigger: Calls the rendering sequence immediately upon successful initialization.
The renderPage() function
serves as the system's primary execution node, following a five-step
pipeline:
Page Retrieval: Fetches the specific page object from the
pdfDocviapdfDoc.getPage().Scale Calculation: Invokes
calculateScale()to determine the zoom level based on current hardware context.Viewport Creation: Generates a viewport object using the calculated scale.
Canvas Resizing: Dynamically adjusts the HTML5 canvas dimensions to match the viewport.
Contextual Rendering: Directs the engine to draw the content onto the canvas context.
Architectural Analysis: The Role of Async/Await The use of
async/await is not merely for syntax; it
is a critical performance guard. Functions like getDocument
and page.render return Promises.
By leveraging async/await, the engine
prevents complex callback nesting and, more importantly, ensures that
the main UI thread is not blocked during heavy GPU/CPU cycles. This
prevents the browser from becoming "janky" or unresponsive
during page transitions, effectively eliminating race conditions
between the worker thread and the UI rendering.
4. Dynamic Scaling and Viewport Computational Logic
Cross-platform PDF display requires solving the challenge of
variable screen aspect ratios. The calculateScale()
function acts as a responsive controller, dynamically adjusting the
zoom to match the hardware environment.
Device Context |
Condition / Breakpoint |
Calculated Result |
Desktop |
|
|
Mobile Portrait |
|
|
Mobile Landscape |
|
|
This orientation-aware scaling is managed via an orientationchange
event listener. A strategic 200ms debounce delay is
implemented before re-rendering. This ensures that the engine does
not perform calculations while the browser is still animating the
viewport transition, preventing visual layout glitches.
5. UI Synchronization and State Management
Maintaining a perfect sync between the internal engine state
(currentPage) and visual indicators is
paramount for user confidence. This is managed by the following
functions:
updatePageIndicator(): Accesses thepdfDoc.numPagesproperty to provide real-time feedback (e.g., "Page 1 of 10") in the overlay.debugOverlay(message): Programmatically creates or updates a fixed diagnostic layer for real-time developer feedback.
The system utilizes "idempotent" functions such as
resetZoom() and fitPage().
Specifically, fitPage() sets a
forceFitPage state flag before
re-triggering the renderPage() cycle.
This state-driven approach ensures that repeated user interactions
produce a consistent, predictable visual output without accumulating
scaling errors.
6. Developmental Framework: Debugging and Extensibility
To optimize production performance, the debug UI is isolated into
a separate Server Side Include (SSI), alfviewer-debug.shtml.
This prevents production environments from loading unnecessary
HTML/JS overhead.
Implementation Requirements for
alfviewer-debug.shtml:
Include Marker: The SSI must set
window.ALF_VIEWER_DEBUG_INCLUDED = trueto inform the core engine of its presence.CSS Guard Logic: To prevent a "flash-of-debug" during initialization, the following CSS guard is mandatory:
Priority Order for Debug Activation: The engine checks for debug requirements in this specific order:
Include Marker (Is the SSI present?)
URL Parameter (
?debug)Config Object (
ALF_VIEWER_CONFIG.debug)
Extensibility Options:
Fit Modes: Can be extended to include
fitHeight()orsetZoom(level)by modifying thecalculateScale()logic.Theming: Supports Light/Dark modes by toggling a
.dark-modeclass on the<body>element.Keyboard Shortcuts: Customizable via
keydownlisteners inalfviewer.js(e.g., "D" for debug toggle).
7. Performance Engineering and Mobile Optimization
Mobile devices are constrained by limited memory and CPU. To
mitigate this, alfviewer prioritizes
rendering efficiency.
Advanced Optimization Strategies (Future Roadmap): While the core engine focuses on immediate rendering, the following strategies are recommended for high-load document sets:
Page Caching: Utilizing a
pageCacheobject to store renderedcanvasobjects. This allows instantaneous navigation back to previously viewed pages without re-invoking the full rendering pipeline.Lazy Rendering: Implementing an
IntersectionObserverto trigger rendering only when thumbnails or pages enter the viewport.
Current Mobile Enhancements:
Touch-Friendly Controls:
.floatBtnelements are scaled to a minimum of 48px via media queries to ensure reliable touch targets.Layout Safety: The
#viewerContainerenforcesoverflow-x: hiddento prevent accidental horizontal swiping on small screens.
Pre-Deployment Checklist:
SSI Cleanup: Remove or comment out the
<!--#include virtual="/.../alfviewer-debug.shtml" -->line.Config Verification: Ensure
ALF_VIEWER_CONFIG.debugis set tofalse.Cache Invalidation: Perform a hard reload to ensure the browser fetches the latest
alfviewer.js.UI Verification: Confirm that the
#debugOverlayis not created or visible in the DOM when the include marker is absent.