Bounded paragraph editing
HPDFParagraphReflow selects loaded glyph centres 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 centres 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 centre is selected
Appearance chooses the installed font, style, size, colour, 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 whilst 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 whilst Pending is true
Rollback restores in-memory document state; already saved external files remain the caller's responsibility
Finite work and failure behaviour
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 tokenisation 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