BasicApps Logo
Try Our PDF Editor Now

Built by engineers, for professionals • Zero Adobe dependency

Technical Deep Dive12 min readBy Jamie Mercer · PDF workflow specialistNext.js 14 • Fabric.js 6.6.1

We Built a PDF Editor That Doesn't Suck

Most online PDF editors are garbage. They're slow, crash with large files, and the UI feels like it was designed in 2010. So we built one that engineering teams actually want to use, powered by Next.js 14, Fabric.js v6.6.1, and a custom 3-layer canvas architecture.

This isn't just another form filler. We engineered a browser-based PDF editor with Microsoft Word-style ribbon UI, Canva-style object manipulation, and a plugin architecture that handles everything from font fallback mapping to non-destructive erasure. Here's how we solved the hard problems.

BasicApps PDF editor upload page — drop any PDF to start editing instantly in the browser
Drop any PDF into the editor — no upload to a server, no account required.

The Architecture That Makes It Fast

PDF editing in browsers is notoriously difficult. Most solutions either upload everything to servers (slow, insecure) or try to render PDFs as single canvas elements (inflexible, memory-hungry). We took a different approach.

Finished editing? Lock it down.

Add a password to your edited PDF so only authorised people can open or modify it.

Protect PDF

3-Layer Canvas Architecture

0
HTML Background Layer
PDF page as eraser-proof <img> element
1
Interactive Canvas Layer
Transparent hitboxes + masks over original content
2
Editable Objects Layer
User-created text, shapes, images, annotations
The HTML background layer is the key innovation. When you use our eraser tool, it uses `globalCompositeOperation: 'destination-out'` to punch transparent holes through annotations, but the original PDF page underneath remains untouched.

This 3-layer system means you can erase your annotations without accidentally destroying the original document. Layer 0 stays immutable, Layer 1 handles click detection through transparent hitboxes, and Layer 2 contains all your editable content. When you click on existing PDF text, we spawn a Fabric.js Textbox with precise coordinate mapping at exactly the right position.

Plugin Architecture: One Hook Per Tool

Instead of building a monolithic editor, we created a plugin system where each editing tool is an isolated React hook. The orchestrator hook (`useCanvasActions`) registers all plugins, but only the active tool's event listeners are attached to the canvas.

PDF editor with a document loaded — full Word-style ribbon toolbar visible across the top
A loaded PDF with the full ribbon: Move, Text, Pen, Highlight, Eraser, Shapes, Insert, Redact, Emoji, Find and more.

Plugin Architecture at Work

useTextInsert.ts
useShapes.ts
useEraser.ts
usePenDraw.ts
useHighlighter.ts
useImageInsert.ts
useFormatSelection.ts
useCanvasUtilities.ts

This architecture means adding a new tool requires just creating a new hook file and adding one line to the orchestrator. No cross-plugin conflicts, no memory leaks from unused event listeners, and each tool can be tested independently.

Each plugin hook follows the same pattern: watch the `activeTool` state, attach event listeners only when active, and clean up on tool switch. For example, `usePenDraw.ts` only enables `canvas.isDrawingMode` when `activeTool === "draw"`.

The Pen plugin is the clearest example of the activation pattern — here is what the toolbar looks like the moment it becomes active.

Pen tool active in the PDF editor — floating panel shows stroke size slider, 7 color presets, and opacity control
The Pen plugin activates a floating options panel — stroke size (1–50px slider), 7 color presets, custom color, and opacity. Only active while `activeTool === "draw"`.

Performance Numbers That Actually Matter

Real-time
text editing, no lag
Smooth
shape rendering on canvas
1.5x
render scale precision
50MB
max file size

The architecture keeps interactions snappy in practice. Text edits feel instant because we use lazy object spawning — hitboxes sit over original PDF text, but editable Textbox objects are only created when a user actually clicks. This means only the area being actively edited is live in Fabric.js at any given time, rather than the entire document.

Font Mapping: The Problem Nobody Talks About

PDF fonts don't exist in browsers. A PDF using "Helvetica" needs to render with web-safe fonts, but Helvetica renders differently than Arial on different operating systems. We solved this with dynamic font loading and measurement-based compression.

When you edit PDF text, we create an invisible measurement textbox to determine the natural width of your text with the fallback font. If it's wider than the original PDF bounding box, we compress the fontSize proportionally to prevent overflow.

Font measurement happens invisibly, but the text toolbar is where you see the result — the controls here only unlock when the Text tool is active.

Text tool active in the PDF editor — font size controls, bold, italic, list and color are enabled in the toolbar
Activating the Text tool unlocks font-size ±, Bold, Italic, List and Color in the toolbar. Click any existing PDF text to spawn a Fabric.js Textbox at exact coordinates.

Our font fallback mapping translates common PDF fonts to Google Fonts equivalents. Times New Roman becomes "Times New Roman, serif", Helvetica becomes "Helvetica, Arial, sans-serif", and we inject Google Fonts `<link>` tags dynamically when needed. The result: your edited text looks exactly like the original.

Working with scanned PDFs?

Scanned documents contain images, not real text, so font mapping has nothing to work with. Run OCR first to convert the image layer into selectable, editable text.

Extract text with OCR →

The Redact tool is the most consequential plugin in the editor — once you apply it, the selected content is permanently overwritten in the PDF structure.

Redact tool open in the PDF editor showing a color picker with 9 redaction colors including black, charcoal, red, and white
The Redact tool lets you permanently cover any region with a solid color. Unlike highlighting, the redacted block is burned into the exported PDF — the original content is gone.

Inline Rich-Text: Character-Level Formatting

Most PDF editors treat text as blocks—bold the whole paragraph or nothing. We implemented character-level formatting using Fabric.js's `styles` object. You can highlight three words in a sentence and make just those words bold, italic, or change their color.

Character-level formatting approach:
Each character position can have independent styling properties like font weight, color, size, and decoration. This enables precise formatting control where you can make individual words bold or italic within the same text block, similar to how professional word processors handle rich text formatting.

When you select text and change formatting, we check if the textbox is in editing mode and has a selection. If yes, we use `setSelectionStyles()` to format only the selected characters. If not, we apply formatting to the entire object and clear character-level overrides.

Find and Replace panel open in the PDF editor — a search input appears at the top-right of the canvas
Find & Replace (Ctrl+H) scans all text objects across every page. Match case toggle included — the results are tied directly to the same singleton store as all other edits.

State Management Without the Bloat

We built a custom singleton store using React 18's `useSyncExternalStore` instead of adding Redux or Zustand. The store holds all editor state—pages, objects, current tool, formatting options—with a 50-level undo/redo stack that deduplicates identical states.

Our undo system serializes the entire state as JSON, compares SHA-256 hashes to prevent duplicate entries, and maintains a circular buffer. Undo operations typically complete in under 10ms for documents with hundreds of objects.

The emoji plugin is a good test of the undo system because inserting an emoji creates a new state entry immediately — here is the picker before any selection is made.

Emoji picker open in the PDF editor with search bar, category tabs, and a grid of Smiley and People emojis
The emoji plugin ships 1,800+ emojis with full-text search and category tabs. Each emoji inserts as a Fabric.js text object — scalable, rotatable, with opacity control.

The store provides methods like `addObject()`, `updateObject()`, and `deleteObject()` that automatically trigger React re-renders. It also handles page operations—rotation, deletion, reordering—while maintaining object associations across page changes.

Need to reorder pages before editing?

The Organize tool lets you drag pages into the right sequence, delete blanks, and rotate individual pages.

Organize pages

The Insert menu exposes all five object types from a single dropdown — the one place where everything you can add to a PDF page is listed.

Insert dropdown open in the PDF editor showing Image, Signature, Stamp, Sticky Note and Watermark options
The Insert dropdown exposes five object types: Image, Signature (drawn on a canvas pad), Stamp, Sticky Note, and Watermark (applied across all pages automatically).

Microsoft Word Ribbon, But Better

Users expect familiar interfaces. Our ribbon toolbar mimics Microsoft Word's tabbed design—Home, Insert, Draw, Format, Extras—but adapts contextually to your selection. Select a shape, see shape-specific controls. Select text, see typography options. Select nothing, see document-level tools.

Home
Font, Size, Color
Insert
Images, Shapes, Text
Draw
Pen, Highlighter, Eraser
Format
Opacity, Fill, Stroke
Extras
Find, Replace, Pages

The Shapes dropdown shows all 18 object types at once — clicking any of them sets the active tool and queues the next click as object placement.

Shapes panel open in the editor showing 18 shape types including Rectangle, Circle, Arrow, Star, Heart, Cloud and more
The Shapes dropdown reveals 18 shape types and a color palette. Each shape is a full Fabric.js object: resizable, rotatable, opacity-adjustable.

The toolbar never directly manipulates canvas objects. It only updates store state. The `useFormatSelection` hook watches those state changes and applies them to the active Fabric.js object. This separation means we can batch multiple formatting changes and maintain consistent undo behavior.

Mobile-First, Desktop-Optimized

Editing PDFs on phones is painful with most tools. We implemented touch-optimized controls, pinch-to-zoom, and adaptive UI. The page panel becomes a horizontal strip on mobile, the toolbar collapses into bottom sheets, and Fabric.js object handles scale appropriately for touch interaction.

Mobile zoom starts at fit-to-width and adapts to viewport size. We prevent iOS overscroll rubber-banding and disable Safari's gesture-based zoom to avoid conflicts with our pinch-to-zoom implementation.

Touch event handling required the most care — this is what the editor looks like at 390px viewport width with the compact toolbar.

PDF editor on a 390px mobile viewport — compact toolbar with Move, Text, Pen, Highlight, Eraser tools visible
On mobile, the toolbar stays usable at 390px — Move, Text, Pen, Highlight and Eraser always visible; Shapes/Insert/Redact accessed via the action bar.

What We Shipped and What It Cost Us

PDF editing shouldn't require expensive desktop software or risky cloud uploads. We proved you can build professional-grade document editing that runs entirely in browsers, processes files locally, and matches the performance of native applications.

The techniques we developed—3-layer canvas architecture, plugin-based hooks, coordinate mapping with render scaling, font fallback systems—can be applied to other document formats too. This is just the beginning.

Ready to Try Professional PDF Editing?

Experience the editor that engineering teams trust. No downloads, no subscriptions, no data uploads.

Launch PDF Editor

Technical Questions

How do you handle PDF coordinate systems in browsers?

We use a RENDER_SCALE multiplier of 1.5x to balance visual quality with performance. All coordinates from the backend arrive pre-multiplied, and we convert back to PDF points during export. This ensures pixel-perfect placement while maintaining crisp text rendering at common zoom levels.

What makes your eraser tool different from other implementations?

Our eraser uses `globalCompositeOperation: 'destination-out'` to create transparent holes in the canvas layer, but the HTML background image remains untouched. This means erasing reveals the original PDF content underneath, not the webpage background—a technique most online editors get wrong.

How do you achieve such fast text editing performance?

We use lazy object spawning—hitboxes are placed over original PDF text, but editable Textbox objects are only created when users click. Combined with our plugin architecture that activates tools on-demand, this keeps memory usage low and interactions responsive even with complex documents.

What's the browser compatibility for advanced features?

Chrome and Edge provide optimal performance with hardware acceleration. Firefox works excellently for all core features. Safari requires specific optimizations for touch events but handles the core functionality well. We implement fallbacks for older browsers while maintaining feature parity.

Related Articles