Bounded paragraph editing

HPDFParagraphReflow selects loaded glyph centers inside an explicit source rectangle and writes a newly measured paragraph inside an explicit target rectangle on the same page

The acceptance profile covers Delphi Win32/Win64 and Windows FPC/Lazarus; measured font generation uses the existing document appearance builder and its installed-font support

Selection, layout, and preservation

Coordinates are unrotated PDF page user-space points, with an upward y axis; both rectangles must be finite, have positive dimensions no larger than 14400 points, and fit the page MediaBox

The selection can span multiple original baselines and multiple page content streams; literal, hexadecimal, TJ, single quote, and double quote text operands are rewritten at selected character-code boundaries

Retained prefixes, suffixes, text advances, and original numeric kerning adjustments stay in place; the source text is removed from actual operands rather than hidden by a background rectangle

Nested Form XObjects use their Matrix and BBox; each selected invocation and its ancestor resources are copied privately, preserving other invocations and other pages that share the original Forms

Glyph centers outside a Form BBox are excluded; partial source glyph bounds reject by default, and RejectPartialSourceGlyphs = False explicitly allows removal of an entire character code when its center is selected

Appearance chooses the installed font, style, size, color, padding, alignment, and overflow policy; target width controls wrapping, and shrink-to-fit reports the actual EffectiveFontSize

Generated glyph geometry is independently interpreted and checked against the target rectangle; rotation is fixed to zero and opacity to one for this bounded helper

An empty replacement deletes the selected source text; set RequireSourceText = False to insert a paragraph into an empty source region

Collision evidence

Unselected non-whitespace text, XObject and inline images, painted path bounds, and annotation rectangles are compared with the generated replacement glyph bounds

Each collision identifies its kind, page-space bounds, optional neighboring text, and a replacement glyph index; the total count is retained even when the returned collision list reaches its cap

Path control points, transformed Form clip bounds, stroke width and miter limits produce conservative boxes; shading with an unknown paint extent conservatively occupies the whole target rectangle

This geometry test can overreport transparent images, unpainted annotation areas, curves, clipped paths, and rotated Form envelopes; it does not prove pixel-perfect ink separation or inspect arbitrary clipping paths, blend compositing, or font outlines

RejectCollisions = True rejects overlapping replacements without mutation; setting it to false retains the report while allowing the caller's deliberate overlap policy

Preview, commit, and rollback

HPDFPreviewParagraphReflow returns a caller-owned sealed plan, semantic replacement layout, fingerprints, and collision evidence, then rolls back generated document objects

HPDFCommitParagraphReflow repeats the bounded analysis and atomically applies the plan only when source content, Form bounds, annotation geometry, and replacement font geometry still match the preview

A plan belongs to the same live document instance; free it when finished, keep the document alive throughout, and regenerate the preview after loading another document or changing selected content

THPDFParagraphReflowSession adds an applied review state: Apply exposes the edited document for extraction or rendering, Commit publishes it, and Rollback or destruction restores the original graph

Use sessions serially; the document must outlive the session, and other graph transactions or editing calls must not be interleaved while Pending is true

Rollback restores in-memory document state; already saved external files remain the caller's responsibility

Finite work and failure behavior

Default limits are 64 MiB decoded content, 64 MiB generated streams, 100000000 work units, 100000 glyphs, 250000 tokens, 12 Form levels, 256 Form invocations, 256 returned collisions, and 100000 cloned resource nodes

Encoded string bytes are checked before glyph interpretation against the remaining glyph limit, so multi-byte character encodings may conservatively consume more budget than their visible glyph count

Decoded and generated limits cover content handled by the helper; generated stream size is checked before publication, and does not represent a process peak-memory limit or cap all installed-font builder allocations

Cancellation is cooperative through content tokenization and helper loops, with checks around existing font-generation calls; retain the token until the call completes

Missing source, invalid input, collisions, stale plans, unsupported content, exhausted budgets, cancellation, and ordinary failures return false with an explicit status and preserve the original object graph

Selected vertical or singular text, cyclic or unresolved Forms, unsupported painted XObject types, and documents with signature fields are rejected; signature-preserving incremental editing requires a separate workflow

Example

Options := HPDFDefaultParagraphReflowOptions;
Options.Appearance.FontName := 'Arial';
Options.Appearance.FontSize := 11;
Source.Left := 55; Source.Bottom := 659;
Source.Right := 350; Source.Top := 714;
Target := Source;
Plan := nil;
try
  if HPDFPreviewParagraphReflow(Document, 0, Source, Target,
    'The Supplier must deliver signed monthly reports.', Options,
    Plan, Report, CancellationToken) then
    if HPDFCommitParagraphReflow(Document, Plan, Report, CancellationToken) then
      Document.SaveLoadedDocument('reviewed-contract.pdf');
finally
  Plan.Free;
end;

Acceptance and saved output

The dedicated Delphi and FPC runners use real loaded PDF fixtures for multi-baseline paragraphs, partial operands, nested shared Forms, clipped Form text, width and font changes, collision kinds, cancellation, and graph rollback

Repeated file/file and stream/file saves preserve the edited graph; fresh unchanged loaded documents keep their exact source bytes, and an injected output write failure can be followed by a successful retry

Tests/Fixtures/ParagraphReflow/loaded-repeat-save.pdf isolates the save regression without using the paragraph helper: load, insert a marker, save twice, and independently read the second file

Run Tests/Delphi/Run-ParagraphReflowTests.ps1 -Platform Win64 or Tests/FPC/Run-ParagraphReflowTests.ps1 -Platform Win64, and select Win32 for the other architecture

The optional Python verifier Tests/Fixtures/ParagraphReflow/verify_output.py uses PyMuPDF to extract actual saved Unicode text and render edited pages; every runner suppresses automatic viewer launch

Related topics

Bounded paragraph editing · Options · Report