Signatures
A digital signature has two parts: the mark, what people see in the signature field, such as a drawn signature; and the key, which seals the document so any later change shows. The signature plugin signs with both, checks the signatures a document already has, and warns before an edit would break one.
Register withSignature() next to withForm() and withStamp(), with the key to sign with:
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
output,
signal,
untracked,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput, PageRef } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfForm, EpdfFormLayer, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { createTestSigner, EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { localEngine } from '@embedpdf/engine';
const engine = localEngine();
const assetEngine = engine; // a person's marks are stamps, kept as PDFs; they open here too
// A throwaway key for the demo. Bring your own with `webCryptoSigner`, a
// service with `remoteSigner`, or a person's own with `personalSigner`.
const signer = createTestSigner({ commonName: 'Ada Lovelace' });
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// A mark's button: its picture once there is one, its label until then.
@Component({
selector: 'button[demoMarkButton]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { type: 'button', class: 'button', '[title]': 'label()', '(click)': 'pick.emit()' },
template: `
@if (url(); as url) {
<img [src]="url" [alt]="label()" class="preview" />
} @else {
{{ label() }}
}
`,
})
export class MarkButton {
readonly assetId = input.required<string>();
readonly label = input.required<string>();
readonly pick = output();
protected readonly url = inject(EpdfStamp).previewUrlOf(() => this.assetId());
}
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfFormLayer,
MarkButton,
],
providers: [
provideEmbedPdf(
{ engine, initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withForm(),
withStamp({ assetEngine }),
withSignature({
key: () => signer,
// Trust the demo key itself, so its signatures check out as 'valid'.
trust: { anchors: async () => [(await signer).certificate] },
}),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './basic.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<div class="toolbar">
@for (asset of person()?.signatures; track asset.id) {
<button demoMarkButton [assetId]="asset.id" [label]="asset.label" (pick)="sign(asset.id)"></button>
}
<output class="readout">{{ status() }}</output>
</div>
<epdf-stage class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<!-- An empty signature field is "sign here": a click makes it the target -->
<epdf-form-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
private readonly form = inject(EpdfForm);
private readonly stamp = inject(EpdfStamp);
private readonly signature = inject(EpdfSignature);
private readonly document = inject(EpdfDocument);
private readonly stage = viewChild(EpdfStage);
private done = false;
protected readonly person = computed(() => this.signature.signerRows()[0]);
private readonly field = computed(() =>
this.form.fields().find((candidate) => candidate.family === 'signature'),
);
private readonly error = signal<string | null>(null);
protected readonly status = computed(() => {
const signed = this.signature.signatures()[0];
const error = this.error();
if (!this.person() || !this.field()) return 'Getting ready…';
if (error) return error;
if (this.signature.busy()) return 'Signing…';
if (signed) return `Signed by ${signed.signer.name}: ${signed.verdict?.summary ?? 'checking…'}`;
if (this.signature.target()) return 'Now pick the signature';
return 'Click the field, or pick the signature';
});
constructor() {
// The ebook has no signature field, and the browser holds no signatures yet: add a field
// to the last page, and one person with a typed signature.
effect(() => {
const page = this.document.pages().at(-1)?.ref;
const stage = this.stage();
if (this.form.status() !== 'ready' || !page || !stage || this.done) return;
this.done = true;
untracked(() => void this.setUp(page, stage));
});
}
/** The target is the field someone clicked; without one, the form's signature field. */
protected sign(assetId: string) {
const destination = this.signature.target() ?? this.field()?.ref;
if (!destination) return;
this.error.set(null);
this.signature
.placeMark({ assetId }, { field: destination })
.catch((reason: Error) => this.error.set(reason.message));
}
private async setUp(page: PageRef, stage: EpdfStage) {
await this.form.create({
family: 'signature',
name: 'approval',
widgets: [{ page, rect: { x: 72, y: 560, width: 220, height: 64 }, color: '#94a3b8' }],
});
stage.goToPage(page);
const { library } = await this.stamp.createLibrary('Ada Lovelace', { kind: 'signatures' });
await this.stamp.createAsset({
libraryId: library.id,
name: 'signature',
label: 'Signature',
mark: { kind: 'text', text: 'Ada Lovelace', fontFamily: 'times-italic', color: '#1d2b53' },
});
}
}import { Component } from '@angular/core';
import { withForm } from '@embedpdf/angular/form';
import { provideEmbedPdf } from '@embedpdf/angular/runtime';
import { indexedDbKeyStore, personalSigner, withSignature } from '@embedpdf/angular/signature';
import { withStamp } from '@embedpdf/angular/stamp';
import { rootCertificate } from './certificates';
import { engine } from './pdf';
@Component({
selector: 'app-signing',
providers: [
provideEmbedPdf(
{ engine },
/* … */
withForm(),
withStamp(),
withSignature({
key: () => personalSigner({ subject: 'Ada Lovelace', store: indexedDbKeyStore('my-app-keys') }),
trust: { anchors: async () => [rootCertificate] }, // the certificates you trust
}),
),
],
template: `<!-- … -->`,
})
export class Signing {}Sign a field#
const { version } = await signature.sign({
field: toFieldRef('approval'),
mark: { assetId },
signer: { reason: 'Approved', location: 'Amsterdam' },
});The mark becomes what the field shows, the key seals the document, and the signed document is saved as a new version. If anything fails along the way, nothing is left half-signed.
A mark is a stamp from a person’s signature library, or image bytes you bring:
{ assetId } // a stamp, drawn, typed or uploaded; see "A person's signatures" below
{ source: pngBytes } // a PNG, a JPEG, or a one-page PDFLet people click to sign#
People can also arm their signature and click where it goes, as in Preview. With the stamp plugin, arm a mark from a signature library:
- over an empty signature field, the click signs it;
- over a signed field, nothing happens;
- anywhere else, the mark is placed as a stamp.
placeMark() does the same from code:
await signature.placeMark({ assetId }, { field: toFieldRef('approval') }); // signs the field
await signature.placeMark({ assetId }, { page: page.ref, center: { x: 120, y: 90 } }); // a stampWhat happens in a field depends on mode: 'sign' signs it, 'visual' only draws the mark in,
and 'ask' asks your UI first through signRequested$, so you can show your own sign dialog.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
untracked,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput, PageRef } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfForm, EpdfFormLayer, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { createTestSigner, EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { localEngine } from '@embedpdf/engine';
const engine = localEngine();
const assetEngine = engine; // a person's marks are stamps, kept as PDFs; they open here too
const signer = createTestSigner({ commonName: 'Ada Lovelace' });
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfAnnotationLayer,
EpdfFormLayer,
],
providers: [
provideEmbedPdf(
{ engine, initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withAnnotation(),
withForm(),
withStamp({ assetEngine }),
withSignature({ key: () => signer }),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './click-to-sign.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<div class="toolbar">
<button
type="button"
class="button"
[disabled]="!mark()"
[attr.aria-pressed]="stamp.armedAsset() !== null"
(click)="toggleArmed()"
>
{{ stamp.armedAsset() ? 'Disarm' : 'Arm the signature' }}
</button>
<output class="readout">{{ hint() }}</output>
</div>
<epdf-stage class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-annotation-layer />
<epdf-form-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
protected readonly stamp = inject(EpdfStamp);
private readonly form = inject(EpdfForm);
private readonly signature = inject(EpdfSignature);
private readonly document = inject(EpdfDocument);
private readonly stage = viewChild(EpdfStage);
private done = false;
protected readonly mark = computed(() => this.signature.signerRows()[0]?.signatures[0]);
protected readonly hint = computed(() => {
const signed = this.signature.signatures()[0];
if (signed) return `The field is signed by ${signed.signer.name}`;
if (this.stamp.armedAsset()) return 'Click the empty field to sign it, or anywhere else to place it';
if (this.mark()) return 'Arm the signature, then click where it goes';
return 'Getting ready…';
});
constructor() {
// On load: a signature field on the last page, and a person's signature, armed.
effect(() => {
const page = this.document.pages().at(-1)?.ref;
const stage = this.stage();
if (this.form.status() !== 'ready' || !page || !stage || this.done) return;
this.done = true;
untracked(() => void this.setUp(page, stage));
});
}
protected toggleArmed() {
const mark = this.mark();
if (this.stamp.armedAsset()) this.stamp.disarm();
else if (mark) void this.stamp.armAsset(mark.id);
}
private async setUp(page: PageRef, stage: EpdfStage) {
await this.form.create({
family: 'signature',
name: 'approval',
widgets: [{ page, rect: { x: 72, y: 560, width: 220, height: 64 }, color: '#94a3b8' }],
});
stage.goToPage(page);
const { library } = await this.stamp.createLibrary('Ada Lovelace', { kind: 'signatures' });
const { asset } = await this.stamp.createAsset({
libraryId: library.id,
name: 'signature',
label: 'Signature',
mark: { kind: 'text', text: 'Ada Lovelace', fontFamily: 'times-italic', color: '#1d2b53' },
});
await this.stamp.armAsset(asset.id, { targetWidth: 160 });
}
}Fill a field without signing#
Sometimes a signature only needs to look signed, such as for a form that gets printed. Draw the mark into the field without sealing anything:
await signature.fillField(toFieldRef('approval'), { source: pngBytes });
await signature.clearField(toFieldRef('approval'));A signed field can’t be filled or cleared: what it shows is part of what was signed.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
signal,
untracked,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput, PageRef } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfForm, toFieldRef, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { localEngine } from '@embedpdf/engine';
const engine = localEngine();
const assetEngine = engine; // a person's marks are stamps, kept as PDFs; they open here too
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
const APPROVAL = toFieldRef('approval');
@Component({
selector: 'demo-root',
imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer],
providers: [
provideEmbedPdf(
{ engine, initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withForm(),
withStamp({ assetEngine }),
// No key: a mark is only drawn in, nothing is sealed.
withSignature(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './fill.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<div class="toolbar">
<button type="button" class="button" [disabled]="!assetId() || drawn()" (click)="drawIn()">
Draw it in
</button>
<button type="button" class="button" [disabled]="!drawn()" (click)="takeOut()">
Take it out
</button>
<output class="readout">
{{ drawn() ? 'Drawn in, not signed: no key sealed anything' : 'The field is empty' }}
</output>
</div>
<epdf-stage class="stage">
<ng-template epdfPage>
<epdf-render-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
private readonly form = inject(EpdfForm);
private readonly stamp = inject(EpdfStamp);
private readonly signature = inject(EpdfSignature);
private readonly document = inject(EpdfDocument);
private readonly stage = viewChild(EpdfStage);
private done = false;
protected readonly assetId = signal<string | null>(null);
protected readonly drawn = signal(false);
constructor() {
this.signature.filled$.pipe(takeUntilDestroyed()).subscribe(() => this.drawn.set(true));
this.signature.cleared$.pipe(takeUntilDestroyed()).subscribe(() => this.drawn.set(false));
// On load: a signature field on the last page, a typed signature, drawn into the field.
effect(() => {
const page = this.document.pages().at(-1)?.ref;
const stage = this.stage();
if (this.form.status() !== 'ready' || !page || !stage || this.done) return;
this.done = true;
untracked(() => void this.setUp(page, stage));
});
}
protected drawIn() {
const assetId = this.assetId();
if (assetId) void this.signature.fillField(APPROVAL, { assetId });
}
protected takeOut() {
void this.signature.clearField(APPROVAL);
}
private async setUp(page: PageRef, stage: EpdfStage) {
await this.form.create({
family: 'signature',
name: 'approval',
widgets: [{ page, rect: { x: 72, y: 560, width: 220, height: 64 }, color: '#94a3b8' }],
});
stage.goToPage(page);
const { library } = await this.stamp.createLibrary('Ada Lovelace', { kind: 'signatures' });
const { asset } = await this.stamp.createAsset({
libraryId: library.id,
name: 'signature',
label: 'Signature',
mark: { kind: 'text', text: 'Ada Lovelace', fontFamily: 'times-italic', color: '#1d2b53' },
});
this.assetId.set(asset.id);
await this.signature.fillField(APPROVAL, { assetId: asset.id });
}
}The key#
The engine never holds a key. You give the plugin a signer, which does the signing.
@embedpdf/angular/signature has one for each case:
// Your own key and certificate chain
webCryptoSigner({ privateKey, certificateChain: [leaf, issuer] });
// Your signing service or hardware module: only the document's fingerprint leaves the browser
remoteSigner({ sign: ({ digest, algorithm, subFilter }) => api.sign(digest, algorithm, subFilter) });
// A key per person, created in the browser and kept there, never readable
personalSigner({ subject: 'Ada Lovelace', store: indexedDbKeyStore('my-app-keys') });
// For tests and demos
createTestSigner({ commonName: 'Demo' });A personal signer works like signing in Preview, with real cryptography: a reader who trusts that
person’s certificate sees a valid signature, and everyone else sees “validity unknown”, the honest
answer for a key the person made themselves. For signatures everyone can check, sign with a
certificate from a trusted provider, through webCryptoSigner or remoteSigner.
Signing in two steps#
When the key signs somewhere else, such as on a phone or in a signing service that asks for approval, split the signing in two:
const { signingId, digest } = await signature.prepareSignature({ field, mark: { assetId } });
const cms = await myService.sign(digest); // may take minutes
await signature.completeSignature(signingId, cms);While a signature is prepared, the document can’t be changed, so nothing can slip in between.
signature.cancelPending() gives up.
Check the signatures#
import { Component, inject } from '@angular/core';
import { EpdfSignature } from '@embedpdf/angular/signature';
@Component({
selector: 'app-signature-list',
template: `
<ul>
@for (sig of signature.signatures(); track sig.fieldName) {
<li>{{ sig.fieldName }}: {{ sig.verdict?.summary ?? 'checking…' }}</li>
}
</ul>
`,
})
export class SignatureList {
protected readonly signature = inject(EpdfSignature);
}A verdict’s summary is:
| Summary | Means |
|---|---|
'valid' | Nothing changed since it was signed, and you trust the signer |
'valid-untrusted' | Nothing changed, but the signer’s certificate isn’t one you trust |
'invalid' | The document changed in a way the signature doesn’t allow |
'indeterminate' | It couldn’t be checked, for example because a certificate is missing |
The plugin checks the signatures when the document opens, and again after every change. For a
detailed report of what changed since a signature, rule by rule, use signature.analyzeChanges().
Warn before an edit breaks a signature#
After an approval signature, filling in the form and adding more signatures keep it valid, but an annotation doesn’t. The plugin warns you the moment a change would break a signature once it’s saved, as Acrobat does:
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfSignature } from '@embedpdf/angular/signature';
import { toast } from './toast';
@Component({ selector: 'app-signature-guard', template: '' })
export class SignatureGuard {
private readonly signature = inject(EpdfSignature);
constructor() {
this.signature.invalidationPredicted$.pipe(takeUntilDestroyed()).subscribe(({ field }) => {
const name = this.signature.getSignature(field)?.fieldName;
toast(`This change will break the signature in "${name}" when saved.`);
});
}
}signature.getProtection() tells you what the document’s signatures allow, before anyone tries. A
signer who wants comments to stay allowed signs with a certification that allows them.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
signal,
untracked,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput, PageRef } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import {
EpdfAnnotation,
EpdfAnnotationLayer,
withAnnotation,
} from '@embedpdf/angular/annotation';
import { EpdfForm, toFieldRef, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { createTestSigner, EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { localEngine } from '@embedpdf/engine';
const engine = localEngine();
const assetEngine = engine; // a person's marks are stamps, kept as PDFs; they open here too
const signer = createTestSigner({ commonName: 'Ada Lovelace' });
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
@Component({
selector: 'demo-root',
imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, EpdfAnnotationLayer],
providers: [
provideEmbedPdf(
{ engine, initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withAnnotation(),
withForm(),
withStamp({ assetEngine }),
withSignature({
key: () => signer,
// Trust the demo key itself, so its signature checks out as 'valid'.
trust: { anchors: async () => [(await signer).certificate] },
}),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './break-warning.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<div class="toolbar">
<button type="button" class="button" [disabled]="!signed() || !page()" (click)="drawBox()">
Draw a box on the page
</button>
<output class="readout">{{ status() }}</output>
@if (warning(); as warning) {
<output class="warning">{{ warning }}</output>
}
</div>
<epdf-stage class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
private readonly form = inject(EpdfForm);
private readonly stamp = inject(EpdfStamp);
private readonly annotation = inject(EpdfAnnotation);
private readonly signature = inject(EpdfSignature);
private readonly document = inject(EpdfDocument);
private readonly stage = viewChild(EpdfStage);
private done = false;
/** The last page, which has room for the field. */
protected readonly page = computed(() => this.document.pages().at(-1)?.ref);
protected readonly signed = computed(() => this.signature.signatures()[0]);
protected readonly status = computed(() => {
const signed = this.signed();
return signed
? `Signed by ${signed.signer.name}: ${signed.verdict?.summary ?? 'checking…'}`
: 'Signing…';
});
protected readonly warning = signal<string | null>(null);
constructor() {
// The moment a change would break a signature once it's saved, as Acrobat warns.
this.signature.invalidationPredicted$.pipe(takeUntilDestroyed()).subscribe(({ field }) => {
const name = this.signature.getSignature(field)?.fieldName;
this.warning.set(`This change will break the signature in "${name}" when saved.`);
});
// On load: a signature field on the last page, signed by Ada with a typed signature.
effect(() => {
const page = this.page();
const stage = this.stage();
if (this.form.status() !== 'ready' || !page || !stage || this.done) return;
this.done = true;
untracked(() => void this.signDocument(page, stage));
});
}
protected drawBox() {
const page = this.page();
if (!page) return;
void this.annotation.create(page, {
subtype: 'square',
box: { x: 320, y: 560, width: 140, height: 64 },
color: '#e11d48',
strokeWidth: 2,
});
}
private async signDocument(page: PageRef, stage: EpdfStage) {
await this.form.create({
family: 'signature',
name: 'approval',
widgets: [{ page, rect: { x: 72, y: 560, width: 220, height: 64 } }],
});
stage.goToPage(page);
const { library } = await this.stamp.createLibrary('Ada Lovelace', { kind: 'signatures' });
const { asset } = await this.stamp.createAsset({
libraryId: library.id,
name: 'signature',
label: 'Signature',
mark: { kind: 'text', text: 'Ada Lovelace', fontFamily: 'times-italic', color: '#1d2b53' },
});
await this.signature.sign({ field: toFieldRef('approval'), mark: { assetId: asset.id } });
}
}A person’s signatures#
A person’s signature and initials are stamps, in a library of kind 'signatures', drawn, typed or
uploaded with the stamp plugin:
const { library } = await stamp.createLibrary('Ada Lovelace', { kind: 'signatures' });
await stamp.createAsset({ libraryId: library.id, name: 'signature', label: 'Signature', mark: { kind: 'ink', strokes } });
await stamp.createAsset({
libraryId: library.id,
name: 'initials',
label: 'Initials',
mark: { kind: 'text', text: 'AL', fontFamily: 'times-italic' },
});signature.signerRows() lists each person with their signature and initials, for a picker. The library is
a normal PDF, so Acrobat can open it too.
”Sign here” in a form#
With the form layer, an empty signature field shows a “sign here” button. Clicking it makes the field the target: the next mark the person picks goes there. A signed field, clicked, asks your UI to show its details:
signature.setTarget(toFieldRef('approval')); // the next mark goes here; null clears| Event | Fires when | Show |
|---|---|---|
targetChanged$ | A field became the target | your signatures panel |
signRequested$ | In 'ask' mode, a mark was dropped on a field | your sign dialog |
inspectionRequested$ | A signed field was clicked | the signature’s details |
Methods#
Everything inject(EpdfSignature) gives you.
| Method | What it does |
|---|---|
sign({ field, mark }) | Signs a field: the mark becomes what it shows, and the key seals the document. Also takes signer, a per-call key, certify and lock, and resolves { signature, version, protection }. Sign a field |
placeMark(mark, { field }), placeMark(mark, { page, center }) | Does what a click with an armed mark does: in a field, signs, fills or asks, by mode; anywhere else, places a stamp. Resolves what it did as kind: 'signed', 'filled', 'requested' or 'placed'. Let people click to sign |
fillField(field, mark), clearField(field) | Draws a mark into a field without signing, or takes it out again. Refused on a signed field. Fill a field without signing |
prepareSignature({ field, mark }), completeSignature(signingId, cms) | Signs in two steps: the first resolves { signingId, digest } for your key to sign, the second seals the document with what the key returned. Signing in two steps |
cancelPending() | Gives up a signature prepared in two steps, so the document can be changed again |
validate({ until? }) | Checks every signature again and resolves the verdicts. until: 'persisted' checks the file as it was opened. Which bytes are checked |
validateField(field) | Checks one signed field and resolves its verdict |
getVerdict(field), listVerdicts() | One signed field’s verdict from the last check, or every verdict; null before the first check. Check the signatures |
getSignature(field) | One signature field’s facts, signed or not, such as fieldName, signed and signer, or null |
listUnsignedFields() | The signature fields still to sign, in document order |
analyzeChanges({ since }) | What changed after a signature ({ signatureIndex }) or a version of the file ({ revisionIndex }), rule by rule |
readRevision(field), readRevision({ revisionIndex }) | The exact bytes of the file version a signature covers. The signed bytes |
setTarget(field) | Makes a field the target, where the next mark the person picks goes; null clears. “Sign here” in a form |
requestInspection(field) | Asks your UI to show a signed field’s details, as clicking it does: inspectionRequested$ fires |
getMode() | The mode in effect: the mode setting, or its default. Settings |
getSnapshot() | Everything at once, as the engine reads it: every signature field, signed or not, the file’s versions, and protection |
refresh() | Reads the signatures again. You rarely need it: the plugin reads and checks them again after every change. |
canSign(field), canCertify(), canFill(field), canReadRevision() | Whether signing a field, certifying, drawing a mark into a field without signing, and reading the signed bytes are allowed. Permissions |
getSettings(), updateSettings(changes), resetSettings() | Reads the settings, changes them while the app runs, or goes back to what you registered. In a component, use inject(EpdfSignature).settings(). Settings |
field is a field’s ref, such as toFieldRef('approval'); the methods that only read also take the
ref of the field’s box. Every method that returns a promise also takes a signal in its options, to
cancel it.
State#
Every value is a signal on inject(EpdfSignature): read it in a template or a computed(), and your view updates when it changes. The method in the last column reads the same value once.
| Field | What it is | Read once |
|---|---|---|
signatures() | The signed fields, in document order, each with its verdict once checked | listSignatures() |
protection() | What the document’s signatures allow, or null before they’re read | getProtection() |
target() | The field the next mark goes to, or null | getTarget() |
busy() | Whether a signing or a fill is running | isBusy() |
pending() | A signature prepared in two steps and not completed yet, as { signingId, field }, or null. Until it’s completed or cancelled, the document can’t be changed. | getPending() |
status() | Whether the signatures are read: 'idle', 'loading', 'ready', 'forbidden' or 'error' | getStatus() |
Settings#
Set them when you register the plugin, and change them while the app runs with
signature.updateSettings({ … }); signature.resetSettings() goes back to what you registered, and
inject(EpdfSignature).settings() gives you the current settings.
| Setting | What it does | Default |
|---|---|---|
key | The signer, or a function that returns one when someone signs | none |
mode | What a mark does in a field: 'sign', 'visual' or 'ask' | 'sign' with a key, else 'visual' |
trust | The certificates you trust, for checking signatures | none |
allowCertify | Let a first signature be a certification, which sets what stays allowed | false |
Events#
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfSignature } from '@embedpdf/angular/signature';
import { showWarning } from './warnings';
@Component({ selector: 'app-save-warning', template: '' })
export class SaveWarning {
constructor() {
inject(EpdfSignature)
.invalidationPredicted$.pipe(takeUntilDestroyed())
.subscribe(() => showWarning('Saving this change will break a signature.'));
}
}| Event | Fires when | Carries |
|---|---|---|
signed$ | A field was signed, whoever signed it | field, result, origin |
filled$, cleared$ | A mark was drawn into a field, or removed | field |
validated$ | The signatures were checked | verdicts |
protectionChanged$ | What the signatures allow changed | protection |
invalidationPredicted$ | A change would break a signature once saved | field, detail |
targetChanged$ | A field became the target, or the target was cleared | field (null when cleared) |
signRequested$ | In 'ask' mode, a mark was dropped on a field | field, mark |
inspectionRequested$ | A signed field was clicked | field |
settingsChanged$ | The settings changed | settings, changed |
result is what sign() resolves. “Sign here” in a form says what to show
for the last three.
Permissions#
| Permission | What it allows | Check |
|---|---|---|
doc.sign | Signing | signature.canSign(field) |
doc.sign.certify | Signing a certification | signature.canCertify() |
doc.forms.fill | Drawing a mark into a field without signing | signature.canFill(field) |
doc.download | Reading the exact bytes a signature covers | signature.canReadRevision() |
A user with a fields:sign (fields:fill) permission for one group signs (fills in) that group’s
fields only, so the checks take the field. See Signers and groups. On a
signed document, the engine also refuses what its signatures forbid.
Good to know#
Which bytes are checked#
The plugin checks the document as it is in the viewer, with changes that haven’t been downloaded yet,
so the verdict is the one the downloaded file will get. validate({ until: 'persisted' }) checks the
file as it was opened.
The signed bytes#
signature.readRevision({ revisionIndex }) gives you the exact version of the file a signature
covers, for an audit trail.
How signatures are stored, and what each certification allows, is on Digital signatures.
Your feedback goes directly to the documentation team.