This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/headless/text-annotations.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Headless text annotations | Nutrient Web SDK

The instance.annotations.text namespace exposes the text annotation editor as a programmatic surface. Apply bold, italic, and underline; change the font family, size, color, and background; and subscribe to editor state changes, all from your own UI.

Looking for the visual side of this? Replacing the built-in text controls with your own takes two steps: Remove the built-in tool, and then create your own. This page covers the programmatic surface you’ll call from inside that custom UI.

When to use this

Reach for the headless text annotation API when you’re building:

  • A host-app text-formatting toolbar that lives outside the viewer container.
  • A keyboard shortcut palette your application defines for bold, italic, font selection, and so on.
  • A custom font picker fed by getFonts() instead of the default font list.
  • A multipane editing experience where the formatting controls are in a separate panel from the canvas.

API reference

The text annotation namespace exposes three groups of methods: style and formatting, editor focus and selection, and editor state and fonts.

Style and formatting

Each setter accepts an optional second argument identifying the annotation to apply the change to. Omit the argument and the SDK targets the annotation currently in the editor.

MethodNotes
instance.annotations.text.setTextStyle(options, annotationOrId?)Applies one or more text styles. options is { bold?: boolean, italic?: boolean, underline?: boolean }. Omit a field to leave it unchanged.
instance.annotations.text.setFontFamily(family, annotationOrId?)Sets the font family. The value is applied as passed. getFonts() lists the families the SDK resolved.
instance.annotations.text.setFontSize(size, annotationOrId?)Sets the font size in points.
instance.annotations.text.setFontColor(color, annotationOrId?)Sets the font color. color is a NutrientViewer.Color.
instance.annotations.text.setBackgroundColor(color, annotationOrId?)Sets the background fill color. Pass null to clear.
instance.annotations.text.restoreSelection()Restores the most recent selection inside the inline editor after a toolbar interaction has stolen focus. Requires the annotation to be in EDITING mode.

Editor focus and selection

These methods drive the live editor in EDITING mode, which is useful for keyboard-shortcut palettes and external toolbars that need to drive the caret directly.

MethodNotes
instance.annotations.text.focusEditor()Programmatically focuses the live text editor. Requires the editor to be in EDITING mode.
instance.annotations.text.blurEditor()Programmatically blurs the live text editor. Requires EDITING mode.
instance.annotations.text.setSelection(start, length = 0)Sets the editor selection by character offsets. length = 0 places the caret. Requires EDITING mode; validates non-negative integers.

Editor state and fonts

MethodNotes
instance.annotations.text.getEditorState()Returns a snapshot of the editor state, or null if no text annotation is active.
instance.annotations.text.getFonts()Returns { supportedFonts, customFonts, unsupportedCurrentFont }. The last field is set when the active annotation uses a font the SDK didn’t load. Render it in your picker so the current font is always selectable.
instance.annotations.text.getColorPresets()Returns the resolved color presets for font, background, and border colors.

Events

EventWhen it fires
textAnnotationEditorState.changeWhenever the editor state changes, selection, formatting, focus, and so on.

The event payload is { current, previous }. Each side has the same shape as getEditorState()’s return value, or null when no text annotation is active. Read current to keep your custom toolbar in sync with the editor without polling; previous is useful for diffing transitions.

Example: An external text-formatting toolbar

This example builds a host-app toolbar with bold, italic, font family, font size, and color controls. The toolbar listens for textAnnotationEditorState.change and reflects the current editor state in its button states:

const instance = await NutrientViewer.load({
container: "#viewer",
document: "contract.pdf"
});
const toolbar = document.getElementById("text-toolbar");
const boldButton = toolbar.querySelector("#bold");
const italicButton = toolbar.querySelector("#italic");
const fontSelect = toolbar.querySelector("#font");
const sizeInput = toolbar.querySelector("#size");
const colorInput = toolbar.querySelector("#color");
const fonts = instance.annotations.text.getFonts();
const families = [...fonts.supportedFonts, ...fonts.customFonts];
// `unsupportedCurrentFont` is the font the active annotation uses but the
// SDK didn't load. Prepend it so the dropdown always reflects the current font.
if (fonts.unsupportedCurrentFont && !families.includes(fonts.unsupportedCurrentFont)) {
families.unshift(fonts.unsupportedCurrentFont);
}
families.forEach((family) => {
const option = document.createElement("option");
option.value = family;
option.textContent = family;
fontSelect.appendChild(option);
});
instance.addEventListener("textAnnotationEditorState.change", ({ current }) => {
if (!current) {
toolbar.style.display = "none";
return;
}
toolbar.style.display = "flex";
boldButton.classList.toggle("is-active", current.marks.bold === "on");
italicButton.classList.toggle("is-active", current.marks.italic === "on");
fontSelect.value = current.fontFamily.value ?? "";
sizeInput.value = current.fontSize.value ?? "";
});
boldButton.onclick = () => {
instance.annotations.text.restoreSelection();
const state = instance.annotations.text.getEditorState();
const isBold = state?.marks.bold === "on";
instance.annotations.text.setTextStyle({ bold: !isBold });
};
italicButton.onclick = () => {
instance.annotations.text.restoreSelection();
const state = instance.annotations.text.getEditorState();
const isItalic = state?.marks.italic === "on";
instance.annotations.text.setTextStyle({ italic: !isItalic });
};
fontSelect.onchange = () => {
instance.annotations.text.restoreSelection();
instance.annotations.text.setFontFamily(fontSelect.value);
};
sizeInput.oninput = () => {
instance.annotations.text.restoreSelection();
instance.annotations.text.setFontSize(Number(sizeInput.value));
};
colorInput.oninput = () => {
instance.annotations.text.restoreSelection();
instance.annotations.text.setFontColor(
NutrientViewer.Color.fromHex(colorInput.value)
);
};

The restoreSelection() call before each formatting action matters: Clicking a toolbar button moves focus out of the editor and clears the selection. Restore the selection before applying the change.

Example: Offering a font the SDK didn’t load

getFonts() reports the families the SDK resolved: the ones from its built-in list of common families that it confirmed the browser can render. It also includes any loaded through customFonts on a Standalone setup. Document Engine uses fonts placed in its custom font directory for rendering and export, so such a family is absent from supportedFonts and customFonts. It surfaces in unsupportedCurrentFont only while a single annotation already using it is selected, which doesn’t make it available for anything else. The picker is yours, so its option list is yours too. Build the list of Document Engine families from the get global fonts endpoint, which returns the fullName of each font mounted in the directory. That endpoint takes the Document Engine API key, so call it from your backend. Hand the resulting list to the client. Then add those families alongside the ones getFonts() reports:

const serverFontSelect = document.querySelector("#server-font");
// Families served by Document Engine. Fetch these from your backend — the
// get-global-fonts endpoint needs the Document Engine API key.
const serverFamilies = ["Nunito Sans Regular"];
const reported = instance.annotations.text.getFonts();
const reportedFamilies = [...reported.supportedFonts, ...reported.customFonts];
const offered = [
...reportedFamilies,
...serverFamilies.filter((family) => !reportedFamilies.includes(family)),
];
// The font of the selected annotation when the SDK did not load it. Keep it in
// the list so the current font stays selectable.
if (
reported.unsupportedCurrentFont &&
!offered.includes(reported.unsupportedCurrentFont)
) {
offered.unshift(reported.unsupportedCurrentFont);
}
offered.forEach((family) => {
const option = document.createElement("option");
option.value = family;
option.textContent = family;
serverFontSelect.appendChild(option);
});

setFontFamily() applies the value as passed, so a family from that list reaches the annotation and syncs like any other:

serverFontSelect.onchange = () => {
const annotation = instance.getSelectedAnnotations()?.first();
if (annotation instanceof NutrientViewer.Annotations.TextAnnotation) {
instance.annotations.text.setFontFamily(serverFontSelect.value, annotation);
}
};

Omitting the second argument targets the single selected text annotation in either SELECTED or EDITING mode. Pass the annotation explicitly to target one that isn’t selected.

To render the text while editing, load the same font in the browser through a custom style sheet. The signature fonts guide describes the same approach for the signing UI. To embed it on export, keep the file in the Document Engine font directory. The browser draws the text during editing, and Document Engine writes the appearance stream.

Replacing the built-in font control also removes the font size and alignment controls, because the annotation toolbar exposes all three as a single font item. Filter it out for text annotations only:

NutrientViewer.load({
annotationToolbarItems: (annotation, { defaultAnnotationToolbarItems }) =>
annotation instanceof NutrientViewer.Annotations.TextAnnotation
? defaultAnnotationToolbarItems.filter((item) => item.type !== "font")
: defaultAnnotationToolbarItems,
});

Rebuild the size and alignment controls with setFontSize() and the annotation’s horizontalAlign and verticalAlign properties.

Example: Combining several styles in one call

setTextStyle accepts multiple flags at once, so a “make this bold and italic” command is a single call:

instance.annotations.text.restoreSelection();
instance.annotations.text.setTextStyle({ bold: true, italic: true });

Omitting a field leaves that style unchanged; the SDK applies only the fields you set.