---
title: "Extraction JSON"
url: https://recursiveai.org/onepdf/docs/extraction-json/
description: "The JSON form of text extraction: glyphs, words, lines and blocks per page. It is written by PdfExtractionJson (ToJson, Write) and is meant to be compared…"
version: "1.11.0"
source: https://github.com/recursive-ai-oss/onepdf-csharp/blob/b90019f86b3d08820577502740a10c66befdd989/docs/extraction-json.md
---

# Extraction JSON

The JSON form of text extraction: glyphs, words, lines and blocks per page. It is written by `PdfExtractionJson`
(`ToJson`, `Write`) and is meant to be compared byte for byte across implementations, so it is versioned and fully
deterministic.

- **Format:** `onepdf.extraction`, **version 1**. A reader must check both members. Additive changes (new members) keep the
  version; renaming, removing or changing the meaning of a member raises it.
- **Encoding:** UTF-8, no byte-order mark. Strings are escaped as JSON requires and nothing more (non-ASCII text is written
  as it is). Unpaired surrogates in glyph text have been replaced by U+FFFD before writing.
- **Numbers:** coordinates and sizes are rounded to three decimals (ties to even), written in invariant form without an exponent and without
  trailing zeros; negative zero is written `0`. Integers are plain.
- **Member order** is the order below. Whitespace is insignificant (the writer can indent).

## Coordinate space

All coordinates are in *upright page space*: points, origin at the bottom-left of the page as a viewer shows it (page
`/Rotate` and box already applied), y up. A box is `[left, bottom, right, top]`.

## Shape

```
{
  "format": "onepdf.extraction",
  "version": 1,
  "pageCount": <int>,
  "pages": [ Page, ... ]
}

Page = {
  "index": <int>,                    zero-based page index
  "width": <number>, "height": <number>,
  "usedStructureTree": <bool>,       true when the structure tree decided the order
  "diagnostics": [ <string>, ... ],  notes about this page (for example a structure tree that covered too little)
  "text": <string>,                  the page's reading-order plain text: blocks separated by "\n\n", lines by "\n", table cells by "\t"
  "blocks": [ Block, ... ],          reading order
  "glyphs": [ Glyph, ... ]           omitted when the writer is asked not to include glyphs
}

Block = {
  "role": "body" | "header" | "footer",
  "table": <bool>,                   a grid of short cells; lines are rows
  "bounds": [l, b, r, t],
  "lines": [ Line, ... ]
}

Line = {
  "direction": "ltr" | "rtl",        base direction used to put the line in logical order
  "vertical": <bool>,                vertical writing
  "bounds": [l, b, r, t],
  "cells": [ <int>, ... ],           indices into "words" where a cell (a run set apart by a wide gap) starts; first is 0
  "text": <string>,                  words joined by " ", cells by "\t"
  "words": [ Word, ... ]             logical order (right to left on the page for right-to-left text)
}

Word = {
  "text": <string>,                  logical order
  "bounds": [l, b, r, t],
  "visible": <bool>,                 false when every glyph is invisible
  "glyphs": [ <int>, ... ]           indices into the page's "glyphs", visual order along the reading direction
}

Glyph = {
  "text": <string>,                  Unicode (after ligature decomposition and /ActualText substitution; may be empty)
  "font": <string> | null,
  "fontId": <int>,                   -1 when unknown
  "size": <number>,                  em size in points
  "matrix": [a, b, c, d, e, f],      glyph space (em) to page space
  "advance": <number>,               em units along the writing direction
  "bounds": [l, b, r, t],
  "vertical": <bool>,
  "color": "#rrggbb" | null,         fill colour
  "renderMode": <int>,               0 to 7
  "visible": <bool>,                 false for invisible text (render mode 3, OCR layers)
  "mcid": <int>                      marked-content id, -1 for none
}
```

The glyphs listed are the ones that took part in extraction: clipped and off-page glyphs and duplicates (fake bold) are already
gone unless the options kept them, and a glyph inside an `/ActualText` span carries the replacement text (first glyph) or empty text.
