Developers working on document automation or digital archiving often face the task of extracting structured text from a PDF and exporting it as a Word document. PDFs differ from Word files by storing text as individual glyphs positioned on a page, without built-in awareness of paragraphs or tables. Every PDF-to-Word converter must analyze layout, group glyphs into lines, and reconstruct document structure before exporting to .docx format.
Understanding PDF Structure and Extraction Challenges
PDF files lack explicit objects for paragraphs or tables. Instead, they contain glyphs—individual characters—with precise x and y coordinates, along with rectangles and lines. For instance, a one-page notice might have 327 glyphs, with 277 non-space characters arranged in 16 lines. No metadata marks titles or headings. Font size and position offer the only clues. Some PDFs even omit spaces, as certain generators place each word separately without inserting space characters. Libraries like PyMuPDF try to infer spaces by measuring gaps between glyphs, but this method is heuristic and not foolproof.
PDF text extraction centers on layout reconstruction because extracted text may appear as blocks with bounding boxes, not as guaranteed paragraphs or ordered sections. PyMuPDF’s block output includes coordinates, text, block numbers, and block types, letting applications inspect approximate paragraph regions instead of relying on a flat text stream. The PyMuPDF documentation notes, "PyMuPDF extracts blocks that are approximately paragraphs, but the PDF format does not guarantee that these units correspond to the structure the document's author intended." Multi-column pages, full-width headings, abstracts, tables, and figures can cause simple top-to-bottom extraction to mix unrelated content, so reading order must be inferred from spatial relationships. Untagged PDF streams lack a concept of sentence or paragraph, and image-only pages without OCR may return little or no usable text (MA-NO technical analysis).
PyMuPDF exposes text blocks as tuples containing the block’s x0, y0, x1, and y1 coordinates, extracted text, block number, and block type. Text blocks are often filtered by block type so text regions can be separated from image or drawing content before reconstruction. The library’s sort=True option requests top-to-bottom and left-to-right ordering, but this only aids ordering and does not guarantee correct sequence on complex layouts. The PyMuPDF project maintainers state, "sort=True asks PyMuPDF to reorder from top to bottom and left to right, but it does not by itself guarantee the correct sequence on every complex page." A production converter needs validation rules for columns, spanning elements, tables, and unusual margins, rather than assuming coordinate sorting alone will reproduce the source document.
Multi-column documents need more than a static page midpoint because a page may include a full-width title, abstract, table, or figure between column regions. A robust reconstruction strategy identifies blocks spanning most of the page width, divides the page into vertical reading bands, and processes blocks within each band from top to bottom. Static midpoint splitting can misclassify content when margins differ, when pages use three columns, or when wide elements interrupt the column structure. The root issue is that untagged PDF content provides positional drawing instructions, not a universally reliable semantic reading order, as KolmoPDF technical authors explain.
Reconstructing Document Structure with Python
To convert a PDF to a Word document programmatically, layout analysis and grouping lines into meaningful structures are required. The following approach uses PyMuPDF to extract text and python-docx to build the Word file. The process involves:
- Extracting lines of text with their positions and font sizes.
- Determining the most common font size to identify body text.
- Using larger font sizes to detect headings.
- Grouping lines with the same y-coordinate and significant x-gap as table rows.
- Joining consecutive lines into paragraphs based on spacing and font size.
Here is a simplified Python implementation that demonstrates this process:
import collections, statistics, pymupdf, docx
def is_cjk(ch):
return " " <= ch <= "鿿" or "\uff00" <= ch <= "\uffef"
def join(prev, nxt):
return nxt if is_cjk(prev[-1]) or is_cjk(nxt[0]) else " " + nxt
def read_lines(page):
out = []
for b in page.get_text("dict")["blocks"]:
for ln in b.get("lines", []):
spans = [s for s in ln["spans"] if s["text"].strip()]
if spans:
out.append({"text": "".join(s["text"] for s in ln["spans"]).strip(), "x": ln["bbox"][0], "y": ln["bbox"][1], "size": round(spans[0]["size"], 1)})
return sorted(out, key=lambda l: (round(l["y"]), l["x"]))
def rebuild(pdf_path, out_path):
lines = read_lines(pymupdf.open(pdf_path)[0])
body = statistics.mode([l["size"] for l in lines])
rows = collections.defaultdict(list)
for l in lines:
rows[round(l["y"])] .append(l)
doc, para, prev, table = docx.Document(), None, None, None
for y in sorted(rows):
cells = sorted(rows[y], key=lambda l: l["x"])
if len(cells) >= 2:
if table is None:
table = doc.add_table(rows=0, cols=2)
r = table.add_row().cells
r[0].text, r[1].text = cells[0]["text"], cells[1]["text"]
para = prev = None
continue
l, table = cells[0], None
if l["size"] > body * 1.2:
doc.add_heading(l["text"], level=1 if l["size"] >= body * 1.8 else 2)
para = None
elif para and l["y"] - prev["y"] < 2.0 * l["size"] and l["size"] == prev["size"]:
para.add_run(join(para.text, l["text"]))
else:
para = doc.add_paragraph(l["text"])
prev = l
doc.save(out_path)This script reads the first page of a PDF, analyzes text positions and sizes, and writes a Word document with paragraphs, headings, and tables reconstructed from the layout. The approach works for both English and Japanese documents, handling script-specific line joining and table detection.
PyMuPDF’s block extraction can serve as the intermediate representation for a PDF-to-Word pipeline, with each block retaining its page coordinates and text content. Applications can discard non-text blocks, detect page-spanning regions, and apply layout-specific ordering before generating paragraphs in a DOCX file. This approach is more inspectable than immediately joining all extracted strings because developers can review the geometric evidence behind each ordering decision. However, the output remains heuristic when the original PDF lacks tags or explicit structural metadata, so visual comparison and exception handling are necessary for high-fidelity conversion.
Performance and Limitations
On an Apple M1 running Python 3.12, MuPDF 1.28.6, and python-docx 1.2.0, the conversion takes about 17 milliseconds per page for the tested English and Japanese notices. The output matches the structure produced by the osFoundry desktop app's PDF Editor export, with correct headings, paragraphs, and table rows.
This method has some limitations. Scanned PDFs containing only images require OCR before any text extraction is possible. The pypdf documentation warns that if a page contains only an image, extraction can be minimal or empty; pypdf is not OCR software. PyMuPDF release information also points to ongoing development around OCR-related functionality, showing that scanned-document handling remains an active technical concern. A converter intended for archival or business use should detect empty or suspiciously sparse extraction and route those pages to OCR instead of silently producing an incomplete Word document.
Pages with two columns of body text may be misinterpreted as tables, since the heuristic for table detection relies on lines sharing a baseline with a large x-gap. Multi-column documents need more than a static page midpoint because a page may include a full-width title, abstract, table, or figure between column regions. Documents with wide margins, three-column layouts, or mid-page spanning elements can defeat a simplistic two-column algorithm and require dynamic gutter detection or manual review. The resulting Word file should be presented as a reconstructed approximation unless the source PDF contains reliable tagged structure and the conversion has been visually validated.
PyMuPDF exposes span-level properties such as font size and color, which can support diagnostics and style inference during reconstruction. Such metadata can help identify unusually small text or text rendered in colors close to the background, conditions that may otherwise be overlooked when only visible page appearance is considered. Font size can assist with heading classification, but it remains an inference and should not be treated as definitive semantic metadata in an untagged PDF. A dependable converter should preserve the extracted evidence and flag uncertain classifications rather than claiming that font metrics alone recover the original document hierarchy, as discussed by Alessandro Pignati in "Your PDF parser reads more than you do."
Tagged PDF is identified as the cleaner long-term solution when document producers control the source-generation process. PDF/UA and ISO 32000 tagging structures are mechanisms for preserving semantic relationships that ordinary positional extraction must otherwise infer. Tags can provide a more reliable basis for reading order and document structure than font size, coordinate gaps, or block geometry alone. When tagged structure is absent, a converter must fall back to layout heuristics and cannot treat inferred headings, paragraphs, or tables as guaranteed facts about the source document (KolmoPDF technical discussion).
Why Structured Word Output Matters
Rebuilding a PDF as a structured Word document enables search, restyling, and editing features that are unavailable with plain text extracted by position. For example, real heading styles and tables allow for efficient document navigation and formatting changes. The process also makes it easier to verify the output by reading the resulting .docx file with python-docx and inspecting paragraph styles and table content.
For developers interested in related PDF processing tasks, see the earlier breakdown of PDF merging techniques using Python and command-line tools.
