Skip to content

Draw a signature field's box, and keep a mark once it's in - #982

Merged
bobsingor merged 4 commits into
mainfrom
feature/signature-field-box
Nov 4, 2026
Merged

bobsingor merged 4 commits into
mainfrom
feature/signature-field-box

Conversation

@bobsingor

@bobsingor bobsingor commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

What changes

A signature field's style panel offered a border colour, background, border width and style, but picking one made the field flicker and go back. The engine wrote /MK and /BS and then had nothing to draw them with: the fork's form generator had no /Sig branch, so a restyle reported generation-unavailable, and the form painter skipped signature fields. The viewer drew the new style while the change was pending, then fell back to the engine's picture, which was empty.

Now a signature field's widget draws its box in its style, like every other widget, in both places that draw it:

  • Engine: placing a signature field writes its box (the fork bakes it on attach), and a restyle draws it again. A widget in no field draws its box too.
  • Viewer: the form painter draws the same box live, so the drawing while a change is pending and the engine's picture after it are the same.

Once a mark is in the field, it shows the mark instead. That happens when the field is signed, or a mark was drawn in with forms.setSignatureAppearance:

  • The widget reads hasMark: true.
  • Its style no longer applies. A style change is refused with InvalidArg; the style it reads, sent back, is no change.
  • The engine never draws over the mark. A resize keeps it.
  • The viewer treats it as the widget-mark kind, which has no style rows, so the style panel offers nothing that wouldn't show.
const { field } = await doc.forms.create({
  family: 'signature',
  name: 'approval',
  widgets: [{ page, rect, color: '#1a1a1a', interiorColor: '#eeeeee' }],
});
await doc.forms.updateWidget(field.widgets[0].ref, { interiorColor: '#d43c2c' }); // redrawn
await doc.forms.setSignatureAppearance(field.ref, { pdf: artwork });
// the widget now reads hasMark: true, and a style change fails with InvalidArg

By layer:

  • Runtime: pinned to 4346c5131. It brings Draw a signature field's box runtime#114 (GenerateBoxFormAP, the /Sig and no-field branches, the bake on attach) and Zooming with Ctrl + mouse wheel #113 (a comment's rich text reads over the reader's defaults, safe since Read and write a markup annotation's comment as rich text #978). No function is added.
  • Engine core:
    • The widget declaration gains hasMark, an engine field.
    • A placed signature widget is predicted with its appearance.
    • WIDGET_STYLE_NAMES lists a widget's style fields. It lives in a zod-free module, so the runtime entry still reaches no zod.
  • Engine services:
    • The widget read joins hasMark: a signature field's widget, with a value (signed) or a recorded fit.
    • assertMarkKeepsItsLook refuses a style change on a marked widget before anything is written.
    • A marked widget is never redrawn.
    • A visual fill records its fit in /EMBD_Metadata/AppearanceFit, the key a stamp records its own in. The key's read and write move to appearanceFit.ts, shared by both. Signing records nothing: a signing changes no other key of its widget, and the signing analysis holds it to that.
  • Viewer:
    • core-form paints signature fields: fieldBoxOps, the port of the fork's WriteFieldBox, shared with the text field.
    • core-annotation adds the widget-mark kind; widgetKindOf takes the widget.
    • plugin-form gives a marked widget no painter.
    • plugin-annotation: a lent record with no live drawing keeps its picture while a new look is pending, instead of drawing its bare style.
  • Contract and SDKs: api:sync. The TypeScript SDK's WidgetAnnotation gains hasMark.
  • Docs: "Move and style widgets" and the signature family row on the forms authoring page, and the visual fill on the signatures page, synced to both sites.
  • Changesets: engine-core, engine-services, core-annotation, core-form, cloudpdf-contract and cloudpdf-sdk (minor); plugin-form and plugin-annotation (patch).

Not in this PR:

  • The empty-panel message: a selected field with a mark in it shows the style panel's generic empty text ("Pick a draw tool or select an annotation to edit its style."), the same text a locked annotation shows.
  • Re-fitting a mark: a resized mark stretches as the file places it; it isn't re-fitted into the new box the way a stamp is.

Testing

Run on the branch with main merged in, against the pinned runtime's code (native and wasm, built from the same tree as 4346c5131):

  • Engine (signature-visual-fill.test.ts):
    • a placed field has its box in its style;
    • a restyle is regenerated and drawn in the new colour;
    • after a visual fill the widget reads hasMark;
    • a style change is refused, and the same style sent back is accepted;
    • a resize keeps the mark, with no box drawn over it;
    • a field signed without artwork reads hasMark and refuses a style change too.
  • Painter parity: 12 new signature cases (every border style, no colours, three turns). The painter draws what the engine writes, item by item.
  • Viewer:
    • design.integration.test.ts: a restyled signature field draws live by the painter, then is confirmed by the engine. A marked one is widget-mark with no live drawing.
    • Kind tests: a marked widget has no style rows.
  • Suites: engine 1,943, engine-core 1,059, core-annotation 672, plugin-annotation 289, plugin-form 189 and core-form 15 pass, and the touched packages typecheck.
  • In the viewer: a placed field restyled red keeps its colour after the engine confirms. With a mark placed in it, the field shows the mark, and its style panel offers nothing.

A signature field's widget now draws its box: its background and
border, in its style. Placing one writes it, and a restyle draws it
again, so the colors the style panel offers show, instead of flashing
and going back. The form painter draws the same box live.

Once a mark is in the field (it's signed, or a mark was drawn in with
forms.setSignatureAppearance), the widget reads hasMark: true and shows
the mark. Its style no longer applies: a style change is refused with
InvalidArg, a resize keeps the mark, and the viewer offers it as the
widget-mark kind, which has no style rows. A mark drawn in records its
fit as a stamp records its own.

Needs the runtime with the fork's signature box (pinned separately).
@vercel

vercel Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cloudpdf-com Ready Ready Preview Nov 5, 2026 1:21am UTC
embed-pdf-snippet Error Error Nov 5, 2026 1:21am UTC
embed-pdf-viewer-react-mui Error Error Nov 5, 2026 1:21am UTC
embed-pdf-viewer-svelte-tailwind Error Error Nov 5, 2026 1:21am UTC
embed-pdf-viewer-vue-vuetify Error Error Nov 5, 2026 1:21am UTC
embed-pdf-website Ready Ready Preview Nov 5, 2026 1:21am UTC

Request Review

The release brings fork PRs 113 and 114: a signature field's widget and
a widget in no field draw their box (GenerateBoxFormAP, the /Sig and
no-field branches of EPDFAnnot_GenerateFormFieldAP, and the bake on
attach), and a comment's rich text reads over the reader's defaults (the
engine reads a redact's overlay font from /DA since PR 978). No function
is added; runtime-src moves to the pin.

This branch had an error being deployed

4 failed and 2 active deployments
Preview – cloudpdf-com — 1df86b43 Deployed Nov 5, 2026 by vercel[bot]
Preview – embed-pdf-website — 1df86b43 Deployed Nov 5, 2026 by vercel[bot]
Preview – embed-pdf-snippet — 1df86b43 Deployed Nov 4, 2026 by vercel[bot]
Preview – embed-pdf-viewer-vue-vuetify — 1df86b43 Deployed Nov 4, 2026 by vercel[bot]
Preview – embed-pdf-viewer-react-mui — 1df86b43 Deployed Nov 4, 2026 by vercel[bot]
Preview – embed-pdf-viewer-svelte-tailwind — 1df86b43 Deployed Nov 4, 2026 by vercel[bot]
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.

1 participant