TPTruPDF

@trupdf/content-edit

Page-content-stream editing. Detects editable text / image blocks on a page, queues pending edits, and bakes them directly into the PDF — edits live in the page content stream, not in annotation dictionaries, so they survive every save mode and render identically in Acrobat, Chrome, Firefox, Edge, and Apple Preview.

ContentEditEngine

import { ContentEditEngine } from '@trupdf/content-edit';
 
// Or grab the viewer's own instance: engine.getContentEditEngine()
const editEngine = new ContentEditEngine(); // stateless; share across documents
const blocks = await editEngine.detectBlocks(page, { documentKey: pdfDoc });
const newBytes = await editEngine.bake(oldBytes, store.list());
MethodPurpose
detectBlocks(page, options?)Returns EditableBlock[] the UI overlays as editable regions. Cached per (documentKey, page) when options.documentKey is passed.
bake(originalBytes, edits, options?)Applies a sequence of PendingEdits to the content stream; returns new PDF bytes
invalidateDetectionCache(documentKey, page?)Drop cached detection for one page or the whole document

Construct with { removal } to swap the removal adapter (see below).

ContentEditStore

UI-facing store of pending edits with undo/redo. Flushed by ViewerEngine.applyContentEdits() (which SaveEngine.saveAll runs before the form and annotation passes).

const store = engine.getContentEditStore();
const edit = store.push({ kind: 'insert-text', page: 1, xPdf: 50, yPdf: 700, widthPdf: 400,
  text: 'Draft', font: { key: 'Helvetica', size: 11 }, color: { r: 0, g: 0, b: 0 } });
store.update(edit.id, { xPdf: 60 });
store.undo(); store.redo();
store.list(); // ReadonlyArray<PendingEdit>
store.on('changed', (snapshot) => /* re-render overlay */);

Edit shapes

PendingEdit is a discriminated union:

type PendingEdit =
  | InsertTextEdit   // kind: 'insert-text'
  | InsertImageEdit  // kind: 'insert-image'
  | ReplaceTextEdit  // kind: 'replace-text'
  | ReplaceImageEdit // kind: 'replace-image'
  | DeleteEdit;      // kind: 'delete'
 
type StandardFontKey =
  | 'Helvetica' | 'HelveticaBold' | 'HelveticaOblique' | 'HelveticaBoldOblique'
  | 'TimesRoman' | 'TimesRomanBold' | 'TimesRomanItalic' | 'TimesRomanBoldItalic'
  | 'Courier' | 'CourierBold' | 'CourierOblique' | 'CourierBoldOblique';

For simple overlay additions without detection, @trupdf/core also exports a lightweight applyEdits(bytes, edits) helper (add text / image / rectangle).

Removal adapters

Replacing or deleting existing content goes through a removal adapter:

import {
  ContentStreamRewriterAdapter, // default — structural operator rewrite
  WhiteOutRemovalAdapter,
  PdfiumRemovalAdapter,
} from '@trupdf/content-edit'; // WhiteOutRemovalAdapter + PdfiumRemovalAdapter also ship in @trupdf/viewer
 
const editEngine = new ContentEditEngine({ removal: new WhiteOutRemovalAdapter() });
AdapterBehaviour
Content-stream rewriter (default)Structurally removes the affected operators from the page content stream
White-out adapterMasks the original bbox with a filled rectangle — maximum cross-viewer safety
Native-engine adapterTrue object removal via the native PDF engine (WASM); plug in your own engine handle

The bake pipeline is adapter-agnostic — implement the RemovalAdapter interface to bring your own backend.

Font resolution

Baking a text edit resolves its font in three tiers:

  1. Original resource reuse — when the detected block carried a reusable font reference (subset-tagged base font + inverted character map + advance widths), the new text is encoded and shown through the font dictionary the source run already used — no embedding, exact typeface. This is the only tier that preserves composite subset fonts, i.e. most office-generated PDFs.
  2. Original program re-embed — when the block carried the document's embedded font program and it has a glyph for every character of the new text, the program is re-embedded (subsetted) so the saved PDF keeps the document's own typeface. The embedder is loaded lazily on the first save that needs it — never on the viewer's critical path.
  3. Standard 14 fallbackmatchStandardFont picks the closest Standard 14 variant from the run's real PostScript name (subset tag stripped) plus bold/italic/serif/monospace classifier flags. Override at edit time by passing font explicitly. Text is sanitized against the face's WinAnsi character set so an unencodable character degrades to ? instead of aborting the save.

While typing, the live edit UI renders with a CSS font chain built from the detected face (buildCssFontChain) so the editor shows the page's own glyphs. A face change from the style panel drops the reuse/re-embed tiers (the captured face no longer matches); size-only changes keep them.

Capturing per-block font intel requires extended font properties from the render engine; ViewerEngine enables this automatically outside secure mode.

Secure mode

The Edit Content tool requires reading existing text, which secure mode forbids — it short-circuits to a disabled state. Insert Paragraph and Insert Image still work in secure mode because they don't read existing content.