PDF & TIFF VIEWER

Render TIFF and PDF in the browser.

No browser displays a multi-page TIFF. The Abscode Browser JS SDK decodes every page of a TIFF, PDF or other image to a JPEG on the user's own device, no upload, no server-side rendering step, no round-trip per page.

Runs on device Works offline TIFF, PDF & images Progressive rendering
WHY A VIEWER IS HARD

The formats your users have aren't the formats browsers show

Scanners, fax gateways and document archives emit multi-page TIFF. Browsers don't decode it. Most teams solve this by shipping every page to a server and pulling rendered images back.

TIFF doesn't render

No mainstream browser decodes TIFF in an img tag, and a multi-page TIFF has no concept a browser could show even if it did. The file is opaque until something decodes it.

PDF display isn't yours to control

The built-in PDF viewer is the browser's, not your application's. You can embed it; you cannot lay it out, tint it, thumbnail it, or hand its pixels to the rest of your UI.

Server rendering costs you twice

Upload the document, render each page, cache the output, serve it back. That is bandwidth, latency, storage of customer documents, and a viewer that stops working the moment the network does.

The SDK does the decoding where the file already is

The Abscode Browser JS SDK is WebAssembly. It loads once, then reads the document's bytes as a Uint8Array, one line off any File, and returns JPEG bytes per page. The document never leaves the tab, nothing is uploaded, and once the page is loaded the viewer keeps working with no network at all.

THE VIEWER PATTERN

Outline first, pixels second

Read the document's structure before rendering anything. You get page count and per-page dimensions instantly, so the scroll container can be laid out correctly while the pages are still being produced.

1
Load the SDK
One script tag. Await helper.ready, then initialize(). The wasm runs on the user's device from here on.
2
Read the outline
readImageInformation returns total pages plus width, height, colour type and DPI for every page.
3
Reserve the slots
You know each page's aspect ratio before a single pixel exists, size the placeholders now and the viewer never reflows.
4
Stream the pages
compressToJPGStream delivers each page the moment it is produced. Page one paints while page forty is still working.
CODE

A working viewer, end to end

This is the whole pattern: inspect, lay out, stream. Every method resolves to {STATUS, DESCRIPTION, DATA} and never throws, so error handling is a branch rather than a try block.

  • ✓ Works the same for a 40-page TIFF and a 40-page PDF
  • ✓ Page metadata before any rendering starts
  • ✓ Each page painted as it lands, with progress
  • ✓ Worker pool keeps the scroll thread free
viewer.js
// Worker-pool build, rendering stays off the scroll thread.
const helper = new ImageWizHelper();
await helper.ready;
await helper.initialize(LICENSE_BLOB);

const toU8 = async (file) =>
  new Uint8Array(await file.arrayBuffer());

async function openDocument(file) {
  // 1. Outline: pages, size, colour type, DPI.
  const info = await helper.readImageInformation(await toU8(file));
  if (!info.STATUS) return showError(info.DESCRIPTION);

  // 2. Lay the viewer out before any pixels exist.
  info.DATA.INFORMATION.forEach(p =>
    addPlaceholder(p.PAGE_NO, p.WIDTH, p.HEIGHT, p.COLOR_TYPE, p.DPI));

  // 3. Render resolution for on-screen viewing.
  await helper.SetDPI(150);

  // 4. Stream, each page arrives as it is produced.
  //    Re-read the file: the worker took the first buffer.
  const res = await helper.compressToJPGStream([await toU8(file)], (page) => {
    // page = { index, total, name, fileData, progress }
    const blob = new Blob([page.fileData], { type: 'image/jpeg' });
    paintPage(page.index, URL.createObjectURL(blob));
    setProgress(page.progress);
  });

  if (!res.STATUS) showError(res.DESCRIPTION);
  // res.DATA, every page's JPEG bytes, also collected for you.
}
DOCUMENT METADATA

Everything a viewer shell needs, before rendering

One call to readImageInformation with the document's bytes returns the file size in KB, the total page count, and a per-page record.

TOTAL_PAGES

The page count for the whole document, the number your pager, thumbnail rail and scroll height are built from.

WIDTH · HEIGHT

Per-page pixel dimensions. Reserve each slot at the true aspect ratio so pages drop in without shifting the layout.

COLOR_TYPE

Per page: BW, GREYSCALE or COLOR. Badge a scan, or route bitonal pages down a lighter path.

DPI

The page's own resolution, enough to show real-world size, or to decide what render DPI is actually worth requesting.

Just need the page count?

In the main-thread build, GetPageCountInImageFile(file) takes a File object directly, no reading it into a buffer first, and returns the count in DATA. Ideal for labelling files in an upload list before anyone opens one. In the worker build, use readImageInformation and read TOTAL_PAGES.

RENDER CONTROL

You choose what each pass costs

Rendering settings are ordinary setter calls that apply to the pages you render next, so a thumbnail pass and a full-page pass are the same code with different numbers.

Thumbnails vs. full page

SetDPI accepts any DPI, 100 for a thumbnail rail, 300 for the page the reader is actually on. Same document, two passes.

Custom canvas

SetCustomPageLayout takes stretch (11) or aspect-fit (12) with explicit pixel dimensions and a fill colour, a fixed thumbnail size or a bespoke target.

Filters per page

SetFilter applies Normal (0), Black & White (200) or Grayscale (201) as each page is rendered.

Fixed page sizes

SetPageLayout(0..6) maps to A0 through A6; a value outside that range falls back to A4 rather than failing.

Two run modes

The main-thread build is the simplest thing that works. The worker-pool build moves rendering off the UI thread, same method names, same responses.

All pages at once

If you don't need progressive paint, compressToJPG returns one JPEG per page as a single array when the whole document is done.

FAQ

Questions before you build

Can a browser display a multi-page TIFF without an SDK?
No. Mainstream browsers don't decode TIFF at all, and multi-page TIFF has no native display path even where single-page images render. The SDK decodes the file on the device and hands you JPEG bytes per page, which every browser renders in an ordinary img tag.
Which file types can the viewer open?
Point it at whatever your users actually have, JPEG, PNG, single- or multi-page TIFF, and PDF, plus other common image formats. The SDK reads the bytes, decodes them, and renders each page to a JPEG your interface can display like any other image. Multi-page TIFF and PDF fan out one page at a time, so a 40-page file comes back as 40 rendered pages.
Are documents uploaded anywhere to be rendered?
No. The SDK is WebAssembly running in the user's tab. It reads the bytes you hand it and returns bytes, there is no upload and no server round-trip per page. Once the page has loaded, rendering works with no network connection.
Does rendering freeze the UI on a large document?
It can, in the main-thread build, that build runs the wasm synchronously and is intended for the simple, one-document case. The worker-pool build runs the same API across background workers instead, which is the right choice for a viewer. Both expose identical method names and the same response contract.
Can it render large multi-page TIFF and PDF files in the browser?
Yes. Rendering happens on the device, so there is no server limit to hit, and the worker-pool build spreads pages across background workers to keep a large multi-page TIFF or PDF responsive. Paired with compressToJPGStream the first page paints while the rest are still decoding, so open time stays flat as page count grows.
How do I show page one before the whole document is rendered?
Use compressToJPGStream. It produces one JPEG per page and delivers each page as it is produced, rather than all of them at the end. Each callback receives { index, total, name, fileData, progress }, so you can paint immediately and drive a progress bar from the same event. The final result still contains every page's bytes.
Why is my file's buffer empty the second time I render it?
In the worker build, input ArrayBuffers are transferred to the worker, which detaches them on the caller's side. That's a deliberate trade for speed, no copy. Re-read the File whenever you need the bytes again, as the sample above does before streaming.
What do I get back if a page fails to render?
Every method resolves to { STATUS, DESCRIPTION, DATA } and a failure never throws across the API. Check STATUS; when it's false, DESCRIPTION carries the error message. There is no exception path to guard.
Can I evaluate it without a licence key?
Yes. initialize() accepts an empty licence blob and the SDK still initializes, every operation works, and rendered pages carry a watermark band. Wire the whole viewer up first, add the key when you're ready. Talk to us about licensing.
Are there any hosting requirements?
Serve the SDK over HTTP(S), the wasm will not load from a file:// URL, and keep the SDK's files together in one directory, since the loader resolves its siblings relative to its own script URL.
THE FULL TOOLKIT

Explore the rest of the Browser JS Library

One license, every capability — take this and you also get all of these.

Build the viewer on the device

Get a key and render your first multi-page TIFF today.