Il contesto
Il sito ha già una manciata di tool PDF separati — unisci, dividi, comprimi, compila moduli con campi AcroForm, firma disegnata. Ognuno è una pagina indipendente, utile per intercettare la propria query di ricerca specifica ("unisci pdf gratis", "comprimi pdf online"). Quello che mancava era un editor vero e proprio: uno spazio di lavoro unico dove intervenire su un PDF con più strumenti nella stessa sessione, senza dover passare da un tool all'altro e riscaricare il file a ogni passaggio.
La decisione architetturale è stata di non toccare le pagine esistenti — restano lì, continuano a fare il loro lavoro SEO — e costruire editor-pdf.html come nuovo tool a parte, che le assorbe funzionalmente in un'unica interfaccia più ricca: sidebar di miniature, riordino e rotazione pagine via drag&drop, annotazioni (testo libero, evidenziazione, forme), redazione, firma disegnata, filigrana. Stesso vincolo di sempre: tutto client-side, nessun upload, il file non lascia mai il browser dell'utente.
The context
The site already has a handful of separate PDF tools — merge, split, compress, fill AcroForm fields, drawn signature. Each is an independent page, useful for capturing its own specific search query ("merge pdf free", "compress pdf online"). What was missing was a real editor: a single workspace where you could work on a PDF with multiple tools in the same session, without bouncing between tools and re-downloading the file at every step.
The architectural call was to leave the existing pages untouched — they stay, they keep doing their SEO job — and build editor-pdf.html as a new, separate tool that functionally absorbs them into one richer interface: a thumbnail sidebar, drag&drop page reordering and rotation, annotations (free text, highlight, shapes), redaction, drawn signature, watermark. Same constraint as always: everything client-side, no upload, the file never leaves the user's browser.
Architettura: canvas di rendering più overlay coordinato
Il pattern di base non è nuovo sul sito: era già stato validato in compila-pdf.html, che renderizza le pagine con PDF.js su un <canvas> e sovrappone un livello HTML per l'interazione (i campi compilabili, in quel caso). L'editor riusa lo stesso schema, generalizzato: ogni pagina è un canvas di rendering PDF.js più un overlay assoluto delle stesse dimensioni, dove vivono tutte le annotazioni come elementi posizionati in percentuale rispetto al viewport della pagina, non in pixel assoluti.
Questo dettaglio conta perché lo zoom e il resize della finestra cambiano continuamente la scala di rendering: se le annotazioni fossero ancorate in pixel, ogni ridimensionamento le farebbe scivolare fuori posizione rispetto al testo sottostante. Ancorandole in percentuale del viewport corrente di PDF.js, restano coerenti a qualunque zoom, e la conversione in coordinate assolute del PDF avviene solo una volta, al momento dell'export finale con pdf-lib.
Il rendering è protetto da un render token per pagina: ogni volta che scale o contenuto cambiano, si genera un nuovo token e il rendering in corso, se non è più quello più recente, si auto-annulla al termine invece di disegnare un frame ormai obsoleto sopra quello nuovo. Senza questa guardia, ridimensionare rapidamente la finestra durante un rendering pesante produce sovrapposizioni tra frame vecchi e nuovi — un problema classico di race condition su canvas asincroni.
Architecture: a rendering canvas plus a coordinated overlay
The base pattern isn't new on the site: it had already been validated in compila-pdf.html, which renders pages with PDF.js onto a <canvas> and layers an HTML overlay on top for interaction (fillable form fields, in that case). The editor reuses the same scheme, generalized: each page is a PDF.js rendering canvas plus an absolutely positioned overlay of the same size, where every annotation lives as an element positioned as a percentage of the page's current viewport, not in absolute pixels.
This detail matters because zoom and window resizing constantly change the rendering scale: if annotations were pixel-anchored, every resize would drift them out of position relative to the underlying text. Anchoring them as a percentage of PDF.js's current viewport keeps them consistent at any zoom level, and the conversion into absolute PDF coordinates happens only once, at final export time with pdf-lib.
Rendering is guarded by a per-page render token: whenever scale or content changes, a new token is generated, and an in-flight render, if it's no longer the most recent one, self-cancels on completion instead of drawing an already-stale frame over the new one. Without this guard, quickly resizing the window during a heavy render produces overlapping old and new frames — a classic race condition on async canvases.
Il pezzo interessante: la redazione non è un rettangolo nero
Annotazioni, firma e filigrana sono, tutto sommato, "solo" gestione di overlay e composizione finale — impegnativo ma diretto. La redazione è diversa, perché ha un requisito che le altre feature non hanno: deve essere irreversibile. Uno strumento che si chiama "redazione" e lascia il contenuto originale ancora presente nel file, anche solo coperto visivamente, non sta facendo quello che promette — e in un contesto legale o professionale, la differenza tra "sembra coperto" e "è stato rimosso" non è un dettaglio.
The interesting part: redaction isn't a black box
Annotations, signature and watermark are, all things considered, "just" overlay management and final composition — demanding but straightforward. Redaction is different, because it has a requirement the other features don't: it must be irreversible. A tool called "redaction" that leaves the original content still present in the file, even if only visually covered, isn't doing what it claims — and in a legal or professional context, the difference between "looks covered" and "was removed" isn't a detail.
// ❌ Disegna solo un rettangolo nero sopra il contenuto esistente
const page = pdfDoc.getPage(pageIndex);
page.drawRectangle({
x, y, width, height,
color: rgb(0, 0, 0)
});
// Il testo originale è ancora nel content stream della pagina:
// selezionabile, copiabile, estraibile con qualunque parser PDF
Questo è esattamente il comportamento di molti tool "redigi PDF" trovati in giro: disegnano una forma sopra il testo e la chiamano redazione. Aprendo il PDF risultante in un lettore qualsiasi e selezionando "tutto il testo della pagina" (o, più semplicemente, copiando l'area nera stessa), il contenuto "nascosto" riappare negli appunti. Il PDF, a livello di dati, non ha perso nulla — ha solo guadagnato un rettangolo colorato in cima.
This is exactly the behavior of many "redact PDF" tools found around: they draw a shape over the text and call it redaction. Open the resulting PDF in any reader and select "all text on the page" (or, more simply, copy the black area itself), and the "hidden" content reappears in the clipboard. At the data level, the PDF hasn't lost anything — it's just gained a colored rectangle on top.
Coprire visivamente un testo in un PDF non lo elimina: il content stream della pagina resta invariato sotto la forma disegnata. Chi si affida a un blackout come questo per proteggere dati sensibili sta distribuendo comunque quei dati, solo un po' più nascosti.
La soluzione: rasterizzare, ma solo le pagine che serve
L'unico modo per rimuovere davvero un contenuto da una pagina, restando completamente client-side senza un motore di riscrittura del content stream PDF, è convertire l'intera pagina in un'immagine dopo aver applicato il blackout — un bitmap non ha "testo sotto" da estrarre, perché non c'è più testo: ci sono solo pixel. Il compromesso ovvio è che rasterizzare gonfia il peso del file e perde la selezionabilità del testo. La soluzione adottata è applicare questo trattamento solo alle pagine che contengono almeno una redazione, lasciando tutte le altre esattamente come sono nell'originale.
The fix: rasterize, but only the pages that need it
The only way to truly remove content from a page, while staying fully client-side without a PDF content-stream rewriting engine, is to convert the whole page into an image after applying the blackout — a bitmap has no "text underneath" to extract, because there's no text left: just pixels. The obvious trade-off is that rasterizing bloats file size and loses text selectability. The approach taken applies this treatment only to pages that contain at least one redaction, leaving every other page exactly as it is in the original.
// Per ogni pagina del documento originale...
for (let i = 0; i < totalPages; i++) {
const hasRedaction = redactionsByPage[i]?.length > 0;
if (!hasRedaction) {
// ✅ Nessuna redazione: pagina copiata intatta, resta vettoriale
const [copied] = await outputDoc.copyPages(sourceDoc, [i]);
outputDoc.addPage(copied);
continue;
}
// 🔥 Pagina con redazioni: rasterizzata ad alta risoluzione
const bitmap = await renderPageToCanvas(pdfjsDoc, i, { scale: 3 });
burnRedactionsIntoCanvas(bitmap, redactionsByPage[i]);
burnAnnotationsIntoCanvas(bitmap, annotationsByPage[i]);
const jpg = await outputDoc.embedJpg(bitmap.toJPEG(0.92));
const newPage = outputDoc.addPage([bitmap.width, bitmap.height]);
newPage.drawImage(jpg, { x: 0, y: 0, width: bitmap.width, height: bitmap.height });
}
Due dettagli fanno la differenza in questo passaggio. Primo: la rasterizzazione avviene a una scala più alta (3x) di quella usata per la visualizzazione a schermo, altrimenti il testo sulle pagine "bruciate" risulterebbe visibilmente sgranato rispetto a quelle vettoriali rimaste nel documento. Secondo: sulle pagine rasterizzate, anche le annotazioni non-redazione (testo, evidenziazioni, forme, firma, filigrana) vengono bruciate nello stesso bitmap invece che aggiunte come overlay vettoriale separato — altrimenti si avrebbe una mescolanza di rendering diversi sulla stessa pagina, con incongruenze quando il PDF viene poi riaperto in lettori diversi.
Il risultato pratico: un documento di 20 pagine con una sola redazione a pagina 12 produce un PDF dove 19 pagine restano leggere, vettoriali e con testo selezionabile, e solo pagina 12 è un'immagine — pesante quel tanto che basta, ma senza nulla di estraibile sotto il blackout.
Two details make the difference in this step. First: rasterization happens at a higher scale (3x) than the one used for on-screen display, otherwise text on the "burned" pages would look visibly blurrier than the vector pages left in the document. Second: on rasterized pages, even non-redaction annotations (text, highlights, shapes, signature, watermark) get burned into the same bitmap instead of being added as a separate vector overlay — otherwise you'd end up mixing different rendering types on the same page, with inconsistencies when the PDF is later reopened in different readers.
The practical result: a 20-page document with a single redaction on page 12 produces a PDF where 19 pages stay light, vector, and text-selectable, and only page 12 is an image — just as heavy as it needs to be, with nothing extractable under the blackout.
Riordino, rotazione ed eliminazione pagine
La sidebar di miniature supporta drag&drop per riordinare le pagine, rotazione di 90° per pagina e eliminazione — tutte operazioni che si applicano a un array di stato in memoria (l'ordine, gli angoli di rotazione, le pagine escluse) senza toccare il documento sorgente finché non si arriva all'export. Questo tiene l'interfaccia reattiva anche su PDF di decine di pagine, perché ogni interazione aggiorna solo lo stato e ridisegna la miniatura interessata, non l'intero documento.
La rotazione è l'unico punto dove va prestata attenzione extra sulle pagine che finiscono rasterizzate: il viewport passato a PDF.js in fase di rendering deve già includere la rotazione applicata dall'utente, così il bitmap esce con l'orientamento corretto senza bisogno di ruotare l'immagine dopo — ruotare un bitmap già renderizzato introdurrebbe un ricampionamento aggiuntivo (e quindi ulteriore perdita di nitidezza) del tutto evitabile.
Page reordering, rotation and deletion
The thumbnail sidebar supports drag&drop reordering, 90° per-page rotation, and deletion — all operations applied to an in-memory state array (order, rotation angles, excluded pages) without touching the source document until export time. This keeps the interface responsive even on PDFs with dozens of pages, since every interaction only updates state and redraws the affected thumbnail, not the whole document.
Rotation is the one spot needing extra care on pages that end up rasterized: the viewport passed to PDF.js during rendering must already include the user-applied rotation, so the bitmap comes out with the correct orientation without needing to rotate the image afterward — rotating an already-rendered bitmap would introduce an extra, entirely avoidable resampling pass (and thus further loss of sharpness).
Firma disegnata e filigrana: riuso, non reinvenzione
La firma disegnata (touch e mouse, con smoothing sui punti di tracciamento) era già stata scritta e validata in compila-pdf.html: qui è stata riadattata da "un campo del form" a "un'annotazione riposizionabile" — stesso motore di cattura del tratto, ma inserita nel sistema generico di overlay dell'editor, con lo stesso handle di ridimensionamento condiviso da tutte le altre annotazioni. La filigrana è più semplice: testo con opacità e rotazione configurabili, applicabile a una singola pagina o all'intero documento, disegnata anch'essa come overlay vettoriale e "bruciata" solo se la pagina finisce rasterizzata per via di una redazione nelle vicinanze.
Drawn signature and watermark: reuse, not reinvention
The drawn signature (touch and mouse, with smoothing on the tracked points) had already been written and validated in compila-pdf.html: here it was adapted from "a form field" to "a repositionable annotation" — same stroke-capture engine, but plugged into the editor's generic overlay system, sharing the same resize handle as every other annotation. The watermark is simpler: text with configurable opacity and rotation, applicable to a single page or the whole document, also drawn as a vector overlay and only "burned in" if the page ends up rasterized because of a nearby redaction.
Cosa ho imparato
- "Redazione" ha un requisito implicito di irreversibilità che un semplice overlay grafico non soddisfa. Se il nome della feature promette rimozione, va verificato che il dato sparisca davvero, non solo che sparisca alla vista.
- Rasterizzare selettivamente, non l'intero documento. Applicare il trattamento pesante solo alle pagine che lo richiedono mantiene leggero e ricercabile tutto il resto — un compromesso netto migliore che trattare l'intero PDF come "meno fidato" per via di una singola pagina sensibile.
- Ancorare le annotazioni al viewport, non ai pixel dello schermo. Coordinate percentuali rispetto al viewport corrente di PDF.js restano coerenti a ogni zoom o resize; coordinate pixel assolute si disallineano non appena la scala di rendering cambia.
- Un render token per pagina evita race condition su canvas asincroni. Con rendering ripetuti in rapida successione (resize, zoom), senza un modo per invalidare un rendering ormai obsoleto si rischiano frame sovrapposti visivamente scorretti.
- La rasterizzazione va fatta a scala più alta di quella di visualizzazione. Renderizzare al 3x invece che alla scala schermo evita che le pagine "bruciate" risultino visibilmente più sgranate di quelle rimaste vettoriali nello stesso documento.
-
Codice già scritto per un tool si riusa per il successivo. Motore di rendering PDF.js/overlay e firma disegnata, validati in
compila-pdf.html, hanno accelerato di molto la costruzione dell'editor invece di essere riscritti da zero.
What I learned
- "Redaction" carries an implicit irreversibility requirement that a simple graphic overlay doesn't satisfy. If the feature's name promises removal, it's worth verifying the data actually disappears, not just that it disappears from view.
- Rasterize selectively, not the whole document. Applying the heavy treatment only to pages that need it keeps everything else light and searchable — a clearly better trade-off than treating the entire PDF as "less trustworthy" because of one sensitive page.
- Anchor annotations to the viewport, not to screen pixels. Coordinates as a percentage of PDF.js's current viewport stay consistent through any zoom or resize; absolute pixel coordinates drift as soon as the rendering scale changes.
- A per-page render token avoids race conditions on async canvases. With repeated renders in quick succession (resize, zoom), without a way to invalidate an now-stale render you risk visually incorrect overlapping frames.
- Rasterization should happen at a higher scale than on-screen display. Rendering at 3x instead of screen scale keeps "burned" pages from looking noticeably blurrier than the vector pages left in the same document.
-
Code already written for one tool gets reused for the next. The PDF.js rendering/overlay engine and drawn signature, validated in
compila-pdf.html, sped up building the editor considerably instead of being rewritten from scratch.
Domande frequenti
Perché un rettangolo nero sopra un testo in un PDF non è vera redazione?
Perché in un PDF il testo è un livello di dati indipendente dalla grafica: se si disegna una forma nera sopra una riga, quella riga resta nel documento sotto la forma, selezionabile e copiabile, ed è estraibile con un semplice "seleziona tutto" o con qualunque parser PDF, anche se visivamente non si vede più nulla. La vera redazione deve rimuovere il contenuto testuale dal documento, non solo coprirlo.
Come si rimuove davvero il testo sotto un'area redatta senza un server che processa il file?
Rasterizzando la pagina interessata: la si disegna ad alta risoluzione su un canvas tramite PDF.js, si applica il blackout direttamente sul bitmap, e si sostituisce l'intera pagina vettoriale con quell'immagine nel PDF finale. A quel punto il testo originale non esiste più come dato: è stato convertito in pixel insieme al resto della pagina.
Perché rasterizzare tutte le pagine di un PDF, anche quelle senza redazioni, è una cattiva idea?
Perché un'immagine occupa molto più spazio del testo vettoriale che rappresenta, quindi rasterizzare l'intero documento gonfia il peso del file e rende il testo non più selezionabile né ricercabile ovunque, anche dove non serviva nascondere nulla. Meglio rasterizzare solo le pagine con almeno una redazione, copiando le altre vettorialmente.
È possibile copiare pagine vettoriali da un PDF e mescolarle a pagine rasterizzate nello stesso documento?
Sì. Una libreria come pdf-lib permette di copiare pagine originali intatte da un PDF sorgente a uno nuovo (mantenendole vettoriali), e allo stesso tempo di creare pagine da un'immagine, inserendole nella stessa posizione dell'indice pagine — senza differenze visibili nell'ordine o nella numerazione.
Un editor PDF che gira solo nel browser è davvero sicuro per documenti sensibili?
L'assenza di upload elimina il rischio che il file transiti o resti salvato su un server terzo: rendering, editing ed export avvengono in memoria nel browser tramite PDF.js e pdf-lib, e il file lascia il dispositivo solo quando l'utente lo scarica o lo condivide.
Frequently asked questions
Why isn't a black box drawn over text in a PDF real redaction?
Because in a PDF, text is a data layer independent from the graphics: drawing a black shape over a line leaves that line in the document underneath the shape, selectable and copyable, extractable with a simple "select all" or any PDF parser, even though nothing is visible anymore. Real redaction must remove the text content from the document, not just cover it.
How do you actually remove text under a redacted area without a server processing the file?
By rasterizing the affected page: drawing it at high resolution onto a canvas via PDF.js, applying the blackout directly to the resulting bitmap, and replacing the whole vector page with that image in the final PDF. At that point the original text no longer exists as data — it's been converted to pixels along with the rest of the page.
Why is rasterizing every page of a PDF, even ones with no redactions, a bad idea?
Because an image takes up far more space than the vector text it represents, so rasterizing the whole document bloats file size and makes text unselectable and unsearchable everywhere, even where nothing needed hiding. Better to rasterize only pages with at least one redaction, copying the rest as vector.
Can you copy vector pages from a PDF and mix them with rasterized pages in the same output document?
Yes. A library like pdf-lib lets you copy original pages intact from a source PDF into a new one (keeping them vector), while also creating pages from an image and inserting them at the same position in the page index — with no visible difference in order or numbering.
Is a browser-only PDF editor actually safe for sensitive documents?
The absence of upload removes the risk of the file passing through or being stored on a third-party server: rendering, editing and export all happen in browser memory via PDF.js and pdf-lib, and the file leaves the device only when the user downloads or shares it themselves.