---
title: "PDF viewer: passages, anchors and quotes"
description: "The add-on @skaile/forge-editor-pdf-viewer: the source-anchor property, a pdfjs viewer with a text layer, scrolling to a passage on reveal, and „Zitat anlegen“, which creates a quote block derived from the PDF."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/pdf-viewer"
---
# PDF viewer: passages, anchors and quotes

> The add-on @skaile/forge-editor-pdf-viewer: the source-anchor property, a pdfjs viewer with a text layer, scrolling to a passage on reveal, and „Zitat anlegen“, which creates a quote block derived from the PDF.

The add-on `@skaile/forge-editor-pdf-viewer` shows a PDF next to the text that cites it. A text selection in the PDF becomes an **anchor**: the page, the area on the page and the quoted words. The button „Zitat anlegen“ turns the anchor into a `quote` Typed block that is [derived](https://skaile-ai.github.io/forge-editor/guides/derive-methods) from the PDF. A click on a [reference chip](https://skaile-ai.github.io/forge-editor/guides/citations) with a page locator scrolls the viewer to the passage.

<table>
<thead>
  <tr>
    <th>
      Piece
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Where
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        source-anchor
      </code>
    </td>
    
    <td>
      the <a href="https://skaile-ai.github.io/forge-editor/guides/block-properties">
        Block property
      </a>
      
       type of a passage of a PDF
    </td>
    
    <td>
      library (<code>
        @skaile/forge-editor/core
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        page=<n>&anchor=<base64 JSON>
      </code>
    </td>
    
    <td>
      the reference locator that names such a passage
    </td>
    
    <td>
      library
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        quoteBlockTypes(locale)
      </code>
    </td>
    
    <td>
      the Block type <code>
        quote
      </code>
      
       with one <code>
        source-anchor
      </code>
      
       property
    </td>
    
    <td>
      editor and server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        anchorOfSelection
      </code>
      
      , <code>
        quoteBlockOf
      </code>
    </td>
    
    <td>
      pure: selection to anchor, anchor to <code>
        quote
      </code>
      
       block
    </td>
    
    <td>
      anywhere
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pdfViewerFeature()
      </code>
    </td>
    
    <td>
      the viewer in the inspector of a Typed block that keeps a PDF
    </td>
    
    <td>
      editor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PdfViewer
      </code>
    </td>
    
    <td>
      the viewer as a component for any page
    </td>
    
    <td>
      editor
    </td>
  </tr>
</tbody>
</table>

## The source-anchor property and the locator

A `source-anchor` value is `{ page, rects, quote: { exact, prefix?, suffix? } }`. `page` counts from 1. `rects` are fractions of the page (`x`, `y`, `w`, `h` from 0 to 1, origin top left), so the passage stays right at any zoom. `quote.exact` is the selected text with white space cut to single blanks; `prefix` and `suffix` are a little text around it.

The property kind orders and compares by page, reads as the quoted text and groups by page (`PROPERTY_KINDS['source-anchor']`), so a [query](https://skaile-ai.github.io/forge-editor/guides/query) can filter quotes by page or by words. The properties form shows page and quote read-only. Only the viewer writes the value.

The same passage as text is the locator of a reference chip: `page=12&anchor=<base64url JSON>`. `anchorLocatorOf(anchor)` writes it and `parseAnchorLocator(text)` reads it back (`null` when the text is not an anchor or its page differs from the locator's). Chips and backlinks show it as `S. 12`; the long base64 stays out of sight. A plain locator (`S. 12`, `12`, `3:07`) works as before.

When the editor reveals a source at an anchor locator, it sends two DOM events to the node view of the PDF's file block: `sk-reveal-page` (`detail` is the page) and `sk-reveal-anchor` (`detail` is the `SourceAnchor`). The viewer listens to both.

## Install and wire

```ts
import { defaultBlockTypes } from '@skaile/forge-editor/composites'
import { quoteBlockTypes } from '@skaile/forge-editor-pdf-viewer'
import { pdfViewerFeature } from '@skaile/forge-editor-pdf-viewer/vue' // or '/react'
import '@skaile/forge-editor-pdf-viewer/styles/pdf-viewer.css'

const editor = composeEditor({
  content: 'rich',
  interaction: 'full',
  // The Block types of the editor: the built-in ones and `quote`.
  blockTypes: [...defaultBlockTypes(t), ...quoteBlockTypes('de')],
  add: [pdfViewerFeature({ workerSrc: '/pdf.worker.min.mjs' })],
})
```

Give the Block type to the server too, so the schema, the AI chat and the query validation know it:

```ts
import { quoteBlockTypes } from '@skaile/forge-editor-pdf-viewer/server'

// installEditorServer({ blockTypes })
export const blockTypes = [...serverBlockTypes, ...quoteBlockTypes('de')]
```

pdfjs needs a worker. By default the viewer loads `pdf.worker.min.mjs` of the installed pdfjs version from jsDelivr. A host that cannot reach the CDN serves the file from `pdfjs-dist/build/` itself and passes its URL as `workerSrc` (feature option, or prop of `PdfViewer`).

## The viewer

`pdfViewerFeature` adds a section „PDF-Ansicht“ to the Typed block inspector. It shows for every block whose source (the `source` capability: `paper`, `book`, `document`) is a PDF file block. The viewer draws only the pages near the visible part, each as a canvas with a transparent text layer on top, so the text can be selected and searched with the browser.

- The toolbar has previous and next page, a page field, zoom out and in (50 % to 300 %).
- A reveal of the PDF's block scrolls to the page, or to the first line of the anchor, and marks the anchor's rects for a few seconds. Reveals of other blocks do not move this viewer.
- `PdfViewer` takes `src` (the URL of the PDF), `editor` (to create quotes and follow reveals) and `blockId` (the Typed block that keeps the PDF). Without `editor` it is a plain viewer.

## Create a quote

Select text in one page and press **Zitat anlegen**. The button works when the editor is editable and the PDF is a stored [asset](https://skaile-ai.github.io/forge-editor/guides/media-mentions-transcript) of a Document. The quote goes right behind the PDF's Typed block and is revealed.

```json
{
  "type": "typedBlock",
  "attrs": {
    "blockType": "quote",
    "meta": {
      "anchor": { "page": 12, "rects": [{ "x": 0.15, "y": 0.1, "w": 0.5, "h": 0.025 }], "quote": { "exact": "Der Hund bellt laut." } },
      "derivation": { "from": { "asset": "<name>.pdf" }, "method": "highlight", "inputHash": "<sha256 of the PDF>", "outputHash": "<sha256 of the body>", "createdAt": "…" }
    }
  }
}
```

The title is the first words of the quote. The body is the quoted text as a block quote and, when the PDF belongs to a Typed block, a reference chip to it whose locator is the anchor: a click on the chip in the quote opens the PDF at the passage. Because the quote has a Derivation, the block shows its origin, the [Explore trail](https://skaile-ai.github.io/forge-editor/guides/explore) lists it, and an edit of the body shows as „bearbeitet“.

`quoteBlockOf(asset, anchor, options)` builds the same JSON without a viewer: use it in an importer or a server tool.

```ts
import { anchorOfSelection, quoteBlockOf } from '@skaile/forge-editor-pdf-viewer/server'

const anchor = anchorOfSelection({ page: 3, box: pageRect, rects: selectionRects, text, pageText })
if (anchor) editor.commands.insertContent(quoteBlockOf({ name, hash }, anchor, { sourceBlockId: paperId }))
```


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
