DEV Community

Cover image for I was tired of “free” PDF editors that upload files and add watermarks — so I built a client-side one
Maaz Sohail
Maaz Sohail

Posted on

I was tired of “free” PDF editors that upload files and add watermarks — so I built a client-side one

Most “free” PDF editors aren’t really free. You upload a file, fix a typo, hit download… and get a watermark or a paywall. Worse, your document lives on someone else’s server now.

I’m a builder and I needed a tool I could trust. So I made one: a 100% client-side PDF editor. No signup. No watermark. Your file never leaves your device. This post is the story — what we tried, what broke, and what finally worked.

The problem we kept hitting:

  • Bait-and-switch UX: “Free” until the last click.
  • Privacy risk: Uploading IDs/contracts to unknown servers isn’t acceptable for many people or companies.
  • Latency and cost: Upload → process → download is slow, and server costs scale as you grow.

The obvious answer: don’t upload at all. Do the work in the browser.
**
What “client-side PDF editing” really means**

PDFs are a weird mix of vector graphics, text glyphs, images, annotations, and page transforms. In practice we needed to solve five things:

  • Parse and render pages fast enough that mobile users don’t bounce.
  • Edit text without wrecking layout.
  • Place images/logos/stamps precisely.
  • Capture signatures smoothly (mouse/touch) and embed them reliably.
  • Export a clean PDF instantly, all offline.

We leaned on modern web APIs: WebAssembly, Canvas, OffscreenCanvas, Web Workers, and careful memory management. No servers required.

Text editing: “glyph runs” and white-out surgery

PDF text isn’t like a contenteditable div. It’s positioned glyph runs. Our approach:

  • Detect text runs on the page.
  • When the new text doesn’t match metrics exactly, place a subtle white-out rectangle behind the edited area to “erase” the old glyphs, then draw the new text on top (preserving alignment/flow).
  • Support common font choices; fall back gracefully when fonts aren’t embeddable.

This was the most fragile part. Early versions had hilarious bugs: a single emoji could push a line 2px and every subsequent line went crooked. Fix: normalize metrics and snap to baselines.

Images & logos: pixels meet PDF space

Placing a PNG/JPG/WebP on a PDF page sounds simple until you remember that PDF has its own coordinate space with transforms. We convert CSS pixels → PDF user units, apply rotation/scale, and clamp to page bounds. For logos with transparency, PNG with alpha is ideal.

Signatures: feel matters

We capture pointer events, smooth the path (Catmull–Rom → cubic Beziers), and embed the result as a vector or high-res bitmap. The two things that made it feel “real”:

  • Slight pressure simulation based on velocity (thicker when moving faster).
  • Immediate feedback at 60fps using a worker + offscreen canvas on supported browsers.
    Performance: how we kept it snappy on a Moto G

  • Lazy page rendering: only draw the viewport + a small prefetch window.

  • Worker pool: parsing/rendering off the main thread to keep TBT ~0–30ms.

  • Chunked processing: stream large PDFs and free buffers aggressively.

  • No layout thrash: one write per frame; batch DOM updates; avoid sync measures.
    Result: on mobile we see ~1.2s FCP and ~1.5s LCP for the landing UI, and edits feel instant.

Privacy & trust: rules we set for ourselves

  • Default: no network calls. The editor runs locally.
  • If you choose an optional cloud action later, it’s explicit and TLS-only.
  • No watermark, no usage caps, no “gotcha” step. We’d rather earn goodwill than convert via traps.
    **
    What we got wrong (so you don’t have to)**

  • Font chaos: even when you think you embedded a font, subsetting can bite you. We added thorough fallbacks and test PDFs with non-Latin scripts.

  • Mobile Safari limits: large canvases + memory spikes = crashes. Fix: tile rendering and lower peak memory.

  • Pointer jitter: naive smoothing feels sluggish. Velocity-aware smoothing felt right.

  • Export size: we shipped a build that ballooned file size after multiple edits. The culprit: duplicate image XObjects. We now dedupe assets on export.

Open problems we’re still chewing on

  • Full OCR for scanned PDFs (we’re evaluating on-device models).
  • True find & replace across pages.
  • Batch stamping (same logo on N pages in one step).
  • PDF/A export for archival workflows.

If you’re building something similar, these patterns helped

  • Workers everywhere. Even small parsing tasks benefit.
  • One source of truth for coordinates. Don’t mix CSS pixels, device pixels, and PDF units across modules.
  • Design for failure. Fonts missing? Fall back. Image corrupt? Render a placeholder and let the user continue.
  • Keep promises user-visible. “No watermark” is a commitment — put it in the UI, not just the blog.

Want to try it (no pressure)

If a truly free, client-side editor is useful, you can try it here: pdffreeeditor.com.
No signup, no watermark; edit, sign, merge/split, and add images right in the browser.

I’d love feedback on edge cases (weird fonts, giant scans, RTL text). If you’ve solved similar problems in canvas/WebAssembly, please share your tricks — I’ll happily credit and implement.

— Maaz

Top comments (1)

Collapse
 
techrex profile image
Maaz Sohail

I await your guy's feedback.