← All tools
πŸ“‘

Duplex Scan Assembler β€” Merge Front and Back PDF Pages Online

Free tool to assemble duplex scans: combines two PDFs (front pages + back pages) into one correctly ordered document. 100% client-side, no upload.

+Upload PDF or photoor drag & drop files hereπŸš€ Launch Tool for Free

How It Works

01

Open the Duplex tool

Click the Duplex Scan tool in the main app toolbar.

02

Upload front and back PDFs

In the dialog, load two PDFs: the front pages scan and the back pages scan.

03

Set back page order

If the back PDF was scanned in reverse order, toggle the Reverse Back option.

04

Wait for processing

The app reads both files and interleaves pages: Front₁ / Back₁ / Frontβ‚‚ / Backβ‚‚...

05

Get the ordered gallery

All pages are interleaved in correct order β€” export your final PDF.

Why pdfredX

πŸ”’

Full Privacy

Files never leave your browser. No upload to third-party servers.

⚑

Client-Side Processing

All computations happen locally β€” fast, secure and free.

πŸ†“

No Registration

Just open your browser and start. No accounts needed.

πŸ“±

Works on All Devices

Desktop, tablet, and mobile β€” no installation required.

Related Articles

Duplex Scan PDF: How to Merge Two ADF Passes Into One Document
When you scan a stack of double-sided pages through an automatic document feeder (ADF), the scanner runs two passes: all front sides first, all back sides second. You end up with two separate PDF files instead of one correctly ordered document. Most online tools have no idea how to handle this structure. The duplex scan organizer in pdfredX does exactly that β€” entirely in your browser, with no file ever leaving your device.

When you scan a stack of double-sided pages through an automatic document feeder (ADF), the scanner runs two passes: all front sides first, all back sides second. You end up with two separate PDF files instead of one correctly ordered document. Most online tools have no idea how to handle this structure. The duplex scan organizer in pdfredX does exactly that β€” entirely in your browser, with no file ever leaving your device.


Step 01. Loading Your Files

Open pdfredX and drag both PDF files into the upload zone β€” or browse to them through your file picker. Load them in the right order: front side first (odd pages, face-up stack), back side second (even pages, reverse stack). The order matters β€” it determines which file the algorithm treats as the starting point.

After upload, the gallery immediately applies color coding: a green bar on cards marks front-side pages, a blue bar marks back-side pages. This isn't decoration. It reflects the internal keys pdfSrcKey: 'duplex:front:...' and 'duplex:back:...' inside the items[] array β€” the same keys the interleaving algorithm reads during assembly. If the colors are swapped, the files were loaded in the wrong order; remove them and reload.


Step 02. Configuring the Duplex Settings

Click the Duplex Scan button in the toolbar. This opens the #DM side panel with three controls.

File assignment. Two dropdowns: Front and Back. If you loaded the files in the right order, the assignment fills in automatically based on load sequence. If not β€” reassign manually, it takes five seconds.

Reverse back (dmReverseBack). The most critical toggle. Most ADFs flip the paper stack after the first pass β€” back pages are scanned in reverse order: the last sheet comes out first. Without accounting for this, the interleaving breaks: Front₁ β†’ Backβ‚… β†’ Frontβ‚‚ β†’ Backβ‚„ instead of Front₁ β†’ Back₁ β†’ Frontβ‚‚ β†’ Backβ‚‚.

With reverse enabled, the algorithm reads the back-side array right to left: index backLen - 1 - i instead of i. One line of math, but it's the difference between a garbage document and a correctly ordered one. How to tell if your scanner needs it: enable the toggle and scroll through the gallery preview. If page 2 is now the back side of page 1 β€” you're done. If not β€” flip the toggle.

Live order preview. The gallery updates the card order in real time as you change settings. You can scroll all pages and verify the interleaving before committing to the final assembly.


Step 03. Assembly and Export

Click Apply. The algorithm rebuilds the items[] array in milliseconds β€” this is a pure index-reordering operation, no re-rendering involved. The gallery immediately reflects the final order: Front₁ / Back₁ / Frontβ‚‚ / Backβ‚‚ / ...

Quick start from the landing page. If you arrived at pdfredX through the Duplex Scan feature page, the browser has already saved you a step. js/features-landing.js writes openTool=duplex to sessionStorage and redirects to the main app. On load, the application reads this parameter and automatically opens the duplex panel β€” no need to hunt for the button in the toolbar. Just drop your files and configure the reverse toggle.

Export. Click Download PDF. jsPDF 2.5.1 iterates over the reordered items[] array: for each page it calls addImage(editUrl, ...) and auto-detects A4 orientation from the image proportions. The file is assembled entirely in browser memory and downloaded directly β€” no server queue, no waiting for a remote response.


How It Works Under the Hood

Each uploaded PDF is rendered page by page through PDF.js 3.11.174. Every page becomes a rasterized dataURL in Canvas and is stored in the items[] array with a source key. Two PDFs produce two separate arrays with distinct pdfSrcKey values.

During duplex assembly, nothing is re-rendered. The algorithm builds a new index array: alternating items[i] from the front array and items[j] from the back array, where j = i or j = backLen - 1 - i depending on the dmReverseBack flag. The entire operation is JavaScript array manipulation β€” no heavy render pipeline. On a 200-page document this takes under 100 milliseconds.


Known Limitations

Rasterized output only. PDF.js renders pages to Canvas, so the output is raster images, not vector text. Text in the final PDF can't be selected as text β€” it's part of the image layer. If you need a live text layer, use the Area OCR tool on the relevant pages after assembly.

Mobile performance. Documents over 80–100 pages render noticeably slower on low-end phones: PDF.js keeps every page in memory as ImageData. On desktop this is nearly imperceptible.

Mismatched scan resolution. If front and back were scanned at different DPI or orientation, the interleaving will work correctly but the visual result will be inconsistent. Use the rotate tool to align page orientations before running the duplex assembly.

Browser memory cap. Practical limit is around 200–300 pages per tab depending on available RAM. Beyond that, Chrome begins evicting cache, and re-rendering slows down the assembly.


If you scanned a book in spread mode β€” where each page contains two leaves β€” that's a different workflow. We have a separate guide for it: Split Book Spread PDF into Two Pages.

Try it now β€” pdfredx.com, no sign-up, your files stay on your device.

Duplex Scan PDF and Data Privacy: What Happens to Your Document the Moment You Hit Upload
You're scanning a signed employment contract, an NDA, or an HR intake form with passport copies attached. The scanner produces two files β€” front and back. To merge them into a single correctly ordered document, you need a tool. You open the first service that comes up in search, click Upload β€” and at that moment, your document is no longer entirely yours.

You're scanning a signed employment contract, an NDA, or an HR intake form with passport copies attached. The scanner produces two files β€” front and back. To merge them into a single correctly ordered document, you need a tool. You open the first service that comes up in search, click Upload β€” and at that moment, your document is no longer entirely yours.

Here's exactly what happens in that moment. And why processing PDF files directly inside the browser isn't a marketing line β€” it's an architectural decision with measurable consequences for your document's privacy.


Where Your Documents Actually Go With Standard Cloud Services

When you upload a file to a typical online PDF service, here's what takes place:

The file leaves your device and travels to a remote server. It physically moves to a data center β€” possibly in another country, under a different legal jurisdiction, on infrastructure you have no visibility into or control over.

The file gets cached for processing. To handle your request, the service writes your file to temporary storage. The service promises to delete it after an hour, a day, a week β€” but you cannot verify this. You're trusting a privacy policy drafted by that service's own lawyers.

Metadata ends up in server logs. Upload timestamp, your IP address, file name, file size, browser fingerprint β€” all of this is recorded in server-side logs. Logs are retained far longer than the files themselves, and they travel through backup systems you never agreed to.

The service may use your content. Some platforms explicitly reserve the right in their terms of service to process uploaded content for product improvement, algorithm training, or analytics. If you didn't read the terms β€” you consented.

A data breach puts your documents in the leak. Server-side incidents happen regularly across the industry. If your file was sitting in cache when a breach occurred, it could end up in a data dump. Employment records, financial statements, legal agreements β€” all of it has real value on the secondary market.

For a lawyer, this is an attorney-client privilege exposure. For an HR professional, it's a potential violation of employee personal data protection obligations. For an accountant, it's confidential client financial data crossing a boundary it should never cross.


Complete Browser Isolation: How pdfredX Processes Your Files

pdfredX does not upload your files to any server. This isn't a promise from an "About Us" page β€” it's a consequence of the application's architecture, which you can verify.

PDF.js renders your file directly inside your browser tab. The PDF.js 3.11.174 library runs within your open tab and reads your PDF file locally through the browser's FileReader API. No network request occurs at this step. The file never leaves your machine.

Page data is stored in the tab's working memory. Each rendered page becomes a dataURL β€” an encoded image string β€” and is stored in the items[] array in the browser's RAM. This array exists only inside your open tab. Close the tab and it's gone.

sessionStorage holds only interface state. Tool settings, panel configuration, the openTool parameter passed from the feature landing page β€” all of this lives in sessionStorage, which is isolated within the browser and never transmitted to a server. This is a browser security standard, not something we engineered specifically.

jsPDF assembles the final document on the client. When you click Download PDF, jsPDF 2.5.1 composes the document in the tab's memory and hands it directly to the browser for download. It goes to your Downloads folder β€” not to a server first, then to you.

The application works fully offline after the page loads. If you want to verify the isolation: open pdfredX, load your files, then disconnect from the internet. The tool continues working because all processing is local. The network connection is only needed to load the application itself.


Secure Duplex Scan Processing: Step by Step

01. Load your files

Drag both PDFs β€” front pass and back pass β€” into the upload zone at pdfredx.com. The browser's FileReader API reads the files locally. They do not leave the device at this step or any subsequent step. Pages appear in the gallery immediately after local rendering.

02. Configure the page order

Open the Duplex Scan panel. Assign which file is the front side and which is the back side. Enable the reverse back toggle (dmReverseBack) if your ADF scanner flips the stack between passes β€” which most do. The gallery shows a live preview of the resulting page order. All of this happens inside the tab's memory.

03. Export and close

Click Apply, then Download PDF. jsPDF assembles the document locally and passes it to the browser for download. After you close the tab, all file data is destroyed β€” nothing persists in sessionStorage or in browser memory.


What We Cannot Guarantee β€” An Honest Engineering Disclaimer

The client-side architecture protects your document from being transmitted to external servers. But it is not a complete shield against every threat, and overstating that protection would be dishonest.

Malware or a keylogger already on your device. If your machine has compromised software installed, it may be reading files from disk or intercepting data at the OS level. A browser application has no visibility into or control over what runs outside the browser sandbox.

Browser extensions with broad permissions. Some extensions request access to read all tab content. If such an extension is installed and active, it can see data rendered in the page DOM. For sensitive documents, work in a browser profile with no third-party extensions, or use a private browsing window.

The downloaded file on your local disk. Once the PDF lands in your Downloads folder, its security is your responsibility: full-disk encryption, access permissions, retention policy.

The visit record in your browser history. Your browser logs the pdfredx.com URL. The document itself is never logged anywhere outside your device, but the visit is. Use a private window if that matters to you.

We think being clear about these limits is part of building trustworthy software β€” not a reason to avoid using it.


Process your duplex scans without uploading anything to a server β€” pdfredx.com, no account required, no subscription.

Browser-Side PDF Duplex Assembly: A Deep Dive into the PDF.js + jsPDF Pipeline
When I started designing the duplex module in pdfredX, the task looked deceptively simple: take two arrays of pages and interleave them. In practice, the real engineering work lives in the details β€” page storage format, memory management, handling the non-standard behavior of ADF scanners, and the hard limits of the browser runtime.

When I started designing the duplex module in pdfredX, the task looked deceptively simple: take two arrays of pages and interleave them. In practice, the real engineering work lives in the details β€” page storage format, memory management, handling the non-standard behavior of ADF scanners, and the hard limits of the browser runtime.

This article walks through the architectural decisions behind pdfredX's duplex tool: why we chose the PDF.js + jsPDF stack, how the index-permutation algorithm works, how dmReverseBack changes the traversal step from i to backLen - 1 - i, and where performance actually hits a wall on real devices.


The Architectural Challenge: What "Merging Two PDFs" Actually Means in a Browser

On a server, this is trivial: receive two files over HTTP, parse their PDF structure, reorder pages through a library like PyMuPDF or iTextSharp, return the result. The browser imposes a different set of constraints.

Why you can't just parse the PDF structure on the client. The PDF format is a complex binary graph: cross-reference tables (xref), compressed object streams, fonts with substitution maps, ICC profiles embedded inside ColorSpace dictionaries. Reading this structure in JavaScript without a server backend means either shipping a full PDF parser to the browser β€” heavyweight, fragile on malformed files β€” or working with a rasterized representation: rendering pages to Canvas first, then operating on pixel data and dataURL strings.

Choosing raster is a deliberate tradeoff. PDF.js 3.11.174 is the industry standard for rendering PDFs to Canvas. It handles 99% of real-world files correctly, including PDFs with digital signatures, optional content layers, and non-standard font encodings. The cost: the original document's text layer is lost. Each page becomes an image. For the duplex scanning use case β€” where source files are already scans, meaning raster from the start β€” this is an acceptable tradeoff.

Why jsPDF over pdf-lib. pdf-lib allows working with the PDF structure without rasterizing, which theoretically preserves the text layer. In practice, its stability on real-world scanned files with non-standard metadata was lower than the PDF.js β†’ Canvas β†’ jsPDF pipeline. The extra rasterization step has a cost, but the pipeline behaves predictably across files of any origin.


Deep Dive: Algorithm Implementation

Page Storage: The items[] Array

Each uploaded PDF is rendered page by page through PDF.js. Rendering happens into lowerCanvasEl β€” the lower canvas layer of the Fabric.js instance, which in unannotated mode acts as the direct working surface. Each page's render result is converted to a dataURL via canvas.toDataURL('image/jpeg', 0.92) and pushed into the shared items[] array.

Each element of the array holds:

{
  id:        string,       // unique identifier, e.g. 'pdf-001-page-3'
  editUrl:   string,       // dataURL of the current page state
  origUrl:   string,       // dataURL of the original β€” never mutates
  pdfSrcKey: string,       // 'duplex:front:filename.pdf' or 'duplex:back:filename.pdf'
  fromPdf:   true,
  isEdited:  boolean
}

The pdfSrcKey field prefixed with duplex:front: or duplex:back: is what lets the assembly algorithm unambiguously identify each page's origin and build two separate sub-arrays for interleaving.

The Index-Permutation Algorithm

After both PDFs are loaded, we have two sub-arrays. Call them F (front, length fLen) and B (back, length bLen). The algorithm builds a new result array R.

Base case β€” reverse disabled:

for (let i = 0; i < Math.max(fLen, bLen); i++) {
  if (i < fLen) R.push(F[i]);
  if (i < bLen) R.push(B[i]);
}
// Result: F[0], B[0], F[1], B[1], F[2], B[2], ...

With dmReverseBack enabled:

for (let i = 0; i < Math.max(fLen, bLen); i++) {
  if (i < fLen) R.push(F[i]);
  const j = bLen - 1 - i;           // traverse back array right to left
  if (j >= 0) R.push(B[j]);
}
// Result: F[0], B[bLen-1], F[1], B[bLen-2], F[2], B[bLen-3], ...

This is precisely what happens when an ADF flips the paper stack between passes: the physically last sheet in the stack becomes the first scanned back page. Without reversing the index to j = bLen - 1 - i, every front page ends up paired with the wrong back page.

Note the Math.max(fLen, bLen) bound: the algorithm correctly handles asymmetric cases where front and back contain different page counts β€” a dropped sheet during scanning, an inherently one-sided document. Unpaired pages from the longer array are appended to the end of the result without a partner.

Final Assembly via jsPDF

After index permutation, items[] is reordered β€” but nothing has been re-rendered. The permutation was pure JavaScript reference manipulation. The actual page renders happened during the initial file upload.

When the user clicks Download PDF, makePDF() runs:

const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
for (const [idx, item] of items.entries()) {
  const { w, h } = imgDims(item.editUrl);           // read image proportions
  const orient = w > h ? 'landscape' : 'portrait';  // auto-detect orientation
  if (idx > 0) doc.addPage([210, 297], orient);
  doc.addImage(item.editUrl, 'JPEG', 0, 0, 210, 297);
}
doc.save('output.pdf');

The key detail: imgDims is called independently for each page. This matters for mixed-orientation documents β€” a contract body in portrait, its appendices in landscape β€” which are common in real-world scanning workflows.


Performance Tradeoffs and Browser Memory Limits

Why Raster Storage Is Expensive

Every page is stored as a JPEG dataURL. For an A4 sheet scanned at 150 DPI: - Resolution: 1240 Γ— 1754 pixels - JPEG at quality 0.92: ~300–600 KB per page depending on content density - In-memory decoded bitmap for Canvas operations: 1240 Γ— 1754 Γ— 4 bytes (RGBA) β‰ˆ 8.7 MB per page

For a 100-page document, decoded bitmaps alone can occupy ~870 MB. Chrome limits tab RAM to approximately 2–4 GB depending on version and system resources. As the tab approaches this limit, the browser begins evicting inactive objects from memory, triggering re-renders when the user scrolls back through the gallery.

In practice, this translates to three distinct performance zones:

Page countMobileDesktop
Up to 80No noticeable lagSmooth
80–200Gallery scroll lagAcceptable
200+Re-render pauses 1–3 s/pageNoticeable slowdown

Why We Don't Use a Web Worker for Rendering

The obvious next step is moving PDF.js rendering into a Web Worker to avoid blocking the main thread. The problem: the Canvas API is not available inside a Worker directly β€” only via OffscreenCanvas, which had unstable Safari support at the time of implementation. Passing ImageData between Worker and main thread via postMessage with Transferable objects adds per-page overhead that partially offsets the threading benefit. For current document volumes, the UX gain doesn't justify the implementation complexity. This is acknowledged technical debt.

Why jsPDF Holds the Entire Document in Memory

jsPDF assembles the full document in memory before doc.save() is called. For 200+ page documents this means that at peak β€” during export β€” browser memory simultaneously holds decoded page bitmaps, jsPDF's internal byte buffers, and the final PDF blob. Peak memory during export can run 2–3Γ— higher than during gallery browsing. Streaming export via WritableStream + TransformStream is on the roadmap for the next version.


What We're Planning to Fix

OffscreenCanvas + Worker for rendering. Once Safari's support stabilizes, this is the first priority for removing main-thread render pressure on mobile devices.

Streaming export via WritableStream. Would allow exporting 500+ page documents without the current peak RAM spike.

LRU cache for decoded bitmaps. Keep only the last N pages in fully decoded form; store the rest as compressed dataURL. Would reduce peak memory consumption by roughly an order of magnitude.


If you want to stress-test the duplex pipeline yourself β€” load two real multi-page scans at pdfredx.com. No sign-up, no upload to any server.

πŸš€ Launch Tool for Free