---
title: "Drawing annotations yourself"
description: "Render each annotation as its own image, bake annotations into the page, or export them as a PDF."
source: "https://www.embedpdf.com/docs/engine/annotations/appearances"
---

# Drawing annotations yourself

A rendered page includes its annotations, which is all most apps need. This page is for when you
draw them yourself: each annotation as its own image over the page, so you can move, select and
edit them without rendering the page again. It also covers baking annotations into the page and
exporting them as a PDF.

What you draw is an annotation's **appearance**: the drawing stored with it.

```ts
import { shownAppearances } from '@embedpdf/engine';

const pageImage = await page.render.image({
  viewport: { kind: 'scale', scale: 2 },
  includeAnnotations: false,
}); // the page on its own

const { annotations } = await page.annotations.list();
const { appearances } = await page.annotations.renderAppearances({
  viewport: { kind: 'scale', scale: 2 },
});

// Each annotation as the page shows it now: its look at rest, in the state it shows.
for (const appearance of shownAppearances(appearances, annotations)) {
  const { url } = await appearance.image.objectUrl();
  const box = pageImage.transform.pageToPixels(appearance.rect); // { x, y, width, height } in the page image
  placeImage(url, box);
}
```

> The viewer's annotation plugin does all of this for you. This page is for drawing annotations
> against the engine directly.

## Render each annotation as an image

`renderAppearances()` draws every annotation of the page that has an appearance, each look as
its own image:

| Field   | What it is                                                                                                           |
| :------ | :------------------------------------------------------------------------------------------------------------------- |
| `ref`   | Which annotation it is.                                                                                              |
| `rect`  | Where the image goes, in page space.                                                                                 |
| `mode`  | Which look it is: `'normal'` at rest, `'rollover'` under the pointer, `'down'` while pressed.                        |
| `state` | Which state it draws, for an annotation whose look has states (a check box's `'Off'` and its on state); else `null`. |
| `image` | The image; `image.objectUrl()` gives a URL for an `<img>`, `image.blob()` its bytes.                                 |

| Option     | What it is                                                                                                                                                                                 | Default    |
| :--------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- |
| `viewport` | The size, the same option as for [a page image](https://www.embedpdf.com/docs/engine/documents/rendering#choose-the-size). Pass the page image's `viewport`, and the annotations match it. | scale `1`  |
| `rotation` | The page's rotation as you show it: `0`, `90`, `180` or `270`, clockwise.                                                                                                                  | `0`        |
| `modes`    | Which looks to render, of those each annotation has. `['normal']` gives only the look at rest. An empty list is refused.                                                                   | every look |
| `format`   | `'png'` or `'webp'`.                                                                                                                                                                       | see below  |

Images are PNG unless you ask for WebP.

### Looks and states

One call brings every look an annotation has, so you can show it at rest, under the pointer and
pressed without asking again:

- **Modes.** An annotation can have a look for when the pointer is over it (`'rollover'`) and for
  when it's pressed (`'down'`), besides the look at rest (`'normal'`). Form fields often do. You
  get one image for each look the annotation has, and none for a look it doesn't have.
- **States.** A check box or radio button has a look for each state: `'Off'` and its on state
  (`'Yes'`, or its export value). You get every state, whatever the check box shows now, each
  image labelled with its `state`. So when someone ticks the box, you already have the picture.
- **Which state shows** is the annotation's `appearanceState` (its `/AS`), or for a form field,
  its value.

`shownAppearances(appearances, annotations)` picks, for each annotation, its look at rest in the
state it shows: the page as it is now. To show a look under the pointer, pick it yourself:

```ts
import { annotationKey } from '@embedpdf/engine';

const hovered = appearances.find(
  (appearance) =>
    annotationKey(appearance.ref) === annotationKey(checkbox.ref) &&
    appearance.mode === 'rollover' &&
    (appearance.state === null || appearance.state === checkbox.appearanceState),
);
```

### Turned annotations

Some annotations can be turned; see [Turned shapes](https://www.embedpdf.com/docs/engine/annotations/shapes#turned-shapes).
A turned rectangle, ellipse, text box, stamp or caret whose drawing stays inside its turned box
has its image drawn unturned: the image's `rect` is the annotation's own `box`, and you turn the
image yourself, about its centre. `appearanceTurnOf(annotation)` gives the turn to apply, in
degrees clockwise, the same direction as CSS `rotate()`, or `null`:

```ts
import { appearanceTurnOf } from '@embedpdf/engine';

const rotation = appearanceTurnOf(annotation) ?? 0;

img.style.transformOrigin = 'center';
img.style.transform = `rotate(${rotation}deg)`;
```

Every other annotation comes as it is shown, placed by `rect`, with nothing to turn: lines,
polygons and ink are drawn with their points turned, a callout turns only its text box, and a
drawing that reaches past its box (a cloudy border's bumps) is drawn turned.

That split is what keeps editing cheap:

- turning an annotation never needs a new image, only a new transform;
- moving one never needs a new image, only a new position;
- resizing can stretch the image while the user drags, then render once when they let go.

Render again when the annotation's look changed, or for a sharper image at a new zoom. An
update's result tells you: `appearance.changed` is `true` when the annotation looks different.

### Raw pixels

`renderAppearancesRaw()` takes the same options and returns each annotation's pixels instead of an
image, with nothing encoded. Use it to draw on a canvas or in WebGL.

```ts
const { appearances } = await page.annotations.renderAppearancesRaw({
  viewport: { kind: 'scale', scale: 2 },
});

const { width, height, stride, data } = appearances[0].raster;
```

`data` holds the pixels as RGBA, four bytes a pixel and `stride` bytes a row, without
premultiplied alpha.

## Bake annotations into the page

`flatten()` paints annotations into the page's own content and removes them as annotations.
What's baked in can no longer be changed or deleted as an annotation.

```ts
const result = await page.annotations.flatten([refA, refB], { usage: 'display' });

result.results; // [{ ref: refA, status: 'applied' }, { ref: refB, status: 'unchanged' }]
```

| `status`      | What happened                                                                 |
| :------------ | :---------------------------------------------------------------------------- |
| `'applied'`   | Painted in and removed as an annotation.                                      |
| `'unchanged'` | Left as it was: hidden for that use, a popup, or with no appearance to paint. |

So you can say "2 of 3 flattened".

| `usage`     | What's painted                                        |
| :---------- | :---------------------------------------------------- |
| `'display'` | The annotations shown on screen. The default.         |
| `'print'`   | The annotations that print: those with `print: true`. |

Every ref must be on this page: a ref on another page, or an empty list, refuses the whole call
with `InvalidArg` and nothing changes. When something was painted, an `annotations.flattened`
[event](https://www.embedpdf.com/docs/engine/concepts/events) fires. Flattening can't be undone, and it ends undo for every
change made before it; see [Changes & undo](https://www.embedpdf.com/docs/engine/documents/changes#what-cant-be-undone).

To bake every annotation of some pages in one call, flatten the pages with `doc.pages.flatten()`;
see [Flatten a page's annotations](https://www.embedpdf.com/docs/engine/documents/pages#flatten-a-pages-annotations).

## Export annotations as a PDF

`exportAppearance()` makes a new one-page PDF of some annotations, exactly as the page shows them:
vector, in their places, on a page the size of the box around them. The document doesn't change.

```ts
const pdf = await page.annotations.exportAppearance([refA, refB]);
```

It's all or nothing: a ref that isn't on the page, is hidden, or has no appearance refuses the call
with `InvalidArg`. The PDF is a one-page drawing, so it can become a [stamp](https://www.embedpdf.com/docs/engine/annotations/stamps):

```ts
await page.annotations.create({ subtype: 'stamp', box: rect }, { resources: { appearance: pdf } });
```

## Permissions

Rendering annotations needs `doc.annotate.read`: seeing an annotation includes seeing how it
looks. Flattening changes the page, so it needs `doc.pages.modify` and `doc.annotate.modify`.
Exporting sends content out, so it needs `doc.download`.

## Good to know

### Appearances, in the PDF

An annotation's appearance is its `/AP`: a small drawing stored with it, with a normal look and
optionally a rollover and a pressed one. The engine draws it for the annotations it
creates, so they look the same in every PDF app. Another app's drawing stays as it is until you
change how that annotation looks.

### Which way `rotation` turns

Every `rotation` in the engine turns clockwise, in degrees, as you see it: a page's rotation, the
`rotation` option on this page, an annotation's own `rotation` and the `rotation` of a run of
turned text. So CSS `rotate()` and the engine always agree.

In the PDF file, an annotation's angle is stored the way PDF measures angles (counterclockwise,
because `y` goes up). The engine converts when it reads and writes, as it does with coordinates,
so you never see the stored number.

### Annotations without a number

Annotations with only a position ref are rendered too. See
[Refs & identity](https://www.embedpdf.com/docs/engine/concepts/identity).

### What prints

New annotations print unless you create them with `print: false`, so `usage: 'print'` includes
them. Annotations from other apps may have `print` off; set `print: true` on the ones that should
print, or flatten with `'display'`.
