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.
| Field | What it is | Default |
|---|---|---|
box | The text box. Required. | |
contents | The text, plain. | empty |
fontFamily | The font; see 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,
'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:
fontColorcolors the text.colordraws the border and a callout’s line.interiorColorfills 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',
});| 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. | 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:
| 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 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. |
// 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.
Your feedback goes directly to the documentation team.