---
title: "Free text"
description: "Put text boxes and callouts on a page, in any font, with bold, italic and colored words."
source: "https://www.embedpdf.com/docs/engine/annotations/free-text"
---

# Free text

A free text annotation writes text straight onto the page: a text box, or a callout with a line
that points at something. The engine lays out the text and draws it, so it looks the same in
every PDF app.

```ts
const { annotation } = await page.annotations.create({
  subtype: 'free-text',
  box: { x: 72, y: 132, width: 200, height: 60 },
  contents: 'Please check this section',
  fontSize: 14,
});
```

## Text boxes

The text wraps inside `box`. Start a new line with `\n` in `contents`; it reads back as `\r`,
the line break PDF uses.

| Field           | What it is                                                       | Default       |
| :-------------- | :--------------------------------------------------------------- | :------------ |
| `box`           | The text box. Required.                                          |               |
| `contents`      | The text, plain.                                                 | empty         |
| `fontFamily`    | The font; see [Fonts](#fonts).                                   | `'helvetica'` |
| `fontSize`      | The text size in points.                                         | `12`          |
| `textAlign`     | `'left'`, `'center'`, `'right'` or `'justify'`.                  | `'left'`      |
| `verticalAlign` | Where the lines sit in the box: `'top'`, `'middle'`, `'bottom'`. | `'top'`       |
| `fontColor`     | The text color.                                                  | black         |
| `color`         | The border color, and a callout's line.                          | black         |
| `interiorColor` | The background, or `null` for none.                              | none          |
| `strokeWidth`   | The border width in points. `0` for no border.                   | `1`           |
| `opacity`       | From `0` to `1`, for the whole box: border, background and text. | `1`           |

A read also has `intent`: `'free-text-callout'` for a box with a [callout line](#callouts),
`'free-text'` for the others. The engine sets it from the `calloutLine`; you don't need to. A
box another app made as a typewriter reads `'free-text-typewriter'` and stays one; you can't
create one.

`borderStyle` and `dashArray` work as for every annotation; see the
[overview](https://www.embedpdf.com/docs/engine/annotations#style). A cloudy border takes `cloudyIntensity`, as on
[shapes](https://www.embedpdf.com/docs/engine/annotations/shapes).

## Colors

A free text box has three colors, each with its own job:

- `fontColor` colors the text.
- `color` draws the border and a callout's line.
- `interiorColor` fills the box. Leave it out for a transparent box.

Acrobat keeps them apart the same way, so a box from Acrobat reads the colors it shows there.

```ts
await page.annotations.create({
  subtype: 'free-text',
  box: { x: 72, y: 212, width: 200, height: 60 },
  contents: 'Approved',
  fontFamily: 'helvetica-bold',
  fontSize: 18,
  textAlign: 'center',
  color: '#006400', // green border
  fontColor: '#000000', // black text
  interiorColor: '#e6ffe6', // pale green background
});
```

## Fonts

`fontFamily` takes one of the 14 standard PDF fonts. Every PDF app has them, so nothing is
embedded in the file:

`helvetica`, `helvetica-bold`, `helvetica-oblique`, `helvetica-bold-oblique`, `times-roman`,
`times-bold`, `times-italic`, `times-bold-italic`, `courier`, `courier-bold`, `courier-oblique`,
`courier-bold-oblique`, `symbol`, `zapf-dingbats`.

The standard fonts only cover Latin text.

For other scripts (Chinese, Arabic, Cyrillic…) or your own typeface, register a font with the
engine and pass its key as `fontFamily`:

```ts
await engine.fonts.register({ key: 'noto-sc', data: fontBytes });

await page.annotations.create({
  subtype: 'free-text',
  box: { x: 72, y: 132, width: 200, height: 60 },
  contents: '请检查这一部分',
  fontFamily: 'noto-sc',
  fontSize: 14,
});
```

When you save, the engine embeds only the characters the annotation uses. Reading the annotation
back gives `fontFamily: 'noto-sc'` again. A key that isn't registered fails with `InvalidArg`. See
[Fonts](https://www.embedpdf.com/docs/engine/documents/fonts) for registering fonts and filling in missing characters.

## Callouts

A callout is a text box with a line that points at something on the page. Give the box a
`calloutLine`:

```ts
await page.annotations.create({
  subtype: 'free-text',
  box: { x: 300, y: 132, width: 180, height: 60 }, // the text box
  calloutLine: [
    { x: 200, y: 272 }, // the point it points at
    { x: 250, y: 162 }, // a bend (optional)
    { x: 300, y: 162 }, // where the line meets the box
  ],
  lineEnding: 'open-arrow',
  contents: 'This total is wrong',
});
```

| Field         | What it is                                                                                                                                                | Default |
| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ |
| `calloutLine` | Two points (straight) or three (with a bend). The first is what it points at, the last touches the box.                                                   | none    |
| `lineEnding`  | What's drawn at the point it points at, such as `'open-arrow'`; see [Line endings](https://www.embedpdf.com/docs/engine/annotations/shapes#line-endings). | none    |

`box` stays the text box; the engine makes room for the line and its arrow in `rect`, where the
callout sits on the page. A callout line goes only with `intent: 'free-text-callout'`, which a
create sets for you; elsewhere it fails with `InvalidArg`. To add a line to a plain box, send
both, and to turn a callout back into a plain box, send `calloutLine: null` and
`intent: 'free-text'`.

## Rich text

Every free text also has a `richText` document, for text with bold, italic or colored words. It
has a `body`, the style of the whole box, and `paragraphs` of `runs`. A run is a piece of text
with a `style` that holds only what it changes:

```ts
await page.annotations.create({
  subtype: 'free-text',
  box: { x: 72, y: 132, width: 300, height: 60 },
  fontSize: 14,
  richText: {
    paragraphs: [
      {
        runs: [
          { text: 'Total is ' },
          { text: 'wrong', style: { weight: 700, color: '#CC0000' } },
          { text: ', see page 4.' },
        ],
      },
    ],
  },
});
```

The `body` is the same style as `fontFamily`, `fontSize`, `fontColor` and `textAlign`, so you
rarely write one. When you send both, the field wins: `fontColor: '#CC0000'` with a `body` of
`{ color: '#0000CC' }` makes red text. `contents` always reads back as the plain text:
`'Total is wrong, see page 4.'`, with paragraphs joined by `\r`.

A style can hold:

| Property          | What it is                                                                                 |
| :---------------- | :----------------------------------------------------------------------------------------- |
| `family`          | The font, by family name: `'Helvetica'`, `'Times'`, `'Courier'`, or a font the engine has. |
| `weight`          | From `100` to `900`: `400` is regular, `700` bold.                                         |
| `italic`          | `true` for italic.                                                                         |
| `size`            | The size in points.                                                                        |
| `color`           | A hex color, `'#RRGGBB'`.                                                                  |
| `decoration`      | `['underline']`, `['line-through']`, or both.                                              |
| `script`          | `'normal'`, `'sub'` or `'super'`.                                                          |
| `letterSpacing`   | Extra points after every character.                                                        |
| `horizontalScale` | Character width, `1` for normal.                                                           |

The `body`, and a paragraph that differs from it, also take `align` (`'left'`, `'center'`,
`'right'` or `'justify'`) and `dir` (`'ltr'` or `'rtl'`).

Runs change only what they name, so a change to the body moves every run that didn't set its
own value: make the body red and a blue word stays blue.

> Editing rich text in the browser? The [rich text editor](https://www.embedpdf.com/docs/headless/concepts/rich-text) in the
> annotation plugin does the typing, the selection and the formatting for you.

## Change the text

What an update does depends on what you send:

| You send                                           | What happens                                                                                                    |
| :------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `contents`                                         | The text is replaced with plain paragraphs in the body style. Bold words and other run styles go.               |
| `richText` without `body`                          | The text is replaced; the body style stays.                                                                     |
| `richText` with `body`                             | The text is replaced, and the body style changes where the `body` says.                                         |
| `fontFamily`, `fontSize`, `fontColor`, `textAlign` | The body style changes; runs keep their own styles.                                                             |
| `contents` and `richText`                          | They must be the same text (`contents` is the plain text of `richText`), or the update fails with `InvalidArg`. |

```ts
// Make one word italic, keep everything else:
await page.annotations.update(annotation.ref, {
  richText: {
    paragraphs: [
      {
        runs: [
          { text: 'Please check ' },
          { text: 'this', style: { italic: true } },
          { text: ' section' },
        ],
      },
    ],
  },
});
```

A `body` changes only the properties it names, like any other update: a body of `{ size: 18 }`
makes the text bigger and keeps its font, color and alignment.

## Permissions

Reading free text needs `doc.annotate.read`. Creating, changing and deleting it needs the same
permissions as any annotation; see [Permissions](https://www.embedpdf.com/docs/engine/annotations#permissions).

## Good to know

### Where the text sits

The text starts twice the border width inside the box, on every side, the way Acrobat lays it
out: a 1-point border leaves 2 points around the text. Text that doesn't fit in that area is cut
off.

PDF has no entry for where the lines sit in the box, so `verticalAlign` is the engine's own:
other PDF apps show the box as the engine drew it, and put the text at the top if they draw it
again themselves.

### Justified text

PDF's own alignment entry has no *justify*, so `'justify'` is saved in the rich text only. A PDF
app that reads just the plain text shows a justified box left-aligned.

### Fonts inside rich text

A run names its font by family, the way the PDF does: `'Helvetica'`, `'Times'`, `'Courier'`, the
family name of a font the engine has, or a font the document already embeds. A family the engine
can't find is drawn in Helvetica; the run keeps the name you gave. `fontFamily` and `fontSize`
always read back the body's font and size.

### Turned boxes

A free text box turns like a rectangle: set `box` and `rotation` (see
[Shapes](https://www.embedpdf.com/docs/engine/annotations/shapes#turned-shapes)). On a callout only the text box turns;
the line stays where it is.

Acrobat turns a text box only a quarter at a time, when you add one on a turned page. Such a box
reads its `rotation` (90, 180 or 270), and a box you turn by a quarter opens in Acrobat as its
own. A box turned by another angle shows turned in Acrobat too, but Acrobat straightens it when
you edit its text there.

### How it's stored

For readers who know PDF: `color` is the color in `/DA`, `fontColor` is the color of the rich
text's body, `interiorColor` is `/C`, and `richText` is written as `/RC` (XHTML) and `/DS`, the
form Acrobat reads and writes. `verticalAlign` is kept in the annotation's `/EMBD_Metadata`. `contents` is
`/Contents`, `calloutLine` is `/CL` and `intent` is `/IT`. For a callout, the PDF's own `/Rect`
takes in the line, and `/RD` says where the text box is inside it, as Acrobat writes it; the
engine writes both from `box`.
