# `@sirdaspdf/core` (library)

The runtime-agnostic engine behind the web app, the CLI and the MCP server. Depends only on `pdf-lib` and `fflate`; the PDF.js part receives an **already-opened** document so it runs the same in a browser (PDF.js worker build) and in Node (`pdfjs-dist/legacy/build/pdf.mjs`, no canvas needed).

```ts
import { anonymize, pdfToMarkdown, estimateTokens, parseRanges, mergePdfs, splitPdf, pageCount, organizePdf, compressLossless, classifyError } from "@sirdaspdf/core";
import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";   // Node

const doc = await pdfjs.getDocument({ data: bytes, isEvalSupported: false, verbosity: 0 }).promise;
const markdown = await pdfToMarkdown(doc);      // layout-aware Markdown
await doc.destroy();

const { text, findings } = anonymize(markdown); // consistent placeholders
console.log(estimateTokens(text), findings.length);
```

Subpath imports keep bundles small: `@sirdaspdf/core/<module>` for any module (`anonymize`, `formats`, `extract`, `classify`, `segment`, `forms`, `chunk`, `ranges`…).

## Exports

| Function | Purpose |
|---|---|
| `pdfToMarkdown(doc, onProgress?)` | Full pipeline: `extractLines` → `linesToMarkdown`. |
| `extractLines(doc, onProgress?)` / `linesToMarkdown(lines)` | The two halves, if you want to post-process lines. |
| `anonymize(text)` | `{ text, findings: { type, value, label }[] }`. |
| `estimateTokens(text)` | ~4 characters per token. |
| `parseRanges("1-3, 5, 8-end", pageCount)` | 0-based page groups; throws a user-facing error on bad input. |
| `mergePdfs(buffers)` · `splitPdf(buffer, groups)` · `pageCount(buffer)` · `organizePdf(buffer, edits)` · `compressLossless(buffer)` | pdf-lib operations returning `Uint8Array`. |
| `classifyError(err)` → `"encrypted" \| "corrupt" \| "generic"` · `PdfError` | Turn pdf-lib / PDF.js failures into actionable causes. |
| `convertToMarkdown(bytes, filename)` · `detectFormat` · `htmlToMarkdown` · `parseDelimited` | DOCX, XLSX, PPTX, ODT/ODS/ODP, EPUB, HTML, CSV, EML, RTF, JSON → `{ format, markdown, parts, tables, warnings }`. |
| `classifyDocument(text)` · `extractFields(markdown, type)` | Document type with visible signals; fields with `page` and `evidence`. |
| `chunkForAgents(markdown, maxTokens)` · `contentId(text)` | Chunks with stable `id`, `pages`, `section`. Use `pdfToMarkdown(doc, _, { pageMarkers: true })`. |
| `locateText(lines, text, page?)` | Bounding box (0–1, top-left origin) of a citation across up to three lines. |
| `segmentPages(pageTexts)` | Documents inside a batch PDF, with the reasons for each cut. |
| `readFormFields(bytes)` · `findTextCheckboxes(text)` | AcroForm fields and written checkboxes. |
| `buildRecords` · `toJsonl` · `datasetCard` · `DATASET_FORMATS` | Training/RAG datasets and the Hugging Face card. |
| `protectPdf` · `unlockPdf` · `lockState` | AES-256 via an injected qpdf WebAssembly module. |
| `deletePages` · `extractPages` · `rotatePages` · `addPageNumbers` · `addWatermark` | Page tools. |

ESM, TypeScript types included. Node ≥ 20 or any modern browser.
