PDF Web Viewer
    Preparing search index...

    E-Sign

    The viewer hosts an E-Sign preparation layered on top of the open PDF. The preparation is a collection of index.ESignRoles, each owning placeholder index.ESignFields the signer will fill before the document is signed.

    The preparation is not persisted into the PDF until the signer actually signs — it lives on the document view until then. When the sign action runs, the viewer bakes the touched fields into a copy of the open document and ships that file to the signing API; the original document the user is looking at is left untouched.

    import type { ESignDocument, ESignRole, ESignField, FieldType, ESignMarkerRenderData } from '@avanquest/pdf-web-viewer';
    
    Type Purpose
    ESignDocument Root preparation object: a list of roles (+ optional active-marker customization).
    ESignRole One signer / participant. Carries name, color, and per-variant artwork.
    ESignField A single placeholder on a page (rect + FieldType + value + touched).
    FieldType Enum discriminating signature / filler / metadata variants.
    ESignFieldOptions Per-variant options (e.g. dropdown items, radio group key, max length).
    ESignMarkerRenderData Appearance of the active "next field" marker (fill / stroke / content + paddings).
    ESignFieldRenderData Appearance of an on-page field placeholder (fill / stroke / content + borderRadius).
    ESignMarkerContent The shared content: text + typography.

    Every index.ESignField carries a boolean touched flag. It distinguishes an empty placeholder (drawn as a styled outline) from a field the signer has interacted with (drawn with its filled value, baked into the signed PDF).

    • The renderer reads touched to pick the draw style.
    • The sign flow only emits widget / signature placements for touched fields.
    • Signing is blocked if any isRequired field is still untouched.

    There are three flow categories. The FieldType value determines which path runs at sign time.

    FieldType What it draws How it bakes into the signed PDF
    Signature role.signature Image is inserted into the page's content stream (permanent pixels, no editable annotation). Aspect ratio preserved, centered in the rect.
    Initials role.initials Same as Signature, using the role's initials artwork.
    DigitalSignature role.digitalSignature Flows through the sign API as a signature placement and produces a real digital signature in the output PDF.
    Certify role.certify Flows through the sign API as the certifying signature (only one allowed per document).

    Visible vs. cryptographic. Signature / Initials are visual placements only — the appearance is baked into the page. A DigitalSignature (or Certify) is required for the output to be actually digitally signed. If the package only contains visible Signature/Initials fields, the viewer auto-injects a near-invisible 1pt placeholder digital signature so the sign API still produces a signed PDF.

    Certify is a signature variant with two extra rules:

    • At most one Certify field per document. Adding a second one is rejected.
    • The output is a certified PDF — the resulting signature carries a permission policy (e.g. AllowFormFill) that constrains what downstream signers / readers can change without invalidating the certification.
    FieldType UI Baked as
    TextInput In-place text editor (optional multiline, max length) Flattened Text widget.
    DropDown Single-select dropdown Flattened Text widget with the chosen value.
    CheckBox Single checkbox Flattened CheckBox widget (ZapfDingbats ✓ glyph).
    RadioButton Mutually-exclusive radio group (key from options.radioGroup) Flattened RadioButton widget (ZapfDingbats ● glyph).
    Date In-place text editor — on first click pre-filled with today's date formatted by role.dateFormat; signer can edit at the caret position. Flattened Text widget with the formatted date.
    Name / Title / Company / Email In-place text editor — on first click pre-filled from the role's profile (role.name / role.title / role.companyName / role.email); signer can immediately edit at the caret position. Flattened Text widget.

    Each touched filler is created as a form widget on a copy of the document, has its value set on the parent acroform, and is then flattened into page content. The signed PDF contains the rendered value as page content — there's no editable form field a downstream signer could change.

    Untouched fields never appear in the signed PDF, regardless of FieldType. They're placeholders only.

    The index.IDocumentViewModel exposes three methods:

    const view = editor.ui.pdfWebService.getActiveDocumentViewElement()?.documentView;

    view.getESignDocument(): ESignDocument | null;
    view.setESignDocument(doc: ESignDocument | null, validate?: { security: boolean; fileType: boolean; maxSize?: number }): boolean;
    view.eSignDocumentChanged(): Observable<ESignDocument | null>;
    import type { ESignDocument } from '@avanquest/pdf-web-viewer';

    const view = editor.ui.pdfWebService.getActiveDocumentViewElement().documentView;

    /* Activate a fresh, empty preparation on the active view. The `validate`
    * gates run the host PDF's E-Sign eligibility checks BEFORE the preparation
    * is attached. Returns false (and shows a warning dialog) when the PDF is
    * encrypted / restricted, the file type is unsupported, or it exceeds
    * `maxSize` bytes. */
    const ok = view.setESignDocument({ roles: [] }, { security: true, fileType: true, maxSize: 50 * 1024 * 1024 });

    if (!ok) {
    /* validation rejected — preparation NOT started */
    return;
    }

    /* Tear the preparation down (e.g. after a successful sign, or on cancel).
    * Passing null skips validation. */
    view.setESignDocument(null);
    const sub = view.eSignDocumentChanged().subscribe((doc) => {
    if (!doc) {
    console.log('E-Sign preparation cleared');
    return;
    }
    const total = doc.roles.reduce((n, r) => n + r.fields.length, 0);
    const filled = doc.roles.reduce((n, r) => n + r.fields.filter((f) => f.touched).length, 0);
    console.log(`E-Sign progress: ${filled}/${total} fields touched`);
    });

    The observable emits whenever the preparation is replaced or cleared, and whenever the view model invalidates after an in-place mutation (a field is added / removed, a field's role changes, the signer types into a field, etc.) — so subscribers can treat each emission as "redraw if needed."

    Two optional hooks on the index.ESignDocument let you fully restyle what the viewer paints — both build on the same shared visual fields (fill? / stroke? / content?):

    Hook Styles Returns
    getMarkerRenderData The floating "next field" marker. index.ESignMarkerRenderData
    getFieldRenderData An on-page field placeholder. index.ESignFieldRenderData

    Both follow the same all-or-nothing contract: when set, the return value is used verbatim — the renderer never mixes in its own defaults; when absent, it draws its built-in default. There is no per-field fallback, so return every field you care about. The shared content object (text + typography) is itself all-or-nothing: include it in full, or omit it for no text.

    While the signer is in view mode, the viewer draws a floating marker — a colored flag pointing at the next field that still needs filling (the next required-but-untouched field, or, once all required fields are done, the next optional one). By default the flag is filled with the role color and shows the field-type glyph.

    import type { ESignMarkerRenderData } from '@avanquest/pdf-web-viewer';
    import { EFontStyle } from '@avanquest/pdf-web-viewer';

    const doc = view.getESignDocument();
    if (doc) {
    doc.getMarkerRenderData = (role, field): ESignMarkerRenderData => ({
    fill: role.color, // body color; omit for a transparent body
    stroke: '#ffffff', // optional outline; omit for none
    paddingX: 9.2, // horizontal padding around the text (sets / grows the flag length)
    paddingY: 7.2, // vertical padding above & below the text (sets the flag thickness)
    content: {
    // omit `content` entirely for a textless flag
    text: 'Sign here', // a single glyph for an icon, or any string
    color: '#ffffff',
    fontFamily: 'Poppins, Roboto, sans-serif',
    fontSize: 12, // view px
    fontStyle: EFontStyle.BOLD,
    },
    });
    view.notifyESignDocumentChanged(); // redraw with the new marker
    }

    The text drives the geometry, not the other way around:

    • Thickness (across the arrow) = content.fontSize + 2 * paddingY.
    • Length (along the arrow) = at least content.fontSize + 2 * paddingX, and grows to fit the measured text width plus paddingX on each end. A long content.text stretches the flag; it is never clipped.
    • For markers pointing up / down, the body is vertical, so the text is rotated 90° to run along that long axis (reading into the arrow direction).
    • With no content, the font term is 0: the flag collapses to just the paddings and draws empty — no glyph, no placeholder box.

    index.ESignMarkerRenderData:

    Field Required Meaning
    fill no Body fill color. Omit for a transparent body.
    stroke no Outline color. Omit for no stroke.
    paddingX yes Horizontal padding around the text (sets / grows flag length).
    paddingY yes Vertical padding above & below the text (sets flag thickness).
    content no Text + typography ( index.ESignMarkerContent). Omit for a textless flag.

    index.ESignMarkerContent (when present, every field is required):

    Field Meaning
    text Glyph or string drawn in the body.
    color Text color.
    fontFamily Font family for text.
    fontSize Font size in view px (also seeds the flag size).
    fontStyle EFontStyle (regular / bold / italic / bold-italic).

    An untouched field is drawn on the page as a placeholder (by default: a low-alpha role-color wash, the field-type icon, and a label). Set getFieldRenderData to replace that look per field. It only affects the placeholder — once a field is touched, its real value (signature image, typed text, checkbox state…) is drawn instead.

    import type { ESignFieldRenderData } from '@avanquest/pdf-web-viewer';
    import { EFontStyle } from '@avanquest/pdf-web-viewer';

    const doc = view.getESignDocument();
    if (doc) {
    doc.getFieldRenderData = (role, field): ESignFieldRenderData => ({
    fill: role.color, // box fill; omit for transparent
    fillOpacity: 0.1, // applied to `fill` only; omit for opaque
    stroke: role.color, // box outline; omit for none
    borderRadius: 6, // view px; omit for square corners
    showIcon: true, // draw the field-type glyph at the left edge
    content: {
    // omit `content` for an empty box
    text: 'Click to sign',
    color: role.color,
    fontFamily: 'Poppins, Roboto, sans-serif',
    fontSize: 11,
    fontStyle: EFontStyle.REGULAR,
    },
    });
    view.notifyESignDocumentChanged();
    }

    The default placeholder is itself an ESignFieldRenderData drawn through this same path — a low-opacity role-color wash, showIcon: true, and the field-type label as content.

    The label is fit to the field: it auto-shrinks so it stays inside the box, treating content.fontSize as the maximum size (it is never drawn larger, but may render smaller in a narrow field).

    index.ESignFieldRenderData:

    Field Required Meaning
    fill no Box fill color. Omit for a transparent box.
    fillOpacity no Opacity (0..1) applied to fill only. Omit (or 1) for opaque.
    stroke no Outline color. Omit for no stroke.
    borderRadius no Corner radius (view px) of the box. Omit for square corners.
    showIcon no Draw the field-type glyph at the left edge; content then sits to its right.
    content no Text + typography ( index.ESignMarkerContent). Omit for an empty box.

    An index.ESignField stores its position as left / top / width / height in the E-Sign coordinate space: page-local, origin at the page's top-left corner, Y growing down, at unit scale (independent of zoom / DPR). To place a field where the user clicked, convert the pointer position (view / device coordinates) into that space.

    Three coordinate systems are involved:

    Space Origin / axes Produced by
    View viewport top-left, device pixels the DOM event (pointer / click)
    Page PDF user space, bottom-left, Y up index.IDocumentViewModel.mapPointToPage
    E-Sign page-local top-left, Y down, unit index.IPageModel.getPageMatrix

    The conversion chain is view → page → E-Sign:

    import type { IDocumentViewModel, PointModel } from '@avanquest/pdf-web-viewer';

    /**
    * Convert a point in view (device/canvas) coordinates — e.g. a click — into the
    * coordinate space `ESignField.left` / `.top` are stored in.
    * Returns `null` when the point is not over any page.
    */
    function viewPointToESign(view: IDocumentViewModel, viewPoint: PointModel): { pageIndex: number; point: PointModel } | null {
    const pageIndex = view.getPageAtPoint(viewPoint);
    if (pageIndex < 0) return null; // click landed outside every page

    // view → PDF page space (bottom-left origin, Y up)
    const pagePoint = view.mapPointToPage(pageIndex, viewPoint);

    // page → E-Sign space (top-left origin, Y down, unit scale)
    const page = view.getDocument().getPage(pageIndex);
    if (!page) return null;
    const esignPoint = page.getPageMatrix().mapPoint(pagePoint);

    return { pageIndex, point: esignPoint };
    }

    Using it to drop a field at the click position:

    import { FieldType, type ESignField } from '@avanquest/pdf-web-viewer';

    const view = editor.ui.pdfWebService.getActiveDocumentViewElement().documentView;

    canvas.addEventListener('click', (e) => {
    const viewPoint = { x: e.offsetX, y: e.offsetY };
    const hit = viewPointToESign(view, viewPoint);
    if (!hit) return;

    const field: ESignField = {
    id: crypto.randomUUID(),
    parentId: null,
    fieldType: FieldType.Signature,
    value: null,
    touched: false,
    pageIndex: hit.pageIndex,
    left: hit.point.x, // already in E-Sign space
    top: hit.point.y,
    width: 220,
    height: 60,
    isRequired: true,
    };

    const doc = view.getESignDocument();
    doc?.roles[0]?.fields.push(field);
    view.setESignDocument(doc);
    });

    Reverse direction (E-Sign → view). To position your own DOM overlay over a stored field, invert the chain: page.getPageMatrix().invert() maps the field's left / top back to page space, then index.IDocumentViewModel.mapPointToDevice maps page space to view space. The matrix already accounts for the page's rotation.

    import { PdfEditor, FieldType, type ESignDocument, type ESignField, type ESignRole } from '@avanquest/pdf-web-viewer';

    const editor = await PdfEditor({
    license: 'YOUR_LICENSE_KEY',
    basePath: '/assets',
    container: document.getElementById('viewer'),
    });

    await editor.ui.pdfWebService.openDocument(myPdfFile);

    const view = editor.ui.pdfWebService.getActiveDocumentViewElement().documentView;

    /* 1. Start an empty preparation, gated by eligibility checks. */
    view.setESignDocument({ roles: [] }, { security: true, fileType: true, maxSize: 50 * 1024 * 1024 });

    /* 2. Build a role and a few fields. (In the UI the toolbar drives this — this
    * snippet shows the shape of the data model.) */
    const role: ESignRole = {
    id: 'signer-1',
    color: '#1E88E5',
    name: 'Jane Doe',
    email: '[email protected]',
    dateFormat: 'dd/MM/yyyy',
    fields: [],
    };

    const signatureField: ESignField = {
    id: 'f-sig-1',
    parentId: null,
    fieldType: FieldType.Signature,
    value: null,
    touched: false,
    pageIndex: 0,
    left: 72,
    top: 600,
    width: 220,
    height: 60,
    isRequired: true,
    };

    const nameField: ESignField = {
    id: 'f-name-1',
    parentId: null,
    fieldType: FieldType.Name,
    value: null,
    touched: false,
    pageIndex: 0,
    left: 72,
    top: 540,
    width: 220,
    height: 24,
    isRequired: false,
    };

    role.fields.push(signatureField, nameField);

    const doc: ESignDocument = { roles: [role] };
    view.setESignDocument(doc);

    /* 3. Signer interacts in the UI — the renderer flips `touched` on each
    * interacted field and stores the entered value. You can also script it:
    * importing setESignFieldValue() from the package is not part of the
    * public API, so flip the flag yourself if you need to. */
    nameField.value = 'Jane Doe';
    nameField.touched = true;

    /* 4. Reading the result before signing: who's done, who's not. */
    const missingRequired = doc.roles.flatMap((r) => r.fields).filter((f) => f.isRequired && !f.touched);
    if (missingRequired.length > 0) {
    console.warn(`${missingRequired.length} required field(s) still untouched`);
    }

    /* 5. The Sign action is wired to the toolbar — it runs the sign API on a
    * copy of the document, bakes touched widgets / signatures in, and then
    * calls view.setESignDocument(null) on success. */

    When the signer triggers the Sign action in the E-Sign toolbar:

    1. The viewer checks all isRequired fields are touched. If not, signing is blocked with a warning.
    2. The original open document is left untouched. A throwaway signing copy is opened from its bytes.
    3. Touched filler fields are baked into the signing copy: each becomes a form widget, gets its acroform value set, then is flattened into page content.
    4. Touched visual signature fields (Signature, Initials) are inserted into the signing copy's page content stream as images (aspect-preserved, centered in the field rect).
    5. Touched DigitalSignature and the optional Certify field are collected into signing-API payloads.
    6. The signing copy is sent through the sign API together with those payloads and returns a signed PDF.
    7. On success the preparation is cleared from the view (view.setESignDocument(null)) and the signed PDF is opened in the same pane the user signed from.

    The output PDF therefore contains:

    • Cryptographic signatures for every DigitalSignature / Certify field,
    • Baked-in pixel appearances for every touched Signature / Initials field,
    • Flattened form widgets carrying the values of every touched filler,
    • Nothing for untouched placeholders — they vanish.

    Callbacks on ISetupOptions let you swap out the library's built-in E-Sign dialogs and signing pipeline without forking the toolbar:

    Callback What it overrides When it fires
    onESignFieldFillRequested The built-in signature-capture modal. First click on an untouched Signature / Initials / Certify / DigitalSignature field.
    onESignFieldEditRequested (No default) lets you open a custom field-editing UI. Double-click on an already-placed field while in edit mode.
    onESignSignRequested The built-in sign API pipeline behind the toolbar's Sign button. After required-field validation passes, before the toolbar would normally call the bundled signing service.

    The fallback is always the library's default — if you don't wire a callback, nothing changes. If a callback throws or is misconfigured, the library logs and falls back so signing/filling never silently no-ops.

    Mirrors the same pattern as the Fill & Sign tab's onAddSignature. The library passes you the field the signer interacted with and the role that owns it. You show your own UI, mutate the field's value / touched (and, for signature variants, the matching role.signature / role.initials / role.certify / role.digitalSignature slot), and resolve with true. Resolving with false lets the library fall back to its default dialog.

    import { PdfEditor, FieldType, type ESignField, type ESignRole } from '@avanquest/pdf-web-viewer';

    await PdfEditor({
    license: 'YOUR_LICENSE_KEY',
    basePath: '/assets',
    container: document.getElementById('viewer'),

    onESignFieldFillRequested: async (field: ESignField, role: ESignRole) => {
    /* Signature variants — capture or look up an HTMLImageElement and store
    * it on the role; flip `touched` so the renderer paints the image. */
    if (
    field.fieldType === FieldType.Signature ||
    field.fieldType === FieldType.Initials ||
    field.fieldType === FieldType.Certify ||
    field.fieldType === FieldType.DigitalSignature
    ) {
    const image = await myCustomSignaturePicker.open({ role });
    if (!image) return false; // user cancelled → use library default
    if (field.fieldType === FieldType.Initials) role.initials = image;
    else if (field.fieldType === FieldType.Certify) role.certify = image;
    else if (field.fieldType === FieldType.DigitalSignature) role.digitalSignature = image;
    else role.signature = image;
    field.value = true;
    field.touched = true;
    return true; // library skips its default dialog
    }

    /* Anything else — let the library handle it (CheckBox / RadioButton
    * toggles; TextInput / DropDown / Name / Company / Title / Email / Date
    * in-place editors). */
    return false;
    },
    });

    Not invoked for: CheckBox, RadioButton (click-toggles), TextInput, DropDown, Name, Company, Title, Email, Date (in-place text editors — the last five pre-fill from the role profile or today's date and then behave like any other text field so the signer can immediately edit). Those never open a modal in the built-in flow, so there's no dialog to override.

    Fires when the author double-clicks an already-placed field while the editor is in edit mode — the counterpart to onESignFieldFillRequested (the signer's first-fill hook). Use it to open your own editing UI for the field: change its value, options, or role. It fires for any field type and has no library default, so a missing callback (or one that returns false / throws) is simply a no-op. Mutate the field directly and resolve with true; the library then redraws.

    import { PdfEditor, type ESignField, type ESignRole } from '@avanquest/pdf-web-viewer';

    await PdfEditor({
    license: 'YOUR_LICENSE_KEY',
    basePath: '/assets',
    container: document.getElementById('viewer'),

    onESignFieldEditRequested: async (field: ESignField, role: ESignRole) => {
    const changes = await myFieldEditor.open({ field, role });
    if (!changes) return false; // user cancelled → no-op
    Object.assign(field, changes); // e.g. update value / options / reason
    return true; // library redraws
    },
    });

    Replaces the entire sign pipeline. The library hands you the active ESignDocument (with every touched field, role artwork, etc.) and a progress reporter you can use to update the Sign button label. Resolve with a signed File and the library opens it in the same pane the user signed from and clears the preparation (view.setESignDocument(null)). Resolve with null to cancel without error.

    import { PdfEditor, FieldType, type ESignDocument } from '@avanquest/pdf-web-viewer';

    await PdfEditor({
    license: 'YOUR_LICENSE_KEY',
    basePath: '/assets',
    container: document.getElementById('viewer'),

    onESignSignRequested: async (doc: ESignDocument, onProgress) => {
    onProgress?.(5);

    /* Encode the preparation into whatever payload your backend wants.
    * Only touched fields are signal — untouched ones are placeholders. */
    const placements = doc.roles.flatMap((role) =>
    role.fields
    .filter((f) => f.touched)
    .map((f) => ({
    roleId: role.id,
    fieldType: f.fieldType,
    pageIndex: f.pageIndex,
    rect: { left: f.left, top: f.top, width: f.width, height: f.height },
    value: f.value,
    signatureImageSrc:
    f.fieldType === FieldType.Signature
    ? role.signature?.src
    : f.fieldType === FieldType.Initials
    ? role.initials?.src
    : f.fieldType === FieldType.Certify
    ? role.certify?.src
    : f.fieldType === FieldType.DigitalSignature
    ? role.digitalSignature?.src
    : undefined,
    })),
    );

    onProgress?.(20);
    const signed = await myCustomSignClient.send({ placements }); // your backend
    onProgress?.(100);

    return new File([signed], 'signed.pdf', { type: 'application/pdf' });
    /* Library: opens the file in the same pane + view.setESignDocument(null). */
    },
    });

    Pre-flight validation still runs. The toolbar enforces isRequired + touched and blocks the sign action with a warning before onESignSignRequested is ever called — so when your callback fires the preparation is already valid.

    You don't have to use the built-in Sign action. The index.IDocumentViewModel pair getESignDocument() / setESignDocument() (plus eSignDocumentChanged()) is sufficient on its own to:

    • render the E-Sign preparation overlay on top of the open PDF (drag-to-place fields, role colors, required-field outlines, value editors, etc.),
    • collect the signer's input (each interacted field flips its own touched flag and stores its value),
    • read out the resulting ESignDocument at any time and hand it to your own sign service — REST, gRPC, DocuSign, Adobe Sign, an in-house HSM workflow, anything.

    The library never forces the built-in sign API path. Hide the toolbar's Sign button (or just ignore it), wire your own button to your own service, and use the viewer purely as a renderer + input collector.

    import { PdfEditor, FieldType, type ESignDocument, type ESignField } from '@avanquest/pdf-web-viewer';

    const editor = await PdfEditor({
    license: 'YOUR_LICENSE_KEY',
    basePath: '/assets',
    container: document.getElementById('viewer'),
    });

    await editor.ui.pdfWebService.openDocument(myPdfFile);
    const view = editor.ui.pdfWebService.getActiveDocumentViewElement().documentView;

    /* 1. Render: hand the viewer a prepared ESignDocument (or start empty and let
    * the user place fields via the toolbar). */
    view.setESignDocument(
    {
    roles: [
    /* ... */
    ],
    },
    { security: true, fileType: true },
    );

    /* 2. Collect: subscribe to react to user edits (enable/disable your own
    * "Send for signing" button, show progress, etc.). */
    view.eSignDocumentChanged().subscribe((doc) => {
    if (!doc) return;
    const allRequiredFilled = doc.roles.flatMap((r) => r.fields).every((f) => !f.isRequired || f.touched);
    myExternalSignButton.disabled = !allRequiredFilled;
    });

    /* 3. Read & send: when your own button is clicked, read the current state
    * and post it to YOUR backend. Nothing else is required. */
    async function sendToMySignService() {
    const doc = view.getESignDocument();
    if (!doc) return;

    /* The original PDF the user is looking at — untouched by the viewer. */
    const pdfBytes = await editor.ui.pdfWebService.getActiveDocumentViewElement().exportFile('uint8array');

    /* Project the ESignDocument into whatever payload your service wants.
    * Untouched placeholders are easy to drop here. */
    const payload = {
    pdf: pdfBytes,
    placements: doc.roles.flatMap((role) =>
    role.fields
    .filter((f) => f.touched)
    .map((f) => ({
    roleId: role.id,
    fieldType: f.fieldType,
    pageIndex: f.pageIndex,
    rect: { left: f.left, top: f.top, width: f.width, height: f.height },
    value: f.value,
    signatureImageSrc:
    f.fieldType === FieldType.Signature
    ? role.signature?.src
    : f.fieldType === FieldType.Initials
    ? role.initials?.src
    : f.fieldType === FieldType.Certify
    ? role.certify?.src
    : f.fieldType === FieldType.DigitalSignature
    ? role.digitalSignature?.src
    : undefined,
    })),
    ),
    };

    const signedPdf = await myCustomSignClient.send(payload);

    /* 4. Done: clear the preparation and (optionally) open the signed file
    * your service returned. The viewer was never coupled to the signer. */
    view.setESignDocument(null);
    await editor.ui.pdfWebService.openDocument(new File([signedPdf], 'signed.pdf', { type: 'application/pdf' }));
    }
    Concern Library provides You implement
    Rendering placeholder outlines on the PDF
    Drag-to-place / resize / move field rects
    Role colors, name / email / company profile fields
    Per-field editors (text, dropdown, checkbox, radio, date)
    touched lifecycle (placeholder vs. filled)
    Required-field validation against isRequired
    Eligibility gates on the host PDF (security/file-type/size)
    Reading the in-progress ESignDocument
    Encoding ESignDocument to YOUR service's request schema
    Talking to YOUR signing backend / DocuSign / Adobe Sign
    Producing the signed PDF / certifying / CMS / PKCS#7
    Opening the signed PDF back in the viewer ✅ (openDocument)

    In other words: the built-in Sign action (described in What happens at sign time) is one consumer of this API. You can ignore it entirely and treat the viewer as a UI-only layer that hands you a fully-typed ESignDocument to feed into your own signing pipeline.