Skip to main content
Version: API v3

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

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")

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.

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()
)

Choose how to match text

Literal matching is the clearest choice when the text is known. Use a regular expression when part of the text varies, such as an invoice number. Keep the pattern narrow and add maxMatches when only a bounded number of edits is expected.

literal = TextReplaceRequest.literal("Acme Ltd", "Northwind GmbH").build()
invoice = (
TextReplaceRequest.regex(r"Invoice\s+#\d+", "Invoice")
.case_sensitive(False)
.max_matches(1)
.build()
)

wholeWords applies to literal selectors. maxMatches limits the number of eligible matches, which protects against changing more occurrences than intended.

PDF text is stored as positioned glyphs, not as a guaranteed plain-text paragraph. PDFDancer matches the text representation it can associate with those glyphs; unusual encoding, separated characters, or text converted to outlines can affect whether a literal or regular-expression target matches. Start with a small representative edit, inspect matched, and verify the rendered output before applying it in bulk.

Replace, insert, delete, and style

Each operation has a dedicated builder. These examples show one minimal edit of each kind.

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()
)

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

FieldMeaning
matchedNumber of selector matches or insertion targets
changedNumber of changes actually applied
pagesChangedOne-based page numbers changed
changePer-change details, including requested and applied layout
warningsNonfatal conditions, including permitted layout fallback
errorsRequested changes that were not applied

matched > 0 is not sufficient for success. If changed < matched, inspect warnings and errors before saving.

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)

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:

  1. Confirm the page scope and capitalization.
  2. Try a smaller, unique literal.
  3. Inspect matched, changed, warnings, and errors from a small representative edit.
  4. Use a regular-expression pattern that matches only the intended text, and set maxMatches when you expect a limited number of matches.

If the result still does not match the rendered text, the PDF may represent the visible content as separated glyphs, outlines, or another structure that is not editable text. In that case, inspect the rendered output and the source PDF as an optional diagnostic step rather than assuming that the visible phrase is one text target.

Continue with Replace, Insert, and Delete, Styling Text, or Text Layout.