EmbedPDF

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.

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.

FieldWhat it isDefault
boxThe text box. Required.
contentsThe text, plain.empty
fontFamilyThe font; see Fonts.'helvetica'
fontSizeThe text size in points.12
textAlign'left', 'center', 'right' or 'justify'.'left'
verticalAlignWhere the lines sit in the box: 'top', 'middle', 'bottom'.'top'
fontColorThe text color.black
colorThe border color, and a callout’s line.black
interiorColorThe background, or null for none.none
strokeWidthThe border width in points. 0 for no border.1
opacityFrom 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, '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. A cloudy border takes cloudyIntensity, as on 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.

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:

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

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',
});
FieldWhat it isDefault
calloutLineTwo points (straight) or three (with a bend). The first is what it points at, the last touches the box.none
lineEndingWhat’s drawn at the point it points at, such as 'open-arrow'; see 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:

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:

PropertyWhat it is
familyThe font, by family name: 'Helvetica', 'Times', 'Courier', or a font the engine has.
weightFrom 100 to 900: 400 is regular, 700 bold.
italictrue for italic.
sizeThe size in points.
colorA hex color, '#RRGGBB'.
decoration['underline'], ['line-through'], or both.
script'normal', 'sub' or 'super'.
letterSpacingExtra points after every character.
horizontalScaleCharacter 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 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 sendWhat happens
contentsThe text is replaced with plain paragraphs in the body style. Bold words and other run styles go.
richText without bodyThe text is replaced; the body style stays.
richText with bodyThe text is replaced, and the body style changes where the body says.
fontFamily, fontSize, fontColor, textAlignThe body style changes; runs keep their own styles.
contents and richTextThey must be the same text (contents is the plain text of richText), or the update fails with InvalidArg.
// 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.

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

Was this page helpful?

Your feedback goes directly to the documentation team.