Working with Text
To edit text, describe what PDFDancer should target and choose an operation: replace, insert, delete, or style. The operation returns a TextEditResponse describing what PDFDancer found and changed.
Download the quickstart PDF as input.pdf. It contains Hello on page 1. The complete example below writes output.pdf with Hello replaced by Final.
The basic workflow
- Python
- TypeScript
- Java
from pathlib import Path
from pdfdancer import PDFDancer, TextReplaceRequest
with PDFDancer.open(Path("input.pdf")) as pdf:
response = pdf.text().replace(
TextReplaceRequest.literal("Hello", "Final")
.whole_words(True)
.build()
)
if response.matched == 0:
raise RuntimeError("Hello was not found")
if response.errors:
raise RuntimeError(f"Replacement failed: {response.errors}")
pdf.save("output.pdf")
import * as fs from 'node:fs';
import {PDFDancer, TextReplaceRequest} from 'pdfdancer-client-typescript';
const input = new Uint8Array(fs.readFileSync('input.pdf'));
const pdf = await PDFDancer.open(input);
const response = await pdf.text().replace(
TextReplaceRequest.literal('Hello', 'Final')
.wholeWords(true)
.build()
);
if (response.matched === 0) throw new Error('Hello was not found');
if (response.errors?.length) throw new Error(JSON.stringify(response.errors));
await pdf.save('output.pdf');
import com.pdfdancer.client.rest.PDFDancer;
import com.pdfdancer.common.request.TextReplaceRequest;
import com.pdfdancer.common.response.TextEditResponse;
PDFDancer pdf = PDFDancer.createSession("input.pdf");
TextEditResponse response = pdf.text().replace(
TextReplaceRequest.literal("Hello", "Final")
.wholeWords(true)
.build());
if (response.matched() == 0) {
throw new IllegalStateException("Hello was not found");
}
if (response.errors() != null && !response.errors().isEmpty()) {
throw new IllegalStateException(response.errors().toString());
}
pdf.save("output.pdf");
Document scope and page scope
pdf.text() searches the document. pdf.page(2).text() limits the operation to page 2. Page numbers are one-based.
You can also limit a document-wide edit to a set of page numbers. Prefer pdf.page(n).text() when one page is the natural boundary; configure page numbers on the edit definition when the same change should run on several known pages.
- Python
- TypeScript
- Java
page_two = pdf.page(2).text().replace(
TextReplaceRequest.literal("Draft", "Final").build()
)
selected_pages = pdf.text().replace(
TextReplaceRequest.literal("Draft", "Final").pages(2, 4).build()
)
const pageTwo = await pdf.page(2).text().replace(
TextReplaceRequest.literal('Draft', 'Final').build()
);
const selectedPages = await pdf.text().replace(
TextReplaceRequest.literal('Draft', 'Final').pages(2, 4).build()
);
var pageTwo = pdf.page(2).text().replace(
TextReplaceRequest.literal("Draft", "Final").build());
var selectedPages = pdf.text().replace(
TextReplaceRequest.literal("Draft", "Final").pages(2, 4).build());
Choose a selector
Literal selectors are safest for known text. Regular expressions are useful for variable content such as invoice numbers.
- Python
- TypeScript
- Java
literal = TextReplaceRequest.literal("Acme Ltd", "Northwind GmbH").build()
invoice = (
TextReplaceRequest.regex(r"Invoice\s+#\d+", "Invoice")
.case_sensitive(False)
.max_matches(1)
.build()
)
const literal = TextReplaceRequest.literal('Acme Ltd', 'Northwind GmbH').build();
const invoice = TextReplaceRequest.regex('Invoice\\s+#\\d+', 'Invoice')
.caseSensitive(false)
.maxMatches(1)
.build();
TextReplaceRequest literal = TextReplaceRequest
.literal("Acme Ltd", "Northwind GmbH").build();
TextReplaceRequest invoice = TextReplaceRequest
.regex("Invoice\\s+#\\d+", "Invoice")
.caseSensitive(false)
.maxMatches(1)
.build();
wholeWords applies to literal selectors. maxMatches limits the number of eligible matches, which is useful protection for broad selectors.
Replace, insert, delete, and style
Each operation has a dedicated builder. These examples show one minimal edit of each kind.
- Python
- TypeScript
- Java
from pdfdancer import (
PdfColorRequest,
TextDeleteRequest,
TextInsertRequest,
TextReplaceRequest,
TextStyleRequest,
)
replaced = pdf.text().replace(
TextReplaceRequest.literal("Draft", "Final").build()
)
inserted = pdf.text().insert(
TextInsertRequest.after("Invoice total", " including tax").build()
)
deleted = pdf.page(2).text().delete(
TextDeleteRequest.literal("Internal only").build()
)
styled = pdf.text().style(
TextStyleRequest.literal("Overdue")
.fill_color(PdfColorRequest.rgb(0.85, 0.1, 0.1))
.build()
)
import {
PdfColorRequest,
TextDeleteRequest,
TextInsertRequest,
TextReplaceRequest,
TextStyleRequest,
} from 'pdfdancer-client-typescript';
const replaced = await pdf.text().replace(
TextReplaceRequest.literal('Draft', 'Final').build()
);
const inserted = await pdf.text().insert(
TextInsertRequest.after('Invoice total', ' including tax').build()
);
const deleted = await pdf.page(2).text().delete(
TextDeleteRequest.literal('Internal only').build()
);
const styled = await pdf.text().style(
TextStyleRequest.literal('Overdue')
.fillColor(PdfColorRequest.rgb(0.85, 0.1, 0.1))
.build()
);
import com.pdfdancer.common.request.PdfColorRequest;
import com.pdfdancer.common.request.TextDeleteRequest;
import com.pdfdancer.common.request.TextInsertRequest;
import com.pdfdancer.common.request.TextReplaceRequest;
import com.pdfdancer.common.request.TextStyleRequest;
var replaced = pdf.text().replace(
TextReplaceRequest.literal("Draft", "Final").build());
var inserted = pdf.text().insert(
TextInsertRequest.after("Invoice total", " including tax").build());
var deleted = pdf.page(2).text().delete(
TextDeleteRequest.literal("Internal only").build());
var styled = pdf.text().style(
TextStyleRequest.literal("Overdue")
.fillColor(PdfColorRequest.rgb(0.85, 0.1, 0.1))
.build());
Replacement and anchor insertion inherit appearance from existing text. Coordinate insertion requires an explicit font and size. Styling changes appearance without changing characters. Deletion removes selected text; it is not a security redaction operation.
Read the response
| Field | Meaning |
|---|---|
matched | Number of selector matches or insertion targets |
changed | Number of changes actually applied |
pagesChanged | One-based page numbers changed |
change | Per-change details, including requested and applied layout |
warnings | Nonfatal conditions, including permitted layout fallback |
errors | Requested changes that were not applied |
matched > 0 is not sufficient for success. If changed < matched, inspect warnings and errors before saving.
- Python
- TypeScript
- Java
if response.matched == 0:
raise RuntimeError("Required text was not found")
if response.errors or response.changed != response.matched:
raise RuntimeError(f"Not every match changed: {response.errors}")
for warning in response.warnings or ():
print(warning.code, warning.page, warning.message)
if (response.matched === 0) throw new Error('Required text was not found');
if (response.errors?.length || response.changed !== response.matched) {
throw new Error(`Not every match changed: ${JSON.stringify(response.errors)}`);
}
for (const warning of response.warnings ?? []) {
console.warn(warning.code, warning.page, warning.message);
}
if (response.matched() == 0) {
throw new IllegalStateException("Required text was not found");
}
if (response.errors() != null && !response.errors().isEmpty()
|| !response.matched().equals(response.changed())) {
throw new IllegalStateException("Not every match changed: " + response.errors());
}
if (response.warnings() != null) {
response.warnings().forEach(warning -> System.err.println(
warning.code() + " on page " + warning.page() + ": " + warning.message()));
}
When visible text does not match
PDF text is stored as positioned glyphs and runs. A phrase that looks continuous in a viewer may be split internally. If a literal selector finds nothing:
- Confirm the page scope and capitalization.
- Try a smaller, unique literal.
- Inspect the source PDF with a PDF viewer or extraction tool to determine how its visible text is encoded.
- Use a narrowly bounded regular expression.
Continue with Replace, Insert, and Delete, Styling Text, or Text Layout.