A form field looks correct in a PDF viewer but shifts when it is flattened onto the page. The widget rectangle may be right even though the appearance uses a different origin or rotation. PDFlibPas accounts for /BBox and /Matrix when placing that appearance inside the widget rectangle
A logical field can have more than one widget annotation, and each widget has its own rectangle and appearance on a page. Flattening takes the chosen appearance and paints it as ordinary page content. When you change a field value first, the appearance must reflect that value before it is placed. PDFlibPas offers FlattenFormField to place an existing appearance and UpdateAndFlattenFormField to update it before flattening. The placement question remains the same for both: which coordinates describe the appearance, and how do they map to the widget's page rectangle?
The rectangle you see is not the rectangle the appearance uses
A widget's rectangle is expressed on the page. Its normal appearance, commonly reached through /AP and /N, is a Form XObject with drawing instructions and a bounding box in the appearance's own coordinate system. The reader transforms and places that appearance into the widget rectangle before displaying it. Width and height alone do not tell the whole story. A bounding box may begin at a nonzero coordinate, and its appearance matrix can translate, scale or rotate the contents. If a placement calculation scales only the original dimensions into the page rectangle, it has skipped the origin and transformation that give those dimensions meaning. This distinction becomes visible when the original form looks aligned but the painted copy lands away from its widget
Widget Rect: [100 200 300 240]
Appearance BBox: [10 20 110 40]
Appearance Matrix: [1 0 0 1 0 0]
Here the appearance is 100 units wide and 20 units high, with its lower-left corner at (10, 20). The widget rectangle is 200 by 40. The placement calculation must account for the offset before mapping the appearance into that rectangle. The identity matrix in this example keeps rotation out of the calculation, making the nonzero origin easy to see
What changes when the appearance is rotated?
Before you calculate the placement, transform all four corners of /BBox through the appearance's /Matrix, then take the minimum and maximum transformed coordinates to obtain the bounds mapped to /Rect. ISO 32000-2 section 12.5.5 describes the placement rule. Why all four? Two opposite corners can appear sufficient for translation and positive scaling, but rotation or negative scaling can move an extremum to one of the other corners. Calculating four points is inexpensive; the shortcut is a poor trade. PDFlibPas uses the transformed bounds when flattening. If the result shifts, check the original box origin and matrix before changing the page rectangle—reasonable-looking dimensions do not establish where the appearance begins in its own coordinates
Update the appearance before you remove the field
A field's value and its appearance stream are related, but changing one does not by itself describe what an existing cached appearance contains. If an application edits values immediately before flattening, update the appearance or use UpdateAndFlattenFormField so the painted content reflects the current field. FlattenFormField returns 1 on success and 0 when the field cannot be flattened. The public field indices are one-based. The following loop works from the last field toward the first, avoiding the skipped-index problem that occurs when removing an entry shifts later fields down
uses
SysUtils, PDFlibrary;
procedure FlattenFields(Pdf: TPDFlib);
var
FieldIndex: Integer;
begin
for FieldIndex := Pdf.FormFieldCount downto 1 do
if Pdf.UpdateAndFlattenFormField(FieldIndex) <> 1 then
raise Exception.CreateFmt(
'Could not flatten field %d', [FieldIndex]);
end;
The routine expects Pdf to own an already loaded document. You save the document after the routine succeeds. The wrapper below opens the input, calls the reverse-order routine and writes a copy under the supplied output name; it checks both loading and saving so the application can treat a failed operation as a failure rather than an archival result
procedure FlattenCopy(const InputFile, OutputFile: string);
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile(InputFile, '') <> 1 then
raise Exception.Create('Cannot open the PDF');
FlattenFields(Pdf);
if Pdf.SaveToFile(OutputFile) <> 1 then
raise Exception.Create('Cannot save the flattened copy');
finally
Pdf.Free;
end;
end;
After you save, reopen the output and check the remaining field count alongside the painted appearance. If you need a signature or approval field to remain interactive, select the fields to flatten explicitly instead of applying the all-fields loop unchanged
Why does the field index move after flattening?
A list of three fields makes the indexing trap concrete. Flatten field 1 and the list contracts: the former field 2 now has index 1, and the former field 3 has index 2. A forward loop that advances to index 2 has skipped the field that moved into index 1. Work backward. Removing a later field leaves the indices of earlier fields alone, which makes a descending loop a safer default when every selected field is to be flattened. Selection itself is an application decision: some fields may need to remain interactive. Establish that policy before the loop, and capture field names before removing entries if diagnostics must identify a failed operation. A numeric index can refer to another field after the list contracts. A logical field can also have several widgets, so page location helps identify which appearance was placed
Hidden widgets and missing appearances
Visibility is part of the form's presentation. A widget marked Hidden or NoView should not become visible just because the form is flattened, so include those flags when reasoning about the page output. Some documents provide a value without a usable normal appearance; PDFlibPas can generate an appearance before flattening. If a valued field still cannot produce one, the operation reports failure and retains the field. Check the return value and keep that field identifiable in the calling workflow, rather than assuming that every entry was removed merely because the loop reached the end of the document
Compare the original and flattened fields
A compact test document can contain a text field, a check box, a widget with a nonzero appearance-box origin and a rotated widget. Add a hidden widget and a visible field whose value is changed immediately before flattening. Render the input and the saved output at the same page size and resolution, then reopen the output to check that the expected marks appear and the flattened fields no longer remain interactive. For output that needs accessible structure, verify the reading structure as well as the painted marks. A correct placement on the page and an appropriate reading structure answer different requirements
When a mark shifts, keep the unflattened input available and read its original appearance stream, /BBox, /Matrix and widget /Rect. The final painted page alone cannot reconstruct the original appearance origin. Record the page and field name along with those geometry values, particularly when one field has widgets on more than one page. A repeat run from the same unchanged input can help establish whether the two flattened outputs agree. If they agree with each other but differ from the input, focus on appearance placement and visibility; if the input changes between runs, isolate the field edit or appearance update before attributing the shift to placement. The normal appearance can have its own resources and drawing instructions, so follow the appearance stream actually selected for the widget. If two widgets belong to one field, their page rectangles and selected appearances can differ; keep those widget-specific entries with the field name when tracing where each mark was placed
For field behavior before flattening, see the interactive form guide; the content stream graphics-state guide follows the transformation machinery. The Delphi APIs above belong to PDF Library for Delphi