# PHP
Extract text, tables, images, and metadata from 90+ file formats and 300+ programming languages including PDF, Office documents, and images. PHP bindings with modern PHP 8.2+ support and type-safe API. ## What This Package Provides - **Document intelligence core** — extract text, tables, images, metadata, entities, keywords, and code intelligence from one API. - **Format coverage** — PDF, Office, images, HTML/XML, email, archives, notebooks, citations, scientific formats, and plain text. - **OCR choices** — Tesseract, PaddleOCR, EasyOCR where supported, VLM OCR through liter-llm, and plugin hooks for custom backends. - **Same engine as every binding** — Rust, Python, Node.js, Go, Java, PHP, Ruby, .NET, Elixir, R, WASM, Kotlin Android, Swift, Dart, Zig, and C FFI share the same Rust implementation. - **PHP package** — PHP 8.2+ API with generated types. ## Installation ### Package Installation Install via Composer: ```bash composer require kreuzberg/kreuzberg ``` ### System Requirements - **PHP 8.2+** required - Optional: [ONNX Runtime](https://github.com/microsoft/onnxruntime/releases) version 1.22.x for embeddings support - Optional: [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) for OCR functionality ## Quick Start ### Basic Extraction Extract text, metadata, and structure from any supported document format: ```php ```php title="basic_extraction_oop.php" extractFile('document.pdf'); echo "Extracted Content:\n"; echo "==================\n"; echo $result->content . "\n\n"; echo "Metadata:\n"; echo "=========\n"; echo "Title: " . ($result->metadata->title ?? 'N/A') . "\n"; echo "Authors: " . (isset($result->metadata->authors) ? implode(', ', $result->metadata->authors) : 'N/A') . "\n"; echo "Pages: " . ($result->metadata->pageCount ?? 'N/A') . "\n"; echo "Format: " . $result->mimeType . "\n\n"; if (count($result->tables) > 0) { echo "Tables Found: " . count($result->tables) . "\n"; foreach ($result->tables as $index => $table) { echo "\nTable " . ($index + 1) . " (Page {$table->pageNumber}):\n"; echo $table->markdown . "\n"; } } ``` ``` ### Common Use Cases #### Extract with Custom Configuration Most use cases benefit from configuration to control extraction behavior: **With OCR (for scanned documents):** ```php ```php title="basic_ocr.php" extractFile('scanned_document.pdf'); echo "OCR Extraction Results:\n"; echo str_repeat('=', 60) . "\n"; echo $result->content . "\n\n"; $multilingualConfig = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: 'eng+fra+deu' ) ); $kreuzberg = new Kreuzberg($multilingualConfig); $result = $kreuzberg->extractFile('multilingual_scan.pdf'); echo "Multilingual OCR:\n"; echo str_repeat('=', 60) . "\n"; echo substr($result->content, 0, 500) . "...\n\n"; $imageConfig = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: 'eng' ) ); $kreuzberg = new Kreuzberg($imageConfig); $imageFormats = ['png', 'jpg', 'tiff']; foreach ($imageFormats as $format) { $file = "scan.$format"; if (file_exists($file)) { echo "Processing $file...\n"; $result = $kreuzberg->extractFile($file); echo "Extracted " . strlen($result->content) . " characters\n"; echo "Preview: " . substr($result->content, 0, 100) . "...\n\n"; } } $languages = [ 'spa' => 'Spanish document', 'fra' => 'French document', 'deu' => 'German document', 'ita' => 'Italian document', 'por' => 'Portuguese document', 'rus' => 'Russian document', 'jpn' => 'Japanese document', 'chi_sim' => 'Chinese (Simplified) document', ]; foreach ($languages as $lang => $description) { $file = strtolower(str_replace(' ', '_', $description)) . '.pdf'; if (file_exists($file)) { $config = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: $lang ) ); $kreuzberg = new Kreuzberg($config); $result = $kreuzberg->extractFile($file); echo "$description ($lang):\n"; echo " Characters extracted: " . mb_strlen($result->content) . "\n\n"; } } use function Kreuzberg\extract_file; $config = new ExtractionConfig( ocr: new OcrConfig(backend: 'tesseract', language: 'eng') ); $result = extract_file('invoice_scan.pdf', config: $config); echo "Invoice OCR:\n"; echo str_repeat('=', 60) . "\n"; echo $result->content . "\n"; $result = $kreuzberg->extractFile('scanned.pdf'); $contentLength = strlen($result->content); $pageCount = $result->metadata->pageCount ?? 1; $avgCharsPerPage = $contentLength / $pageCount; echo "\nOCR Quality Assessment:\n"; echo "Total characters: $contentLength\n"; echo "Pages: $pageCount\n"; echo "Average chars/page: " . number_format($avgCharsPerPage) . "\n"; if ($avgCharsPerPage < 100) { echo "Warning: Low character count may indicate poor scan quality\n"; echo "Consider using image preprocessing or higher DPI settings.\n"; } elseif ($avgCharsPerPage > 2000) { echo "Pass: Good - Adequate text extracted\n"; } else { echo "Pass: Moderate - Text extracted successfully\n"; } ``` ``` #### Table Extraction See [Configuration Guide](https://docs.kreuzberg.dev/guides/configuration/) for table extraction options. #### Processing Multiple Files ```php ```php title="batch_processing.php" $result) { $filename = basename($files[$index]); echo "$filename:\n"; echo " Content: " . strlen($result->content) . " chars\n"; echo " Tables: " . count($result->tables) . "\n"; echo " MIME: " . $result->mimeType . "\n\n"; } } $config = new ExtractionConfig( extractTables: true, extractImages: false ); $kreuzberg = new Kreuzberg($config); $pdfFiles = glob('*.pdf'); if (!empty($pdfFiles)) { echo "Processing " . count($pdfFiles) . " PDF files...\n"; $start = microtime(true); $results = $kreuzberg->batchExtractFiles($pdfFiles, $config); $elapsed = microtime(true) - $start; echo "Completed in " . number_format($elapsed, 2) . " seconds\n"; echo "Throughput: " . number_format(count($pdfFiles) / $elapsed, 2) . " files/second\n\n"; $totalChars = 0; $totalTables = 0; foreach ($results as $result) { $totalChars += strlen($result->content); $totalTables += count($result->tables); } echo "Total content: " . number_format($totalChars) . " characters\n"; echo "Total tables: $totalTables\n"; } $uploadedFiles = [ ['data' => file_get_contents('file1.pdf'), 'mime' => 'application/pdf'], ['data' => file_get_contents('file2.docx'), 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], ]; $dataList = array_column($uploadedFiles, 'data'); $mimeTypes = array_column($uploadedFiles, 'mime'); $results = batch_extract_bytes($dataList, $mimeTypes); echo "\nProcessed " . count($results) . " files from memory\n"; function processDirectory(string $dir, Kreuzberg $kreuzberg): array { $results = []; $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir) ); $files = []; foreach ($iterator as $file) { if ($file->isFile()) { $ext = strtolower($file->getExtension()); if (in_array($ext, ['pdf', 'docx', 'xlsx', 'pptx', 'txt'], true)) { $files[] = $file->getPathname(); } } } if (empty($files)) { return $results; } $batches = array_chunk($files, 10); foreach ($batches as $batchIndex => $batch) { echo "Processing batch " . ($batchIndex + 1) . "/" . count($batches) . "...\n"; $batchResults = $kreuzberg->batchExtractFiles($batch); $results = array_merge($results, $batchResults); } return $results; } $directory = './documents'; if (is_dir($directory)) { echo "\nProcessing directory: $directory\n"; $results = processDirectory($directory, $kreuzberg); echo "Processed " . count($results) . " files\n"; } $mixedFiles = ['valid.pdf', 'nonexistent.pdf', 'another.docx']; try { $results = batch_extract_files($mixedFiles); } catch (\Kreuzberg\Exceptions\KreuzbergException $e) { echo "Batch processing error: " . $e->getMessage() . "\n"; } $allFiles = glob('documents/*.{pdf,docx,xlsx}', GLOB_BRACE); $batchSize = 5; $batches = array_chunk($allFiles, $batchSize); $totalProcessed = 0; echo "\nProcessing " . count($allFiles) . " files in " . count($batches) . " batches...\n"; foreach ($batches as $index => $batch) { $progress = (($index + 1) / count($batches)) * 100; echo sprintf("\rProgress: %.1f%% [%d/%d batches]", $progress, $index + 1, count($batches)); $results = $kreuzberg->batchExtractFiles($batch); $totalProcessed += count($results); } echo "\n\nCompleted! Processed $totalProcessed files.\n"; ``` ``` ### Next Steps - **[Installation Guide](https://docs.kreuzberg.dev/getting-started/installation/)** - Platform-specific setup - **[API Documentation](https://docs.kreuzberg.dev/reference/api-python/)** - Complete API reference - **[Examples & Guides](https://docs.kreuzberg.dev/)** - Full code examples and usage guides - **[Configuration Guide](https://docs.kreuzberg.dev/guides/configuration/)** - Advanced configuration options ## Features ### Supported File Formats (90+) 90+ file formats across 8 major categories with intelligent format detection and comprehensive metadata extraction. #### Office Documents | Category | Formats | Capabilities | |----------|---------|--------------| | **Word Processing** | `.docx`, `.docm`, `.dotx`, `.dotm`, `.dot`, `.odt` | Full text, tables, images, metadata, styles | | **Spreadsheets** | `.xlsx`, `.xlsm`, `.xlsb`, `.xls`, `.xla`, `.xlam`, `.xltm`, `.xltx`, `.xlt`, `.ods` | Sheet data, formulas, cell metadata, charts | | **Presentations** | `.pptx`, `.pptm`, `.ppsx`, `.potx`, `.potm`, `.pot`, `.ppt` | Slides, speaker notes, images, metadata | | **PDF** | `.pdf` | Text, tables, images, metadata, OCR support | | **eBooks** | `.epub`, `.fb2` | Chapters, metadata, embedded resources | | **Database** | `.dbf` | Table data extraction, field type support | | **Hangul** | `.hwp`, `.hwpx` | Korean document format, text extraction | #### Images (OCR-Enabled) | Category | Formats | Features | |----------|---------|----------| | **Raster** | `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`, `.tiff`, `.tif` | OCR, table detection, EXIF metadata, dimensions, color space | | **Advanced** | `.jp2`, `.jpx`, `.jpm`, `.mj2`, `.jbig2`, `.jb2`, `.pnm`, `.pbm`, `.pgm`, `.ppm` | OCR via hayro-jpeg2000 (pure Rust decoder), JBIG2 support, table detection, format-specific metadata | | **Vector** | `.svg` | DOM parsing, embedded text, graphics metadata | #### Web & Data | Category | Formats | Features | |----------|---------|----------| | **Markup** | `.html`, `.htm`, `.xhtml`, `.xml`, `.svg` | DOM parsing, metadata (Open Graph, Twitter Card), link extraction | | **Structured Data** | `.json`, `.yaml`, `.yml`, `.toml`, `.csv`, `.tsv` | Schema detection, nested structures, validation | | **Text & Markdown** | `.txt`, `.md`, `.markdown`, `.djot`, `.rst`, `.org`, `.rtf` | CommonMark, GFM, Djot, reStructuredText, Org Mode | #### Email & Archives | Category | Formats | Features | |----------|---------|----------| | **Email** | `.eml`, `.msg` | Headers, body (HTML/plain), attachments, threading | | **Archives** | `.zip`, `.tar`, `.tgz`, `.gz`, `.7z` | File listing, nested archives, metadata | #### Academic & Scientific | Category | Formats | Features | |----------|---------|----------| | **Citations** | `.bib`, `.biblatex`, `.ris`, `.nbib`, `.enw`, `.csl` | Structured parsing: RIS (structured), PubMed/MEDLINE, EndNote XML (structured), BibTeX, CSL JSON | | **Scientific** | `.tex`, `.latex`, `.typst`, `.jats`, `.ipynb`, `.docbook` | LaTeX, Jupyter notebooks, PubMed JATS | | **Documentation** | `.opml`, `.pod`, `.mdoc`, `.troff` | Technical documentation formats | #### Code Intelligence (300+ Languages) | Feature | Description | |---------|-------------| | **Structure Extraction** | Functions, classes, methods, structs, interfaces, enums | | **Import/Export Analysis** | Module dependencies, re-exports, wildcard imports | | **Symbol Extraction** | Variables, constants, type aliases, properties | | **Docstring Parsing** | Google, NumPy, Sphinx, JSDoc, RustDoc, and 10+ formats | | **Diagnostics** | Parse errors with line/column positions | | **Syntax-Aware Chunking** | Split code by semantic boundaries, not arbitrary byte offsets | Powered by [tree-sitter-language-pack](https://github.com/kreuzberg-dev/tree-sitter-language-pack) — [documentation](https://docs.tree-sitter-language-pack.kreuzberg.dev). **[Complete Format Reference](https://docs.kreuzberg.dev/reference/formats/)** ### Key Capabilities - **Text Extraction** - Extract all text content with position and formatting information - **Metadata Extraction** - Retrieve document properties, creation date, author, etc. - **Table Extraction** - Parse tables with structure and cell content preservation - **Image Extraction** - Extract embedded images and render page previews - **OCR Support** - Integrate multiple OCR backends for scanned documents - **Plugin System** - Extensible post-processing for custom text transformation - **Embeddings** - Generate vector embeddings using ONNX Runtime models - **Batch Processing** - Efficiently process multiple documents in parallel - **Memory Efficient** - Stream large files without loading entirely into memory - **Language Detection** - Detect and support multiple languages in documents - **Code Intelligence** - Extract structure, imports, exports, symbols, and docstrings from [300+ programming languages](https://docs.tree-sitter-language-pack.kreuzberg.dev) via tree-sitter - **Configuration** - Fine-grained control over extraction behavior ### Performance Characteristics | Format | Speed | Memory | Notes | |--------|-------|--------|-------| | **PDF (text)** | 10-100 MB/s | ~50MB per doc | Fastest extraction | | **Office docs** | 20-200 MB/s | ~100MB per doc | DOCX, XLSX, PPTX | | **Images (OCR)** | 1-5 MB/s | Variable | Depends on OCR backend | | **Archives** | 5-50 MB/s | ~200MB per doc | ZIP, TAR, etc. | | **Web formats** | 50-200 MB/s | Streaming | HTML, XML, JSON | ## OCR Support Kreuzberg supports multiple OCR backends for extracting text from scanned documents and images: - **Tesseract** - **Paddleocr** ### OCR Configuration Example ```php ```php title="basic_ocr.php" extractFile('scanned_document.pdf'); echo "OCR Extraction Results:\n"; echo str_repeat('=', 60) . "\n"; echo $result->content . "\n\n"; $multilingualConfig = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: 'eng+fra+deu' ) ); $kreuzberg = new Kreuzberg($multilingualConfig); $result = $kreuzberg->extractFile('multilingual_scan.pdf'); echo "Multilingual OCR:\n"; echo str_repeat('=', 60) . "\n"; echo substr($result->content, 0, 500) . "...\n\n"; $imageConfig = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: 'eng' ) ); $kreuzberg = new Kreuzberg($imageConfig); $imageFormats = ['png', 'jpg', 'tiff']; foreach ($imageFormats as $format) { $file = "scan.$format"; if (file_exists($file)) { echo "Processing $file...\n"; $result = $kreuzberg->extractFile($file); echo "Extracted " . strlen($result->content) . " characters\n"; echo "Preview: " . substr($result->content, 0, 100) . "...\n\n"; } } $languages = [ 'spa' => 'Spanish document', 'fra' => 'French document', 'deu' => 'German document', 'ita' => 'Italian document', 'por' => 'Portuguese document', 'rus' => 'Russian document', 'jpn' => 'Japanese document', 'chi_sim' => 'Chinese (Simplified) document', ]; foreach ($languages as $lang => $description) { $file = strtolower(str_replace(' ', '_', $description)) . '.pdf'; if (file_exists($file)) { $config = new ExtractionConfig( ocr: new OcrConfig( backend: 'tesseract', language: $lang ) ); $kreuzberg = new Kreuzberg($config); $result = $kreuzberg->extractFile($file); echo "$description ($lang):\n"; echo " Characters extracted: " . mb_strlen($result->content) . "\n\n"; } } use function Kreuzberg\extract_file; $config = new ExtractionConfig( ocr: new OcrConfig(backend: 'tesseract', language: 'eng') ); $result = extract_file('invoice_scan.pdf', config: $config); echo "Invoice OCR:\n"; echo str_repeat('=', 60) . "\n"; echo $result->content . "\n"; $result = $kreuzberg->extractFile('scanned.pdf'); $contentLength = strlen($result->content); $pageCount = $result->metadata->pageCount ?? 1; $avgCharsPerPage = $contentLength / $pageCount; echo "\nOCR Quality Assessment:\n"; echo "Total characters: $contentLength\n"; echo "Pages: $pageCount\n"; echo "Average chars/page: " . number_format($avgCharsPerPage) . "\n"; if ($avgCharsPerPage < 100) { echo "Warning: Low character count may indicate poor scan quality\n"; echo "Consider using image preprocessing or higher DPI settings.\n"; } elseif ($avgCharsPerPage > 2000) { echo "Pass: Good - Adequate text extracted\n"; } else { echo "Pass: Moderate - Text extracted successfully\n"; } ``` ``` ## Plugin System Kreuzberg supports extensible post-processing plugins for custom text transformation and filtering. For detailed plugin documentation, visit [Plugin System Guide](https://docs.kreuzberg.dev/guides/plugins/). ## Embeddings Support Generate vector embeddings for extracted text using the built-in ONNX Runtime support. Requires ONNX Runtime installation. **[Embeddings Guide](https://docs.kreuzberg.dev/features/#embeddings)** ## Batch Processing Process multiple documents efficiently: ```php ```php title="batch_processing.php" $result) { $filename = basename($files[$index]); echo "$filename:\n"; echo " Content: " . strlen($result->content) . " chars\n"; echo " Tables: " . count($result->tables) . "\n"; echo " MIME: " . $result->mimeType . "\n\n"; } } $config = new ExtractionConfig( extractTables: true, extractImages: false ); $kreuzberg = new Kreuzberg($config); $pdfFiles = glob('*.pdf'); if (!empty($pdfFiles)) { echo "Processing " . count($pdfFiles) . " PDF files...\n"; $start = microtime(true); $results = $kreuzberg->batchExtractFiles($pdfFiles, $config); $elapsed = microtime(true) - $start; echo "Completed in " . number_format($elapsed, 2) . " seconds\n"; echo "Throughput: " . number_format(count($pdfFiles) / $elapsed, 2) . " files/second\n\n"; $totalChars = 0; $totalTables = 0; foreach ($results as $result) { $totalChars += strlen($result->content); $totalTables += count($result->tables); } echo "Total content: " . number_format($totalChars) . " characters\n"; echo "Total tables: $totalTables\n"; } $uploadedFiles = [ ['data' => file_get_contents('file1.pdf'), 'mime' => 'application/pdf'], ['data' => file_get_contents('file2.docx'), 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], ]; $dataList = array_column($uploadedFiles, 'data'); $mimeTypes = array_column($uploadedFiles, 'mime'); $results = batch_extract_bytes($dataList, $mimeTypes); echo "\nProcessed " . count($results) . " files from memory\n"; function processDirectory(string $dir, Kreuzberg $kreuzberg): array { $results = []; $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir) ); $files = []; foreach ($iterator as $file) { if ($file->isFile()) { $ext = strtolower($file->getExtension()); if (in_array($ext, ['pdf', 'docx', 'xlsx', 'pptx', 'txt'], true)) { $files[] = $file->getPathname(); } } } if (empty($files)) { return $results; } $batches = array_chunk($files, 10); foreach ($batches as $batchIndex => $batch) { echo "Processing batch " . ($batchIndex + 1) . "/" . count($batches) . "...\n"; $batchResults = $kreuzberg->batchExtractFiles($batch); $results = array_merge($results, $batchResults); } return $results; } $directory = './documents'; if (is_dir($directory)) { echo "\nProcessing directory: $directory\n"; $results = processDirectory($directory, $kreuzberg); echo "Processed " . count($results) . " files\n"; } $mixedFiles = ['valid.pdf', 'nonexistent.pdf', 'another.docx']; try { $results = batch_extract_files($mixedFiles); } catch (\Kreuzberg\Exceptions\KreuzbergException $e) { echo "Batch processing error: " . $e->getMessage() . "\n"; } $allFiles = glob('documents/*.{pdf,docx,xlsx}', GLOB_BRACE); $batchSize = 5; $batches = array_chunk($allFiles, $batchSize); $totalProcessed = 0; echo "\nProcessing " . count($allFiles) . " files in " . count($batches) . " batches...\n"; foreach ($batches as $index => $batch) { $progress = (($index + 1) / count($batches)) * 100; echo sprintf("\rProgress: %.1f%% [%d/%d batches]", $progress, $index + 1, count($batches)); $results = $kreuzberg->batchExtractFiles($batch); $totalProcessed += count($results); } echo "\n\nCompleted! Processed $totalProcessed files.\n"; ``` ``` ## Configuration For advanced configuration options including language detection, table extraction, OCR settings, and more: **[Configuration Guide](https://docs.kreuzberg.dev/guides/configuration/)** ## Documentation - **[Official Documentation](https://docs.kreuzberg.dev/)** - **[API Reference](https://docs.kreuzberg.dev/reference/api-python/)** - **[Examples & Guides](https://docs.kreuzberg.dev/)** ## Contributing Contributions are welcome! See [Contributing Guide](https://github.com/kreuzberg-dev/kreuzberg/blob/main/CONTRIBUTING.md). ## Part of Kreuzberg.dev - [Kreuzberg Cloud](https://github.com/kreuzberg-dev/kreuzberg-cloud) — managed extraction API with SDKs, dashboards, and observability. - [kreuzcrawl](https://github.com/kreuzberg-dev/kreuzcrawl) — web crawling and scraping with HTML→Markdown and headless-Chrome fallback. - [html-to-markdown](https://github.com/kreuzberg-dev/html-to-markdown) — fast, lossless HTML→Markdown engine. - [liter-llm](https://github.com/kreuzberg-dev/liter-llm) — universal LLM API client with native bindings for 14 languages and 143 providers. - [tree-sitter-language-pack](https://github.com/kreuzberg-dev/tree-sitter-language-pack) — tree-sitter grammars and code-intelligence primitives. - [alef](https://github.com/kreuzberg-dev/alef) — the polyglot binding generator that produces this README and all per-language bindings. - [Discord](https://discord.gg/xt9WY3GnKR) — community, roadmap, announcements. ## License Elastic-2.0 License — see [LICENSE](../../LICENSE) for details. ## Support - **Discord Community**: [Join our Discord](https://discord.gg/xt9WY3GnKR) - **GitHub Issues**: [Report bugs](https://github.com/kreuzberg-dev/kreuzberg/issues) - **Discussions**: [Ask questions](https://github.com/kreuzberg-dev/kreuzberg/discussions)