Skip to content

DSS engine: add background image layering, text auto-scaling, and tolerant field placement - #433

Open
sam2kb wants to merge 6 commits into
intoolswetrust:masterfrom
sam2kb:pr-dss-visual
Open

DSS engine: add background image layering, text auto-scaling, and tolerant field placement#433
sam2kb wants to merge 6 commits into
intoolswetrust:masterfrom
sam2kb:pr-dss-visual

Conversation

@sam2kb

@sam2kb sam2kb commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Add background image layering, text auto-scaling & tolerant placement to DSS engine

What
Background image support. The DSS engine currently only allows a single image - either a signature graphic or a background. This PR adds proper layering: you can now set a background image (with scale control) that renders behind the signature graphic behind the text, all in one signature block.

Text that fits any rectangle. DSS defaults to a fixed font size - text gets cut off on small signature boxes. This switches to auto-scaling mode (FILL_BOX_AND_LINEBREAK), where the font shrinks to fit whatever rectangle you draw, wrapping lines naturally. The user's font size preference still acts as an upper cap (no giant text on huge boxes).

Transparent text background. DSS draws a solid white rectangle behind signature text, which covers any background image underneath. This sets it to transparent so the background image shows through.

Friendlier annotation overlap. If your visible signature rectangle overlaps an existing PDF annotation (e.g. an Adobe stamp), DSS throws an error and refuses to sign. This changes it to a warning - signing proceeds, and the signature appears on top.

Why
These are the features our users needed to fully replace the old OpenPdf engine with DSS:

Background images are essential for professional stamps, seals, and letterhead
Auto-scaling text is critical for workflows with varying signature box sizes
Overlap tolerance is needed when signing over documents that already have annotations

How
Four new classes in engines/dss/.../pdfbox/ plus changes to DssSigningEngine.java to wire them in. All behaviour is opt-in via the existing GUI controls - nothing changes for users who don't use these features.

@kwart

kwart commented Jul 5, 2026

Copy link
Copy Markdown
Member

Thanks for this — background layering + autoscaling text are genuinely useful additions to the DSS engine. Since the DSS engine hasn't shipped yet (targeting 3.1), I've focused on intrinsic correctness, licensing, and structure. A few things I'd like to see addressed before this lands as the shipped engine:

Please fix before merge

  1. Background image is ignored by getExpectedColorSpaceName() (JSignPdfOverlaySignatureDrawer). It only inspects parameters.getImage() (the foreground graphic). For a background-only signature, it falls through to text color → DEVICEGRAY, and DSS's checkColorSpace() can then inject a DEVICEGRAY output intent into a document that actually contains an RGB/CMYK background image. That's wrong output and a PDF/A inconsistency. The background image needs to participate in color-space determination.

  2. Licensing header. JSignPdfOverlaySignatureDrawer is a near-verbatim fork of DSS's LGPL-2.1 NativePdfBoxVisibleSignatureDrawer, with the copyright/LGPL notice stripped. Please restore the DSS header (or clearly attribute the origin) — this is the moment to get it right before it ships in a release artifact.

  3. Dropped image rotation. The custom setImageScaledToBox() doesn't apply the per-image rotation transform that DSS's setImage() had. If the engine advertises field rotation, a rotated field will render an unrotated graphic.

  4. isEmpty() override is effectively dead. DSS's AbstractPdfBoxSignatureDrawer.init() validates via assertSignatureParametersAreValid(), which checks getImage()==null && text.isEmpty() directly — it never consults your overridden isEmpty(). So a true background-only signature (no foreground image, no text) still throws "Neither image nor text parameters are defined!". It only works today because configureVisibleSignature() always sets L2 text. Either make background-only genuinely reachable or drop the override.

Structural concern

  1. This is a 415-line fork of DSS internals with no tests. It couples to non-public classes (PdfBoxDSSFontMetrics, PdfBoxFontMapper, SignatureFieldDimensionAndPosition.setTextSize()), which can shift on any DSS bump. See the drawer-selection suggestion below, which quarantines this risk, plus please add at least a smoke test (sign with background + graphic + text; assert a valid signature and no spurious output intent).

Suggestion: select the drawer automatically instead of always replacing it

Right now JSignPdfSignatureDrawerFactory returns the custom drawer for every signature, so the fragile fork is in the hot path even for plain text/graphic signatures that DSS's stock drawer already handles well. Instead, let the input pick the drawer — the factory already receives the parameters, which is DSS's designed extension point:

public class JSignPdfSignatureDrawerFactory implements PdfBoxSignatureDrawerFactory {
    @Override
    public PdfBoxSignatureDrawer getSignatureDrawer(SignatureImageParameters p) {
        // Only use the custom layering drawer when a background image is actually configured
        if (p instanceof JSignPdfSignatureImageParameters jp && jp.getBackgroundImage() != null) {
            return new JSignPdfOverlaySignatureDrawer();
        }
        // Otherwise fall back to DSS's stock native drawer
        return new NativePdfBoxVisibleSignatureDrawer();
    }
}

Why this is worth doing:

  • Quarantines the fork — the fragile custom drawer only runs when someone sets a background image. A DSS upgrade that breaks it can't break the default signing path.
  • Clean PDF/A path by default — the stock drawer has no transparency and no color-space gap (Translation in Greek #1 above), so signatures without a background image stay compliant automatically.
  • No new config/GUI knobs — the behavior follows the inputs the user already supplies, rather than a mode toggle users can't reason about.

Separately, the overlap relaxation (LogOnStatusAlert) is currently applied globally in DssSigningEngine.sign(). That one is a real policy choice with no input to infer it from, so I'd make it an explicit opt-in defaulting to DSS's strict throw — safe by default, escape hatch for those who knowingly sign over existing annotations.

Alex and others added 4 commits July 16, 2026 11:55
…erant field placement

- JSignPdfSignatureImageParameters: extends DSS SignatureImageParameters
  with backgroundImage, backgroundScale, and overrides isEmpty() so
  background-only signatures are not silently skipped.

- JSignPdfOverlaySignatureDrawer: custom PDFBox drawer that renders
  background image → foreground graphic → text in layers, capped at
  the user's preferred font size when FILL_BOX_AND_LINEBREAK is used.

- JSignPdfPdfObjFactory / JSignPdfSignatureDrawerFactory: wire the
  custom drawer into PdfBoxSignatureService.

- DssSigningEngine changes:
  * Use JSignPdfPdfObjFactory with LogOnStatusAlert (warn instead of
    throw) when the signature field overlaps existing annotations.
  * Use JSignPdfSignatureImageParameters for background+foreground
    image support (upstream limits to a single image).
  * Set TextWrapping.FILL_BOX_AND_LINEBREAK so text auto-scales to
    fit the signature rectangle.
  * Set text background to transparent so the background image shows
    through the signature text.
1. Add LGPL-2.1 license header to JSignPdfOverlaySignatureDrawer,
   attributing derivation from DSS's NativePdfBoxVisibleSignatureDrawer.

2. Fix getExpectedColorSpaceName() to also inspect background image
   color space, preventing wrong DEVICEGRAY output intent injection.

3. Restore per-image rotation transform in setImageScaledToBox(),
   mirroring NativePdfBoxVisibleSignatureDrawer's image-rotation handling.
   Adds applyImageRotation() helper for ANGLE_90/180/270.

4. Override init() to allow background-only signatures (no foreground
   image, no text, only a background image). The DSS super.init() path
   bypasses isEmpty() and rejects these via assertSignatureParametersAreValid().

5. Conditional drawer selection: JSignPdfSignatureDrawerFactory now
   returns NativePdfBoxVisibleSignatureDrawer by default and only uses
   the custom JSignPdfOverlaySignatureDrawer when a background image is
   configured. Quarantines the fragile fork from the default signing path.

6. Add smoke test: visible signature with background image + foreground
   graphic + L2 text, asserting a valid PAdES-B signature.
Alex Kay and others added 2 commits July 16, 2026 13:02
Checkstyle prohibits /* block comments at the start of source files.
Use // line comments for the DSS derivation / LGPL-2.1 attribution.
- getCurrentImageRotation() -> getGlobalRotation()
- ImageScaling.ZOOM_AND_CENTER to avoid STRETCH+FILL_BOX_AND_LINEBREAK reject
- overlap relaxation now opt-in via engine.dss.relaxFieldOverlap
- class JavaDoc for JSignPdfOverlaySignatureDrawer
@sam2kb

sam2kb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing the PR.
I've been using the original version with most of these changes for over a year, signing architectural and engineering plans.
Everything should be addressed now, let me know.

@kwart

kwart commented Jul 21, 2026

Copy link
Copy Markdown
Member

Thanks for the thorough follow-up — all five points plus the drawer-selection and opt-in overlap suggestions are properly addressed. The color-space fix, restored attribution, image rotation, reachable init(), and the quarantined factory + smoke test all look good.

Two things I'd still like fixed before this ships:

  1. Font-size cap only applies on the background-image path. configureVisibleSignature() sets FILL_BOX_AND_LINEBREAK unconditionally, but the cap lives in JSignPdfOverlaySignatureDrawer.buildSignatureFieldBox(), which only runs when a background image is set. So text/graphic signatures without a background go through the stock drawer and auto-scale up with no cap — contrary to the "font size acts as an upper cap" behaviour. Either apply the cap on both paths, or only enable FILL_BOX_AND_LINEBREAK when the custom drawer is active.

  2. VISIBLE_BACKGROUND_IMAGE_SCALE capability isn't declared. The engine now honours bgImgScale, but its capability set lists VISIBLE_BACKGROUND_IMAGE without ..._SCALE, so EngineMismatchValidator will warn users that scaling is unsupported even though it now works. Please add the capability.

Minor: the new test uses setBgImgScale(100f) (a 100× multiplier on a 10×10 image, clipped by the BBox) — -1f/1f would exercise it more realistically, and asserting on the output color space / absence of a spurious output intent would directly cover the fix #1 regression.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants