# PDF Annotation

Source: https://www.potatoannotator.com/docs/annotation-types/pdf-annotation

The `pdf` display renders a PDF in the browser with [PDF.js](https://mozilla.github.io/pdf.js/) and puts the annotation surface on the rendered page rather than on text extracted beforehand. An annotator sees the real document, with its columns, tables, figures, and page breaks intact, and labels it in place. The display takes one required key, the field holding the PDF path or URL, and everything else is a display option.

```yaml
instance_display:
  fields:
    - key: pdf
      type: pdf
      label: "Document"
      display_options:
        view_mode: scroll
        max_height: 760
        zoom: page-width
```

`view_mode` takes `scroll`, `paginated`, or `side-by-side`. Continuous scroll stacks every page in one container, which matters when an annotation has to reach across a page break. Paginated view shows one page at a time with navigation controls and suits long documents where the annotator works page by page.

## Three annotation modes

`annotation_mode` decides what the annotator can draw, and it is the option that changes the task rather than its appearance. The default is `span`.

| Mode | What the annotator does | Anchored to |
|---|---|---|
| `span` | Selects text and applies a label | The PDF.js text layer |
| `bounding_box` | Draws a box anywhere on the page | Page coordinates |
| `link` | Marks anchors, then connects them | Text spans and page regions |

Span mode depends on the PDF carrying a text layer, which is true of any PDF generated from a word processor or a typesetting system and false of a scan. Bounding-box mode does not, so a scanned page can still be labeled by region without any text extraction at all.

Because the `pdf` display anchors spans through the PDF.js text layer rather than the `.text-content` wrapper that other displays use, it does not accept the `span_target` flag. Span annotation still works, through the PDF's own text layer.

## Cross-page linking

Link mode is for relations that span pages, such as a claim on page 2 that rests on a figure on page 9. The annotator marks anchors first, then draws typed links between them, and Potato records the anchors and the links under separate schema names.

```yaml
instance_display:
  fields:
    - key: pdf
      type: pdf
      label: "Document"
      display_options:
        annotation_mode: link
        view_mode: scroll
        enable_text_anchors: true
        enable_region_anchors: true
        anchor_schema: pdf_anchors
        link_schema: pdf_links
        anchor_labels:
          - name: claim
            color: "#dc2626"
          - name: figure
            color: "#2563eb"
        link_types:
          - name: refers_to
            directed: true
            color: "#dc2626"
            allowed_source_labels: [claim]
            allowed_target_labels: [figure]
```

`allowed_source_labels` and `allowed_target_labels` restrict which anchors a link type can join, which is how a guideline becomes something the interface enforces instead of something the annotator has to remember. A `refers_to` link configured as above can start only on a `claim` and end only on a `figure`, so a link drawn from a figure back to a claim is refused. Set `directed: false` for a symmetric relation such as `same_as`.

Anchors come in two kinds and both can be turned off independently. `enable_text_anchors` allows highlighting a text span, and `enable_region_anchors` allows drawing a region box, which is what a figure or a table needs. A complete worked example ships upstream in [`examples/advanced/pdf-link-scroll/`](https://github.com/davidjurgens/potato/blob/master/docs/annotation-types/text/pdf_cross_page_linking.md).

## Scanned documents and OCR

`ocr` is off by default, and Potato reads it only in link mode. When it is set, words are extracted server-side and used to build a client text layer, so a scan with no embedded text can still carry text anchors. The option takes `false`, `true`, or `auto`, and Potato rejects any other value.

```yaml
instance_display:
  fields:
    - key: pdf
      type: pdf
      display_options:
        annotation_mode: link
        ocr: auto
        ocr_dpi: 200
        ocr_lang: eng
```

The three settings differ in when the pass runs. `false` uses the embedded text layer alone, `true` always runs OCR, and `auto` falls back to OCR only when the embedded text layer comes back empty. Prefer `auto` on a mixed corpus, because OCR is slow and needs Tesseract installed. `ocr_dpi` controls the resolution each page is rasterized at before OCR reads it, and raising it costs time on every page. `ocr_lang` takes a Tesseract language code and defaults to `eng`.

## Display options

| Option | Default | Effect |
|---|---|---|
| `view_mode` | `scroll` | `scroll`, `paginated`, or `side-by-side` |
| `max_height` | `700` | Container height in pixels |
| `max_width` | none | Container width |
| `text_layer` | `true` | Enables text selection |
| `show_page_controls` | `true` | Page navigation controls |
| `initial_page` | `1` | Page shown first |
| `zoom` | `auto` | `auto`, `page-fit`, `page-width`, or a percentage |
| `annotation_mode` | `span` | `span`, `bounding_box`, or `link` |
| `bbox_min_size` | `10` | Smallest box accepted, in pixels |
| `bbox_colors` | none | Per-label box colors |
| `show_bbox_labels` | `true` | Draws labels on boxes |
| `thumbnail_sidebar` | `true` | Page thumbnails in paginated view |
| `enable_text_anchors` | `true` | Text spans usable as link anchors |
| `enable_region_anchors` | `true` | Region boxes usable as link anchors |
| `anchor_schema` | `pdf_anchors` | Schema recorded on anchors |
| `link_schema` | `pdf_links` | Schema recorded on links |
| `ocr` | `false` | `false`, `true`, or `auto` |
| `ocr_dpi` | `200` | OCR rendering resolution |
| `ocr_lang` | `eng` | Tesseract language code |

## Word and Markdown files

A DOCX or Markdown file uses the `document` display instead, which keeps the document's heading and paragraph structure and does accept `span_target`. It reads `annotation_mode` of `span` or `bounding_box`, and `show_outline` builds a table of contents from the headings in the body. Potato passes the rendered markup through an allowlist before it reaches the annotator, so a corpus field carrying an executable tag is stripped rather than run.

```yaml
instance_display:
  fields:
    - key: report
      type: document
      label: "Report"
      display_options:
        show_outline: true
        max_height: 600
```

## Further reading

- [Instance display](/docs/core-concepts/instance-display) lists every display type and which of them accept span targets.
- [Span annotation](/docs/annotation-types/span-annotation) covers the label options a span scheme takes.
- [How to annotate documents](/docs/guides/document-annotation) works through a document-review task end to end.
- For implementation details, see the [source documentation](https://github.com/davidjurgens/potato/blob/master/docs/annotation-types/instance_display.md).
