---
title: "Signatures — Angular"
description: "Sign documents with a drawn, typed or uploaded signature and a key you control, check the signatures a document has, and warn before an edit breaks one."
framework: "Angular"
source: "https://www.cloudpdf.com/docs/headless/angular/forms/signatures"
---

# 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:

**`basic.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  inject,
  input,
  output,
  signal,
  untracked,
  viewChild,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } 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, toFieldRef, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { createTestSigner, EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { cloudEngine } from '@cloudpdf/engine';
import { localEngine } from '@embedpdf/engine';

const engine = cloudEngine();
const assetEngine = localEngine();
// 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 agreement: OpenInput = { kind: 'grant', grant: 'shr_G5GcxJeEBa2IiwIs0DCNruCL' };

// 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: agreement }] },
      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 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(() => {
    // Ada's signature, once she signs: the agreement already carries Lanternfield's.
    const signed = this.signature.signatures().find((each) => each.fieldName === 'approval');
    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() {
    // Start at the agreement's signature field. The browser holds no signatures
    // yet: add one person with a typed signature.
    effect(() => {
      const stage = this.stage();
      if (this.form.status() !== 'ready' || !stage || this.done) return;
      this.done = true;
      untracked(() => void this.setUp(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(stage: EpdfStage) {
    // Bring the agreement's signature field into view.
    const first = this.form.get(toFieldRef('approval'))?.widgets[0];
    const widget = first ? this.form.getWidget(first) : null;
    if (widget) stage.reveal(widget.page, { rect: widget.rect });
    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' },
    });
  }
}
```

**`basic.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

/* A mark's picture, inside its button */
.preview {
  display: block;
  height: 22px;
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`signature-plugin.ts`**

```typescript
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

```ts
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:

```ts
{ assetId } // a stamp, drawn, typed or uploaded; see "A person's signatures" below
{ source: pngBytes } // a PNG, a JPEG, or a one-page PDF
```

## Let 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:

```ts
await signature.placeMark({ assetId }, { field: toFieldRef('approval') }); // signs the field
await signature.placeMark({ assetId }, { page: page.ref, center: { x: 120, y: 90 } }); // a stamp
```

What 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.

**`click-to-sign.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  inject,
  untracked,
  viewChild,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } 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, toFieldRef, withForm } from '@embedpdf/angular/form';
import { EpdfStamp, withStamp } from '@embedpdf/angular/stamp';
import { createTestSigner, EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { cloudEngine } from '@cloudpdf/engine';
import { localEngine } from '@embedpdf/engine';

const engine = cloudEngine();
const assetEngine = localEngine();
const signer = createTestSigner({ commonName: 'Ada Lovelace' });

const agreement: OpenInput = { kind: 'grant', grant: 'shr_G5GcxJeEBa2IiwIs0DCNruCL' };

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfAnnotationLayer,
    EpdfFormLayer,
  ],
  providers: [
    provideEmbedPdf(
      { engine, initialDocuments: [{ source: agreement }] },
      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 stage = viewChild(EpdfStage);
  private done = false;

  protected readonly mark = computed(() => this.signature.signerRows()[0]?.signatures[0]);

  protected readonly hint = computed(() => {
    // Ada's signature, once she signs: the agreement already carries Lanternfield's.
    const signed = this.signature.signatures().find((each) => each.fieldName === 'approval');
    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: the agreement, where the form's signature field is, and a person's signature, armed.
    effect(() => {
      const stage = this.stage();
      if (this.form.status() !== 'ready' || !stage || this.done) return;
      this.done = true;
      untracked(() => void this.setUp(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(stage: EpdfStage) {
    // Bring the agreement's signature field into view.
    const first = this.form.get(toFieldRef('approval'))?.widgets[0];
    const widget = first ? this.form.getWidget(first) : null;
    if (widget) stage.reveal(widget.page, { rect: widget.rect });
    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 });
  }
}
```

**`click-to-sign.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

## 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:

```ts
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.

**`fill.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  signal,
  untracked,
  viewChild,
  ViewEncapsulation,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } 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 { cloudEngine } from '@cloudpdf/engine';
import { localEngine } from '@embedpdf/engine';

const engine = cloudEngine();
const assetEngine = localEngine();

const agreement: OpenInput = { kind: 'grant', grant: 'shr_G5GcxJeEBa2IiwIs0DCNruCL' };

const APPROVAL = toFieldRef('approval');

@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer],
  providers: [
    provideEmbedPdf(
      { engine, initialDocuments: [{ source: agreement }] },
      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 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: Ada's typed signature, ready to draw in, and the signature field in view.
    effect(() => {
      const stage = this.stage();
      if (this.form.status() !== 'ready' || !stage || this.done) return;
      this.done = true;
      untracked(() => void this.setUp(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(stage: EpdfStage) {
    // Bring the agreement's signature field into view.
    const first = this.form.get(APPROVAL)?.widgets[0];
    const widget = first ? this.form.getWidget(first) : null;
    if (widget) stage.reveal(widget.page, { rect: widget.rect });
    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);
  }
}
```

**`fill.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

## 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:

```ts
// 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:

```ts
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

**`signature-list.ts`**

```typescript
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, commenting and adding more signatures keep it
valid, as in Acrobat. Other changes break it once the file is saved, such as adding a form field. The
plugin warns you the moment a change would do that, as Acrobat does:

**`break-warning.ts`**

```typescript
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 to rule out more, such as comments, certifies the document to allow form filling
only: the engine then refuses those changes instead of warning about them.

**`break-warning.ts`**

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  inject,
  signal,
  untracked,
  viewChild,
  ViewEncapsulation,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } 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 { EpdfSignature, withSignature } from '@embedpdf/angular/signature';
import { cloudEngine } from '@cloudpdf/engine';

const engine = cloudEngine();

// The certificate Lanternfield signed the agreement with, published next to it. Trusting it is what
// makes the signature read 'valid'; your app trusts its own certificates the same way.
const lanternfieldCertificate = async () => {
  const response = await fetch('/pdfs/agreement.cer');
  return new Uint8Array(await response.arrayBuffer());
};

const agreement: OpenInput = { kind: 'grant', grant: 'shr_G5GcxJeEBa2IiwIs0DCNruCL' };

// Lanternfield's signature field, signed before the agreement was sent.
const PROVIDER = toFieldRef('provider');

// Two edits after an approval signature: a comment keeps it valid, a new form field breaks it.
@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, EpdfAnnotationLayer],
  providers: [
    provideEmbedPdf(
      { engine, initialDocuments: [{ source: agreement }] },
      withStage(),
      withRender(),
      withInteraction(),
      withAnnotation(),
      withForm(),
      withSignature({ trust: { anchors: async () => [await lanternfieldCertificate()] } }),
    ),
  ],
  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() || made().comment"
          (click)="addComment()"
        >
          Add a comment
        </button>
        <button
          type="button"
          class="button"
          [disabled]="!signed() || made().field"
          (click)="addField()"
        >
          Add a form field
        </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 annotation = inject(EpdfAnnotation);
  private readonly signature = inject(EpdfSignature);
  private readonly stage = viewChild(EpdfStage);
  private revealed = false;

  protected readonly signed = computed(() =>
    this.signature.signatures().find((each) => each.signed),
  );
  protected readonly status = computed(() => {
    const signed = this.signed();
    return signed
      ? `Signed by ${signed.signer.name}: ${signed.verdict?.summary ?? 'checking…'}`
      : 'Opening…';
  });
  protected readonly warning = signal<string | null>(null);
  protected readonly made = signal({ comment: false, field: false });

  /** Where the signature is: the edits go next to it. */
  private readonly widget = computed(() => {
    const first = this.form.get(PROVIDER)?.widgets[0];
    return first ? this.form.getWidget(first) : 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: the signatures, in view. Nothing is changed until a button is pressed.
    effect(() => {
      const stage = this.stage();
      if (this.form.status() !== 'ready' || !stage || this.revealed) return;
      this.revealed = true;
      untracked(() => {
        const widget = this.widget();
        if (widget) stage.reveal(widget.page, { rect: widget.rect });
      });
    });
  }

  protected async addComment() {
    const widget = this.widget();
    if (!widget) return;
    const { rect } = widget;
    await this.annotation.create(widget.page, {
      subtype: 'text',
      rect: { x: rect.x + rect.width + 6, y: rect.y, width: 20, height: 20 },
      contents: 'Looks good to us.',
    });
    this.made.update((current) => ({ ...current, comment: true }));
  }

  protected async addField() {
    const widget = this.widget();
    if (!widget) return;
    const { rect } = widget;
    await this.form.create({
      family: 'text',
      name: 'purchase_order',
      widgets: [
        {
          page: widget.page,
          rect: { x: rect.x, y: rect.y + rect.height + 36, width: rect.width, height: 24 },
        },
      ],
    });
    this.made.update((current) => ({ ...current, field: true }));
  }
}
```

**`break-warning.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --warn: #b45309;
  --warn-tint: #fffbeb;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

.warning {
  flex: 1 1 100%;
  font-size: 13px;
  color: var(--warn);
  background: var(--warn-tint);
  border: 1px solid #fde68a;
  border-radius: 9px;
  padding: 8px 12px;
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** Lanternfield signed this agreement before sending it. Add a comment: the signature
> stays valid. Add a form field: the warning shows before anything is saved.

## 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](../annotations/stamps):

```ts
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](../forms), 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:

```ts
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](#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](#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](#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](#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](#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](#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](#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](#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](#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](#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](#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

**`save-warning.ts`**

```typescript
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](#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](https://www.cloudpdf.com/docs/engine/forms/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](https://www.cloudpdf.com/docs/engine/forms/signatures).
