Coded Horizon
Home / Blog /I Got Tired of Unsecured PDF Web Tools, So I Built My Own Desktop Editor

I Got Tired of Unsecured PDF Web Tools, So I Built My Own Desktop Editor

“Give me six hours to chop down a tree and I will spend the first four sharpening the axe.” — Abraham Lincoln

Have you ever needed to rearrange a PDF and ended up using Chrome’s “Print to PDF” as a workaround just to isolate a few pages? Or turned to one of the usual web tools, only to pause halfway through the upload and think: this is a sensitive document, should it really be sitting on some random server right now?

That was exactly the situation my father was in recently. He needed to organize and merge a set of financial documents, and web-based tools were a non-starter — that data simply wasn’t leaving his machine. I pointed him at PDFSam as a stopgap, but it was more tool than either of us actually needed. The problem was narrow: open some PDFs, pick the pages, put them in order, save. It didn’t need to be complicated.

So one evening I sat down and built PDF Warrior. (There’s no story behind the name — it was just the folder I typed when running bun create tauri-app, and it stuck.)

What It Does

PDF Warrior does one job well: give you full visual control over a set of PDF pages before you compile them into a final file — entirely offline, nothing ever leaves your machine.

  • Open multiple PDFs — native file picker or drag files straight onto the window
  • Thumbnail sidebar — fast, batch-rendered previews of every page across every loaded file, color-coded by source document
  • Full-quality preview — click any thumbnail to inspect it at full resolution
  • Drag-and-drop reordering — mix and reorder pages from different source files into one output
  • Include / exclude toggles — deselected pages get a SKIP badge and drop out of the final export; there’s also a toggle-all per file
  • Rotate & flip — 90° rotation in either direction, plus horizontal/vertical flipping
  • Undo/redo — a full snapshot-based history across every operation: reorder, rotate, flip, toggle, remove
  • Save individually or merged — export the whole arranged set, or pull a single page out as its own PDF

Demo

Why This Stack

I wanted this fast, small, and safe to hand to my dad without worrying about it eating his laptop’s resources:

LayerChoiceWhy
Desktop shellTauri 2Tiny bundle, no bundled Chromium, native OS integration
BackendRust — lopdf, pdfium-render, imageFast, memory-safe PDF manipulation and native-thread rendering
FrontendReact 19 + TypeScript + ViteDeclarative UI, fast dev loop, mature ecosystem
StateZustandMinimal boilerplate, easy to reason about for undo/redo
PDF previewpdfjs-distHigh-fidelity canvas rendering directly in the webview
Drag & drop@dnd-kitAccessible, well-behaved pointer sensors for sortable lists

Going with Tauri instead of Electron was the single biggest lever here — no bundled browser runtime means the whole app compiles down to a binary in the tens of megabytes instead of hundreds, and it barely registers on resource usage while idling in the background. As i simply dont like bloat or resorce hungry enviorment.

The Architecture: One State, Two Runtimes

The interesting part of this build wasn’t the PDF manipulation itself — Rust’s PDF ecosystem makes that fairly mechanical. It was keeping a single source of truth in sync across the Tauri IPC boundary, between a React frontend that owns layout state and a Rust backend that owns the actual bytes.

Every page loaded into the workspace is represented as a PageRef in the Zustand store:

export interface PageRef {
  id: string;                 // stable id, survives reorders
  sourceFile: string;         // absolute path to the source PDF
  sourcePageIndex: number;    // 0-based index within that file
  included: boolean;          // does this page make the final export
  thumbnailUrl: string | null;
  rotation: number;
  flipHorizontal: boolean;
  flipVertical: boolean;
}

pages: PageRef[] is the document — its order is the export order. Every mutating action (reorder, toggle, rotate, flip, remove) pushes the previous array onto a history stack before applying the change, which is what makes undo/redo trivial: it’s just walking backward and forward through snapshots of that array.

function pushState(state: AppState, newPages: PageRef[]) {
  const currentHistory = state.history.slice(0, state.historyIndex + 1);
  currentHistory.push(state.pages);
  return {
    history: currentHistory,
    historyIndex: currentHistory.length - 1,
    pages: newPages,
  };
}

When you hit Save, the frontend doesn’t send PageRef[] as-is — it filters to included === true, strips out everything Rust doesn’t need to know about (id, thumbnailUrl, included itself), and sends a lean manifest of { source_file, source_page_index, rotation, flip_horizontal, flip_vertical } entries over invoke(). On the Rust side, lopdf opens each source file, extracts the requested pages in manifest order, applies the transforms, and assembles a new document — emitting progress events back to the frontend as it works so the UI can show a live progress bar instead of just freezing.

Keeping that boundary narrow — Rust never learns about UI concerns like thumbnails or selection state, React never touches PDF internals — is what let both sides stay simple even as features like rotation and per-page flipping got added on top of the original merge/reorder scope.

Did I Use AI to Build This?

Yes — but probably not in the way you’re picturing. I didn’t generate the whole project and sip coffee while it built itself.

I used AI for the more mechanical parts of the build: things like deriving TypeScript types from the Rust structs, or writing out repetitive Zustand action boilerplate. It sped up the parts that didn’t require thinking, not the parts that did.

Where it actually mattered was the stuff I didn’t know going in — which crates to reach for on the PDF manipulation side, and the deeper, often poorly documented APIs of libraries like pdfium-render and lopdf. My agent worked through a lot of that undocumented surface area with me. I also leaned on it for the frontend, since UI isn’t where I’m strongest — I could probably still hack together something that looks like 1999, but there’s a reason modern interfaces don’t look like that anymore, and getting there isn’t intuitive for me.

Want to Try It Out?

Download Badge

Visit Latest Release to grab a prebuilt installer for Windows. I did not compile it for Linux because I am currently using Windows and am too lazy to setup Github Workflows for Linux build. If you want to build it for Linux, you can clone the repo and follow the instructions in the README . Or if it takes off, I will definitely add it.

What’s Next

Both my dad and I are using this when needed, so the roadmap is driven by what I actually run into or People request. Some things on the horizon:

  • Image → PDF — drag PNG/JPG/WEBP images straight into the workspace and have them become pages
  • PDF compression — configurable-quality size reduction, done natively in Rust
  • Page cropping & margins, watermark overlays, and page numbering
  • Password protection & encryption — lock exported PDFs on the way out

Was It Worth Building?

Yes, without much hesitation. It solved a real problem for my family and me without any data leaving the machine, and it was a good reminder of how quickly Thinks can be prototyped using ai now a days from idea to something that feels like a real, shippable tool.

v1.0.0 is out with prebuilt native installers — no need to build from source unless you want to. Grab a release, read the code, or open a PR.

Source & Releases: github.com/Subhranil-Maity/Pdf-Warior (MIT licensed)