---
title: "Convert DOCX, EPUB, Markdown, and HTML to PDF in Node.js"
canonical_url: "https://www.nutrient.io/blog/pdfjs-file-format-conversion-to-pdf/"
md_url: "https://www.nutrient.io/blog/pdfjs-file-format-conversion-to-pdf.md"
last_updated: "2026-08-11T20:47:43.241Z"
description: "Convert DOCX, EPUB, Markdown, and HTML to PDF in Node.js with LibreOffice, Calibre, md-to-pdf, Puppeteer, and ocrmypdf for scanned documents."
---

**TL;DR**

- LibreOffice (via `libreoffice-convert`) handles DOCX, XLSX, PPTX, RTF, TXT, and other Office formats.

- Calibre’s `ebook-convert` handles EPUB, MOBI, and AZW3; `md-to-pdf` handles Markdown; Puppeteer’s `page.pdf()` handles HTML/webpages.

- After conversion, run `ocrmypdf` over scanned pages to make them searchable. Pipe everything together in one upload handler.

## Prerequisites

This guide is server-side — you’ll need Node.js and the ability to install system packages. The pipeline reads/writes temporary files, so the process needs write access to `os.tmpdir()`.

You’ll also need:

- Node.js 18 or later

- LibreOffice, Calibre, and ocrmypdf installed (see each tool’s section below)

- The npm packages `libreoffice-convert`, `md-to-pdf`, `puppeteer`, and `uuid`

A PDF viewer only handles PDFs — but users upload Word documents, EPUBs, PowerPoints, and more. This guide covers the ecosystem of tools for converting different formats to PDF on the server.

## Overview of conversion tools

| Format                     | Tool                      | Type        |
| -------------------------- | ------------------------- | ----------- |
| DOCX, XLSX, PPTX, RTF, TXT | LibreOffice (headless)    | System CLI  |
| EPUB, MOBI, AZW3           | Calibre (`ebook-convert`) | System CLI  |
| Markdown                   | `md-to-pdf`               | npm package |
| HTML/webpages              | Puppeteer (`page.pdf()`)  | npm package |

## LibreOffice: Office documents to PDF

LibreOffice handles the widest range of Office formats through a single headless CLI call.

### Setup

Install LibreOffice on the target platform:

```bash

# macOS

brew install --cask libreoffice

# Ubuntu/Debian

sudo apt-get install libreoffice

# Docker

FROM node:20
RUN apt-get update && apt-get install -y libreoffice

```

### Usage with libreoffice-convert

Install the npm wrapper. Then convert a buffer to PDF:

```bash

npm install libreoffice-convert

```

```tsx

import libre from "libreoffice-convert";
import { promisify } from "util";

const convertAsync = promisify(libre.convert);

async function officeToPdf(inputBuffer) {
  const pdfBuffer = await convertAsync(inputBuffer, ".pdf", undefined);
  return pdfBuffer;
}

```

Supported input formats include: `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.odt`, `.ods`, `.odp`, `.rtf`, and `.txt`.

## Calibre: eBooks to PDF

Calibre’s `ebook-convert` CLI handles eBook formats that LibreOffice doesn’t, and it can also extract metadata from eBooks and PDFs.

### Setup

Install Calibre on the target platform:

```bash

# macOS

brew install --cask calibre

# Ubuntu/Debian

sudo apt-get install calibre

```

### Usage

Convert an eBook buffer to PDF by shelling out to Calibre’s `ebook-convert` CLI:

```tsx

import { exec } from "child_process";
import { promisify } from "util";
import { writeFile, readFile, unlink } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { v4 as uuid } from "uuid";

const execAsync = promisify(exec);

async function ebookToPdf(inputBuffer, inputExtension) {
  const inputPath = join(tmpdir(), `${uuid()}${inputExtension}`);
  const outputPath = join(tmpdir(), `${uuid()}.pdf`);

  try {
    await writeFile(inputPath, inputBuffer);
    await execAsync(`ebook-convert "${inputPath}" "${outputPath}"`);
    return await readFile(outputPath);
  } finally {
    await unlink(inputPath).catch(() => {});
    await unlink(outputPath).catch(() => {});
  }
}

```

### Extracting metadata

Calibre also extracts metadata from eBooks and PDFs:

```tsx

async function getMetadata(inputBuffer, mimeType) {
  const ext = mimeToExtension(mimeType); // e.g. ".epub", ".pdf".
  const inputPath = join(tmpdir(), `${uuid()}${ext}`);

  try {
    await writeFile(inputPath, inputBuffer);
    const { stdout } = await execAsync(`ebook-meta "${inputPath}"`);
    return parseMetadataOutput(stdout);
  } finally {
    await unlink(inputPath).catch(() => {});
  }
}

```

## md-to-pdf: Markdown to PDF

Install the package:

```bash

npm install md-to-pdf

```

```tsx

import { mdToPdf } from "md-to-pdf";

async function markdownToPdf(markdownBuffer) {
  const markdownString = markdownBuffer.toString("utf-8");
  const result = await mdToPdf({ content: markdownString });
  return result?.content; // PDF as `Buffer`.
}

```

`md-to-pdf` uses Puppeteer under the hood — it renders the Markdown as HTML and prints to PDF.

## Puppeteer: Webpages to PDF

Install Puppeteer:

```bash

npm install puppeteer

```

```tsx

import puppeteer from "puppeteer";

async function webPageToPdf(url) {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto(url, { waitUntil: "networkidle2" });

  const pdfBuffer = await page.pdf({
    format: "A4",
    printBackground: true,
    margin: {
      top: "20mm",
      right: "20mm",
      bottom: "20mm",
      left: "20mm",
    },
  });

  await browser.close();
  return pdfBuffer;
}

```

### Puppeteer tips

- Use `waitUntil: "networkidle2"` to wait for dynamic content.

- Set `printBackground: true` to include background colors/images.

- Consider ad blocking and cookie consent handling for web scraping use cases.

## OCR: Making scanned PDFs searchable

After detecting that a PDF needs OCR (see the [server-side text extraction tutorial](https://www.nutrient.io/blog/pdfjs-server-side-text-extraction.md)), use `ocrmypdf`:

```bash

# Install

sudo apt-get install ocrmypdf

# macOS

brew install ocrmypdf

```

```tsx

async function ocrPdf(pdfBuffer, language = "eng") {
  const inputPath = join(tmpdir(), `${uuid()}.pdf`);
  const outputPath = join(tmpdir(), `${uuid()}.pdf`);

  try {
    await writeFile(inputPath, pdfBuffer);
    await execAsync(
      `ocrmypdf -l ${language} --skip-text --output-type pdf -q "${inputPath}" "${outputPath}"`,
    );
    return await readFile(outputPath);
  } finally {
    await unlink(inputPath).catch(() => {});
    await unlink(outputPath).catch(() => {});
  }
}

```

Options:

- `-l eng` — OCR language.

- `--skip-text` — Skip pages that already have text (don’t re-OCR).

- `--output-type pdf` — Output standard PDF (not PDF/A).

- `-q` — Quiet mode.

## Complete processing pipeline

The pipeline below ties everything together. The helpers `isOfficeFormat`, `isEbookFormat`, `mimeToExtension`, `checkPdf`, `generatePreview`, and `extractText` are reader-implemented — `checkPdf` and `extractText` are covered in [server-side text extraction](https://www.nutrient.io/blog/pdfjs-server-side-text-extraction.md), and `generatePreview` can be built with [pdf2pic](https://www.nutrient.io/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md).

```tsx

async function processUpload(buffer, filename, mimeType) {
  let pdfBuffer;

  // Step 1: Convert to PDF if needed.
  if (mimeType === "application/pdf") {
    pdfBuffer = buffer;
  } else if (isOfficeFormat(mimeType)) {
    pdfBuffer = await officeToPdf(buffer);
  } else if (isEbookFormat(mimeType)) {
    pdfBuffer = await ebookToPdf(buffer, mimeToExtension(mimeType));
  } else if (mimeType === "text/markdown") {
    pdfBuffer = await markdownToPdf(buffer);
  }

  // Step 2: Check if OCR is needed.
  const { pages, needsOcr } = await checkPdf(pdfBuffer);

  // Step 3: Run OCR if needed.
  if (needsOcr) {
    pdfBuffer = await ocrPdf(pdfBuffer);
  }

  // Step 4: Generate preview thumbnail.
  const preview = await generatePreview(pdfBuffer);

  // Step 5: Extract text for indexing.
  const text = await extractText(pdfBuffer);

  return { pdfBuffer, preview, text, pages };
}

```

## Key points

- LibreOffice handles most Office formats but requires a system installation.

- Calibre handles eBook formats (EPUB, MOBI) and metadata extraction.

- `md-to-pdf` is the simplest Markdown converter (uses Puppeteer internally).

- Puppeteer’s `page.pdf()` captures any webpage as a perfectly rendered PDF.

- `ocrmypdf` adds searchable text to scanned PDFs without changing the visual content.

- Always clean up temporary files in `finally` blocks.

- Most CLI tools need temporary file I/O — write buffer to disk, convert, read result, delete both.

## How Nutrient Web SDK handles conversion and OCR

Nutrient Web SDK converts Office documents to PDF directly in the browser and runs OCR on scanned PDFs through the same instance — no LibreOffice, Calibre, Puppeteer, or temporary file plumbing on the server is necessary.

```js

const instance = await NutrientViewer.load({
  container: "#pdf-container",

  document: "report.docx", // Word, Excel, and PowerPoint are all supported.
});

await instance.applyOperations([
  { type: "performOcr", language: "english", pageIndexes: "all" },
]);

```

Supported input formats include Word, Excel, PowerPoint, TIFF, JPG, and PNG — all converted client-side without an MS Office license. For server-side workflows, [Nutrient Document Engine](https://www.nutrient.io/sdk/document-engine/) exposes the same conversion and OCR through a REST API.

[Learn more about Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) | [Conversion guide](https://www.nutrient.io/guides/web/conversion.md) | [OCR guide](https://www.nutrient.io/guides/web/ocr.md) | [Migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) | [Contact Sales](https://www.nutrient.io/contact-sales/?=sdk)

## FAQ

#### Why not run LibreOffice from a CDN or browser?

LibreOffice is a desktop application that needs file-system access, a Java runtime, and ~300 MB of disk space. There’s no browser-shippable version. Conversion has to happen on a server (or in a desktop/Electron app); the browser-only options for Office documents are commercial libraries or REST APIs.

#### What’s the difference between <code>--skip-text</code> and <code>--force-ocr</code> in <code>ocrmypdf</code>?

`--skip-text` leaves pages that already contain a text layer alone — safe for mixed batches. `--force-ocr` re-OCRs every page, overwriting any existing text layer. Use `--skip-text` by default; switch to `--force-ocr` only when you suspect the existing text layer is wrong (poor, old OCR or corrupted text). They’re mutually exclusive.

#### How do I detect whether a PDF needs OCR?

Extract a text sample from the first few pages with `pdfDocument.getPage(i).getTextContent()`. If the total characters are below a threshold (say 50 across the sampled pages), the PDF is likely image-only and needs OCR. See the [server-side text extraction post](https://www.nutrient.io/blog/pdfjs-server-side-text-extraction.md) for a full implementation.

#### What’s <code>headless: true</code> actually doing under the hood?

In current Puppeteer (v22+), `headless: true` runs Chromium’s native headless mode, which renders pages closer to what a real user would see in a visible browser — important for PDFs of complex layouts. The legacy `chrome-headless-shell` binary is still available via `headless: 'shell'` for faster startup at the cost of fidelity. The `"new"` string used in older guides has been removed.

#### Can I run LibreOffice and Calibre in a Docker container?

Yes. The example `FROM node:20` Dockerfile shows the LibreOffice setup. For Calibre, install with `apt-get install -y calibre` — note the image will be ~1 GB larger. For production, use a multistage build that copies only the binaries needed, or split conversion into a dedicated microservice.

#### What about converting PDF to other formats?

Reverse conversion (PDF to DOCX, HTML, images) is harder to do well because text layout reconstruction is brittle. LibreOffice supports PDF → DOCX with mixed results; Calibre handles PDF → EPUB. For high-fidelity output, commercial tools (including Nutrient’s [conversion API](https://www.nutrient.io/guides/web/conversion.md)) generally beat the open source options.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Pdf Data Extraction Developer Guide](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

