EmbedPDF

Reading text

Get a page’s text with page.text.get():

const page = doc.page(pageRef);
const { text } = await page.text.get();

text is the page’s text in reading order, as a normal JavaScript string. Lines are separated by \r\n.

Part of a page#

Characters on a page are numbered from 0. To read just some of them (the text a user selected, or a search match), pass a range:

const selected = await page.text.slice({ start: 120, count: 42 });

A range is always { start, count }: start is the first character, count how many. Search matches and selections are ranges too, so you can pass them straight in:

const { matches } = await doc.search.query({ text: 'total' });
const found = await doc.page(matches[0].page).text.slice(matches[0]);

A whole document#

There’s no single call for the whole document; read the pages and join them:

const { pages } = await doc.pages.list();
 
const texts = await Promise.all(pages.map((page) => doc.page(page.ref).text.get()));
const fullText = texts.map(({ text }) => text).join('\n\n');

The engine reads pages one at a time in the background, so this is fine for long documents too.

Permissions#

Reading text needs the doc.text.copy permission, because text you read can leave the document. Without it, both calls fail with Forbidden.

A user without doc.text.copy can still search (without snippets) and select text: both need only where the text is, not what it says. That lets you allow highlighting a PDF while stopping its text from being copied.

Good to know#

Characters and string positions#

The engine numbers characters the way the PDF stores them. Most of the time that matches the string: character 5 is text[5]. Two things make them differ:

  • Characters outside the Basic Multilingual Plane (most emoji, some CJK characters) are one character in the PDF but two positions in a JavaScript string.
  • Characters with no text of their own (a glyph the PDF maps to no Unicode character) have a number but add nothing to the string.

That’s why you pass ranges to slice() instead of cutting text yourself: slice() converts for you. get() also returns charCount (how many characters the page has) and, when the two numberings differ, a charMap that records where. You only need charMap if you convert positions yourself, for example to run your own search over text:

import { charRangeForTextOffsets } from '@embedpdf/engine';
 
const snapshot = await page.text.get();
const at = snapshot.text.indexOf('Total');
const range = charRangeForTextOffsets(snapshot, { start: at, count: 'Total'.length });
// range is { start, count } in characters: ready for slice(), layout.segments() or a highlight.
Was this page helpful?

Your feedback goes directly to the documentation team.