toolgarden.xyz
中文
browser tool developmentPDF to Wordpdf.jsOpen XMLdocument engineering

How to Convert PDF to Word in the Browser with pdf.js and Open XML

Extract positioned PDF text with pdf.js, rebuild readable lines, preserve page breaks, and generate a minimal editable DOCX package entirely in the browser.

ToolGarden tools prioritize browser-local processing, so files and text do not need to be uploaded to a server.

Published July 22, 202610 min readBy ToolGarden

PDF and Word solve opposite layout problems. PDF stores a fixed visual page; Word stores editable document structure. A browser converter can reliably recover the text layer into a clean DOCX, but it cannot infer every table, column, font, or image relationship from coordinates alone.

This implementation deliberately targets readable, editable text. pdf.js extracts text items and transforms, a coordinate heuristic rebuilds lines, XML escaping protects the document, and fflate packages a minimal Open XML Word file with explicit page breaks.

Define the conversion promise narrowly

A text-layer PDF contains glyph strings and placement transforms, not semantic paragraphs. A scan can contain no text items at all. State that the result is editable extracted text with page boundaries, not a pixel-perfect reconstruction of the source design.

This boundary makes the tool useful for quotes, reports, notes, and copy recovery while avoiding a false promise for complex brochures, forms, equations, and multi-column layouts.

Source featureThis pipeline
Selectable PDF textExtracted into Word paragraphs
Original page boundariesPreserved with page breaks
Scanned image-only pageRequires OCR first
Tables and columnsMay flatten into reading-order lines
Images and exact typographyNot reconstructed

Load pdf.js and extract positioned text

Dynamically import the legacy pdf.js browser build and point GlobalWorkerOptions.workerSrc to its bundled worker. Read the selected File into Uint8Array, load the document with system fonts enabled, and process pages in order.

Each text item provides a string plus a transform. The fifth and sixth transform values act as x and y positions for a lightweight reconstruction. Normalize whitespace and ignore empty or non-text items.

const page = await pdf.getPage(pageNumber);
const content = await page.getTextContent();

const positioned = content.items.flatMap(item => {
  if (!isTextItem(item) || !item.str.trim()) return [];
  return [{
    text: normalizeText(item.str),
    x: Number(item.transform[4] ?? 0),
    y: Number(item.transform[5] ?? 0),
  }];
});

const lines = groupByNearbyY(positioned)
  .map(line => line.sort((a, b) => a.x - b.x))
  .map(line => line.map(item => item.text).join(' '));

Rebuild lines with a coordinate tolerance

Sort items top-to-bottom by descending y and left-to-right by x. Items whose y values are within a small tolerance join the same line, then each line is sorted by x and concatenated. This handles ordinary single-column text without requiring a layout model.

The tolerance is a heuristic. Superscripts, rotated text, vertical writing, columns, tables, and positioned labels can produce unexpected order. A more advanced system needs block segmentation and column detection before line grouping.

  • Limit text items per page to bound pathological inputs.
  • Preserve page order even when one page has no usable text.
  • Reject the conversion when the entire document has zero paragraphs.
  • Return page and paragraph counts so the UI can describe the result.

Generate a minimal valid DOCX package

A DOCX is an Open XML ZIP. The minimal package needs [Content_Types].xml, root relationships, core and application properties, and word/document.xml. Each reconstructed line becomes a w:p paragraph, and each source page after the first begins with a Word page break.

Escape XML control characters, ampersands, angle brackets, quotes, and apostrophes before inserting user text. xml:space="preserve" prevents Word from discarding intended surrounding spaces.

const entries = {
  '[Content_Types].xml': strToU8(contentTypesXml),
  '_rels/.rels': strToU8(packageRelationshipsXml),
  'docProps/core.xml': strToU8(corePropertiesXml),
  'docProps/app.xml': strToU8(appPropertiesXml),
  'word/document.xml': strToU8(buildDocumentXml(pages)),
};

const zipped = zipSync(entries, { level: 6 });
return new Blob([zipped], {
  type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});

Handle scanned PDFs as a separate OCR workflow

If pdf.js returns no usable text, the page may be a scan or the text may be encoded in a way the extractor cannot recover. Silently generating an empty Word file is worse than returning an empty_text error.

A scan-to-Word feature requires rendering each page to an image, running OCR, mapping recognized blocks back into reading order, and then building DOCX. That is a different pipeline with model downloads, image limits, language selection, and lower certainty.

Validate inputs and release output resources

Check empty input, PDF type, and maximum bytes before importing pdf.js. Catch encrypted, damaged, or unsupported PDFs as load failures. The UI should create one object URL for the result and revoke the previous URL when a new file is selected or the component unmounts.

Test output in Word, LibreOffice, and web viewers. A syntactically valid Open XML package can still expose ordering or Unicode problems that only appear in a real document application.

Key takeaways

A focused PDF-to-Word converter can be small and honest: extract positioned text, group lines with a documented heuristic, preserve page breaks, escape XML, and package a minimal DOCX. Scans and high-fidelity layout reconstruction belong to separate, more complex pipelines.

Frequently asked questions

Q.Why does the Word output not look exactly like the PDF?

PDF stores fixed-position page content, while Word needs flowing document structure. This converter prioritizes editable text and page boundaries rather than reconstructing fonts, columns, images, and precise geometry.

Q.Why does a scanned PDF produce no text?

A scan often contains page images without a selectable text layer. pdf.js can render those images but cannot invent text; OCR must recognize the pixels first.

Q.Why build DOCX manually instead of using a large document library?

For a text-only result, the required Open XML package is small and deterministic. A full library becomes valuable when adding styles, tables, images, headers, numbering, and richer layout.

Q.Does the PDF leave the browser?

Not in this conversion path. pdf.js reads the selected bytes in the page and the DOCX is generated as a local Blob. Application and worker assets still need to be delivered to the browser.