Skip to content

Add typed API for existing PDF signature fields #62

Description

@vitormattos

Context

JSignPdf 3.2.0 supports:

  • listing existing PDF signature fields with --list-sig-fields;
  • signing an existing empty signature field with --sig-field.

jsignpdf-php should expose these capabilities through a typed PHP API.

Applications using this library should not need to know JSignPdf CLI syntax or parse JSignPdf output themselves.

This issue requires JSignPdf 3.2.0 to already be available as the default runtime.

Track the runtime update separately and mark this issue as blocked by it.

Goal

Allow PHP applications to:

  • inspect the signature fields reported by JSignPdf;
  • read the field name, page, rectangle and state;
  • select an existing signature field when signing.

The PHP API must hide JSignPdf CLI details from applications.

Important implementation rule

Do not parse PDF or AcroForm structures in PHP.

JSignPdf already discovers and interprets the signature fields.

The wrapper only needs to:

  1. run the existing JSignPdf commands;
  2. parse the documented JSignPdf 3.2.0 output;
  3. expose the result through a typed PHP API.

Do not add Java code or another PDF library.

Public model

Add a final immutable value object under the signing namespace:

final class SignatureField

It should expose:

public function getName(): string;
public function getPage(): int;

public function getLlx(): float;
public function getLly(): float;
public function getUrx(): float;
public function getUry(): float;

public function isSigned(): bool;
public function isHidden(): bool;

Also provide:

public function isBlank(): bool;
public function hasVisibleRectangle(): bool;

with:

isBlank() = !isSigned()

hasVisibleRectangle() =
    urx > llx
    && ury > lly

The object must not expose mutable setters.

Meaning of the values

SignatureField represents the values reported by JSignPdf.

Do not inspect the PDF again to verify or recalculate them.

In particular:

  • isSigned() represents the signed state reported by JSignPdf;
  • it does not mean that the PHP wrapper cryptographically validated the signature;
  • isHidden() represents the hidden state reported by JSignPdf;
  • page and rectangle values are the values reported by JSignPdf.

A zero-size rectangle is valid output and must remain in the result.

Inspect signature fields

Add this method to JSignService:

/**
 * @return list<SignatureField>
 */
public function getSignatureFields(JSignParam $params): array;

This is an inspection operation, not a signing operation.

It requires:

  • PDF content;
  • the configured Java runtime;
  • the configured JSignPdf runtime;
  • the existing temporary-file infrastructure.

It must not require:

  • a signing certificate;
  • certificate password;
  • TSA configuration;
  • other signing-only parameters.

Do not call signing validation that requires certificate material.

Run the inspection command

The inspection flow should:

  1. store the PDF using the existing temporary-file mechanism;
  2. execute JSignPdf with --list-sig-fields;
  3. use --quiet so normal JSignPdf logging does not interfere with the listing;
  4. force English JSignPdf messages for predictable parsing;
  5. parse the output;
  6. create SignatureField objects;
  7. preserve the order returned by JSignPdf;
  8. clean temporary files on success and failure.

Reuse the existing Java runtime resolution, JSignPdf runtime resolution and process execution infrastructure.

Do not create a second execution mechanism only for this feature.

JSignPdf 3.2.0 field output

JSignPdf formats a signature field as:

#<number> <field name> page <page> [<llx> <lly> <urx> <ury>] <state>

Example:

#1   CustomerSignature              page 1    [70.0 700.0 300.0 760.0] blank

Another example:

#2   Manager Signature              page 2    [70.0 600.0 300.0 660.0] signed, hidden

The states produced by JSignPdf are:

blank
signed
blank, hidden
signed, hidden

Coordinates are formatted by JSignPdf with Locale.ROOT.

The PHP parser must therefore parse . as the decimal separator independently from the PHP system locale.

The leading #<number> is part of the JSignPdf CLI representation.

It does not need to become part of the public PHP model.

Parse field names safely

JSignPdf formats the field name using a minimum display width of 30 characters.

Field names may contain spaces and may be longer than this width.

Do not:

  • use fixed character offsets;
  • split the complete line by spaces.

Parse the structural parts of the line:

#<number>
<field name>
page <page>
[<coordinates>]
<state>

and use the text between the leading number and the page ... [...] section as the field name after removing display padding.

Normal single-line Unicode field names must be supported.

Known limitation

The JSignPdf output is intended for human-readable CLI output, so it is not a lossless serialization of every possible PDF field name.

Because the field name is padded with spaces, trailing spaces that are part of the original PDF field name cannot be distinguished from display padding.

Do not try to guess them.

If an output entry cannot be parsed safely, fail explicitly instead of returning incorrect data.

Selector-shadow suffix

JSignPdf gives an exact field-name match priority over its auto and #N convenience selectors.

For a field whose literal name is:

auto

or a numeric selector such as:

#1

the listing may append:

- this field name shadows the selector of the same name, the field name wins

Ignore this informational suffix when parsing.

It must not become part of SignatureField.

PDF without signature fields

For a valid PDF without signature fields, JSignPdf prints:

<file>: no signature fields

and exits successfully.

getSignatureFields() must return:

[]

This is not an error.

Inspection failure

When JSignPdf cannot read the signature fields, it reports:

Can not read the signature fields of '<file>': <reason>

and the listing command exits with code 5.

getSignatureFields() must throw an exception.

Do not return an empty list for this case.

Preserve useful JSignPdf diagnostic information in the exception.

Unexpected output

Do not silently ignore a signature-field entry that does not match the expected JSignPdf 3.2.0 format.

If a field line cannot be parsed, throw an exception containing the unexpected line.

Do not guess a new format.

This avoids silently returning incomplete data if JSignPdf changes its CLI output in the future.

Do not reproduce JSignPdf internals

Do not add PHP logic for:

  • finding AcroForm fields;
  • deciding whether a PDF field is a signature field;
  • deciding whether a field is signed;
  • reading /Rect;
  • reading widget flags;
  • deciding field order;
  • resolving auto;
  • resolving #N;
  • deciding whether a field can be signed.

These rules belong to JSignPdf.

The wrapper only consumes the result.

Select an existing field for signing

Add to JSignParam:

public function setSignatureField(?string $fieldName): self;
public function getSignatureField(): ?string;

Default:

null

When the value is null, existing signing behavior must remain unchanged.

For a non-null value, JSignService must pass it to JSignPdf using:

--sig-field

Applications must not need to add this option manually through:

setJSignParameters()

Use the existing command escaping rules.

Do not reinterpret the configured value

The PHP wrapper must not implement its own signature-field selector.

Do not:

  • trim the configured value;
  • translate it to another identifier;
  • implement auto in PHP;
  • implement #N in PHP;
  • pre-resolve the value using getSignatureFields().

Pass the configured value to JSignPdf and let JSignPdf perform its normal resolution.

This preserves JSignPdf behavior for fields whose literal names are:

auto

or:

#1

because JSignPdf checks exact field names before interpreting its convenience selectors.

Field placement

Do not calculate or modify existing field placement in PHP.

JSignPdf resolves the selected field and uses its existing placement.

Do not copy the page or rectangle from SignatureField into the normal visible-signature coordinate options.

Signing failures

JSignPdf already handles invalid field selections, including cases such as:

  • no matching field;
  • no signature fields in the PDF;
  • already signed field;
  • invalid field selection.

Preserve useful JSignPdf diagnostic output when signing fails.

Do not duplicate these validations in PHP before calling JSignPdf.

Backward compatibility

Existing applications must continue working without changes.

In particular:

  • JSignParam without setSignatureField() follows the current signing path;
  • setJSignParameters() remains available;
  • certificate handling remains unchanged;
  • password handling remains unchanged;
  • TSA behavior remains unchanged;
  • existing visible-signature behavior remains unchanged when no field is configured.

Do not redesign unrelated wrapper APIs.

Tests

SignatureField

Add unit tests for:

  • constructor values;
  • getters;
  • isSigned();
  • isBlank();
  • isHidden();
  • hasVisibleRectangle();
  • zero-size rectangle.

Output parser

Use strings based on the real JSignPdf 3.2.0 formatter.

Cover:

  • blank field;
  • signed field;
  • blank hidden field;
  • signed hidden field;
  • field name with spaces;
  • single-line Unicode field name;
  • field name longer than 30 characters;
  • literal field name auto;
  • literal field name #1;
  • selector-shadow suffix;
  • decimal coordinates;
  • negative coordinates;
  • multiple fields;
  • malformed field entry.

A malformed field entry must fail instead of being ignored.

Do not invent alternative JSignPdf output formats for the tests.

Inspection integration

Use small real PDF fixtures for:

  • PDF without signature fields;
  • one blank signature field;
  • multiple signature fields;
  • blank and signed fields together;
  • hidden signature field;
  • zero-size rectangle;
  • unreadable or invalid PDF.

Verify the PHP result against the values reported by JSignPdf.

Do not add PHP PDF parsing only to implement test assertions.

Signing integration

Cover:

  • signing a blank field by name;
  • field name with spaces;
  • single-line Unicode field name;
  • literal field name auto;
  • literal field name #1;
  • attempting to sign an already signed field;
  • attempting to sign a missing field;
  • signing without setSignatureField() keeps the current behavior.

Let JSignPdf resolve and validate the selected field.

Documentation

Update the README with small examples.

Inspect fields

$params = JSignParam::instance()
    ->setPdf($pdf);

$fields = (new JSignService())->getSignatureFields($params);

foreach ($fields as $field) {
    if ($field->isBlank()) {
        echo $field->getName();
    }
}

Sign an existing field

$params = JSignParam::instance()
    ->setPdf($pdf)
    ->setCertificate($certificate)
    ->setPassword($password)
    ->setSignatureField('CustomerSignature');

$signedPdf = (new JSignService())->sign($params);

Applications should not need to call --list-sig-fields or --sig-field directly.

Out of scope

Do not add:

  • Java code;
  • changes to JSignPdf;
  • another PDF library;
  • PHP AcroForm parsing;
  • generic PDF form editing;
  • PDF field creation;
  • automatic field selection in PHP;
  • PHP implementation of auto;
  • PHP implementation of #N;
  • recipient concepts;
  • workflow concepts;
  • application-specific business rules.

Good first issue

The public API and JSignPdf 3.2.0 integration behavior are already defined.

The contributor does not need to research PDF internals or design signature-field behavior.

Reuse:

  • JSignParam;
  • JSignService;
  • the existing Java runtime resolution;
  • the existing JSignPdf runtime resolution;
  • temporary-file handling;
  • command execution and escaping patterns.

The main work is:

  1. add SignatureField;
  2. add getSignatureFields();
  3. run --list-sig-fields --quiet;
  4. parse the documented JSignPdf 3.2.0 output;
  5. add setSignatureField() and getSignatureField();
  6. pass the configured value through --sig-field;
  7. add focused tests;
  8. update the README.

Do not expand this issue into PDF parsing or application field-domain design.

If real JSignPdf 3.2.0 output is found that is not covered here, discuss it before adding new public API behavior.

Done when

  • SignatureField provides an immutable typed representation of JSignPdf field information.
  • getSignatureFields() works without signing credentials.
  • Inspection uses quiet mode and predictable English messages.
  • Valid PDFs without signature fields return an empty list.
  • Inspection failures throw an exception.
  • Blank, signed and hidden states are represented.
  • Zero-size rectangles are preserved.
  • Field names are parsed without fixed-width assumptions.
  • Normal single-line Unicode names are supported.
  • Unexpected field entries are not silently ignored.
  • Applications can configure an existing field through setSignatureField().
  • The wrapper does not trim or reinterpret the configured value.
  • auto and #N are not implemented in PHP.
  • Applications do not need to construct signature-field CLI options.
  • Existing signing behavior remains unchanged when no field is configured.
  • Useful JSignPdf errors are preserved.
  • Temporary files are cleaned on success and failure.
  • Unit, parser and integration tests cover the supported behavior.
  • README documents inspection and signing with existing fields.

Activity

  1. vikas-kushwaha-dev commented on Sep 16, 2026

    @vikas-kushwaha-dev
    Contributor

    Hi @vitormattos, I'd also be interested in working on this after #63 is completed. I'm planning to start with the JSignPdf 3.2.0 runtime update in #63 first, then I can work on the typed signature-field API here.

    If that approach works for you, could you assign this issue to me as well?

  2. vitormattos commented on Sep 16, 2026

    @vitormattos
    ContributorAuthor

    @vikas-kushwaha-dev finish first the issue #63, after merging, I can assign this to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions