4.7 KiB
Supported formats, and what they honestly do
Two ideas are kept apart throughout this service, because conflating them is how customers get surprised:
- Reproduction — the output is the input's pixels or bytes. Nothing is interpreted, so nothing can be misinterpreted.
- Reconstruction — the output is the engine's reading of the document. It is labelled lossy, because it is.
Every reconstruction target below is lossy. That is a property of the problem: a PDF records where ink went, not what a paragraph was, and recovering the second from the first is inference.
Conversions
43 converters. pdf → * are reconstructions unless marked otherwise.
| From | To |
|---|---|
pdf |
docx xlsx csv json md html txt · png jpeg tiff (reproduction) · pdf (passthrough, or searchable) |
docx |
pdf html md txt json · docx (passthrough) |
xlsx |
pdf csv html md txt json |
pptx |
pdf html md txt json |
csv |
xlsx pdf html md |
md |
pdf html |
html |
pdf docx md |
txt |
pdf docx md |
png jpeg tiff |
pdf |
pdf → pdf
Byte-identical passthrough by default, and that is a promise: a signed or archived PDF that came back re-encoded would be a different artefact.
With an explicit ocr_policy=force, it instead returns a searchable PDF —
the original page images unchanged, with the recognised text laid over them at
render mode 3 (drawn, extractable, painting nothing). Automatic routing choosing
force is not enough to trigger this: that decision is made to help
reconstruction targets recover text, and treating it as permission to re-encode
the source would mean every scan came back as a different file.
The searchable path rasterises at 200 dpi, so a 300 dpi scan is resampled down. That is why it is opt-in.
Input limits
| Limit | Value | On exceeding |
|---|---|---|
| File size | 50 MB | 413 resource_limit |
| PDF pages | 200 | 413 resource_limit |
| Archive entries | bounded | 413 resource_limit |
| Archive uncompressed size | bounded | 413 resource_limit |
| Archive nesting depth | bounded | 413 resource_limit |
| Archive compression ratio | bounded | 413 resource_limit |
Legacy .doc and .docm are refused with a clear message rather than parsed
badly. A PDF with a user password is refused; one with only an owner
password is converted, with a warning saying so, because such files are readable
and common and refusing them turns away legitimate documents.
For inputs beyond these limits, split the document. Chunking is deliberately the caller's decision: an engine that silently splits a 400-page contract and reassembles it has made a structural choice nobody sanctioned.
Known limitations
Each is disclosed in the output's warnings, not just here.
Embedded Word charts (writers/pdf_from_docx.py) render as a labelled
placeholder in docx → pdf. The chart is a full DrawingML plotting
specification; rendering it faithfully means implementing a chart engine, and
an approximation with wrong axes is worse than an honest placeholder.
OCR page cap — 50 pages per document by default. Beyond it the document converts with a warning naming the pages that were skipped.
Arabic OCR roughly doubles recognition time and runs only when the weights are present. Mixed-script documents are merged per line by score.
Reading order on unusual layouts — recursive XY-cut handles columns, sidebars and pull-quotes. A layout with no straight cut anywhere (heavily overlapping artwork, spiral text) degrades to top-to-bottom.
Table detection is conservative by design. Three detectors are tried, most reliable first, each gated by a plausibility check. Prose that merely lines up in columns will not become a table; a borderless table with irregular spacing may not either. Soup tables are worse than missing ones.
A page that cannot be reconstructed is embedded as a page image rather than having a layout invented for it, and says so in the warnings.
Licensing
Every runtime dependency is permissively licensed and every model is freely redistributable:
| Component | Licence |
|---|---|
| FastAPI, uvicorn, pydantic | MIT / BSD |
| pypdf | BSD-3-Clause |
| pypdfium2 (bindings) / PDFium | Apache-2.0 / BSD-3-Clause |
| RapidOCR ONNX Runtime | Apache-2.0 |
| ONNX Runtime | MIT |
| Pillow | MIT-CMU |
| python-docx, openpyxl | MIT |
| reportlab | BSD-3-Clause |
| beautifulsoup4, lxml | MIT / BSD |
| markdown-it-py | MIT |
| arabic-reshaper, mammoth | MIT / BSD |
No component is AGPL, no component is commercial, and no document is sent to a third-party conversion service. The optional Mistral OCR path is off unless a key is configured, and the engine says so in the conversion's warnings when it is used.