Skip to main content

Layout Analysis

Many real-world PDFs use multi-column layouts — academic papers, newspapers, brochures. Naively extracting text from such documents produces garbled output because text fragments from different columns are interleaved. paperjam's layout engine detects columns and reads them in the correct visual order.

Analysing page layout

page.analyze_layout() returns a PageLayout describing the column structure and any detected header/footer zones:

import paperjam

doc = paperjam.open("paper.pdf")
layout = doc.pages[0].analyze_layout(
min_gutter_width=20.0, # minimum whitespace between columns (pts)
max_columns=4, # cap on number of columns detected
detect_headers_footers=True, # identify header and footer bands
header_zone_fraction=0.08, # top 8% of page = header zone
footer_zone_fraction=0.08, # bottom 8% of page = footer zone
)

print(f"Columns: {layout.column_count}")
print(f"Multi-column: {layout.is_multi_column}")
print(f"Page size: {layout.page_width:.0f} × {layout.page_height:.0f} pt")
print(f"Gutter positions: {layout.gutters}")

PageLayout attributes:

AttributeTypeDescription
page_widthfloatPage width in points
page_heightfloatPage height in points
column_countintNumber of body columns detected
gutterstuple[float, ...]X positions of gutter centres
regionstuple[LayoutRegion, ...]All classified regions
is_multi_columnboolTrue if column_count > 1

Regions

Each LayoutRegion covers a rectangular area of the page with a kind label:

for region in layout.regions:
print(f" {region.kind} col={region.column_index} bbox={region.bbox}")
for line in region.lines:
print(f" {line.text}")

Region kinds:

kindDescription
"header"Top-of-page header band
"footer"Bottom-of-page footer band
"body_column"A body text column
"full_width"Full-width section (e.g. a large heading)

LayoutRegion attributes:

AttributeTypeDescription
kindstrRegion classification
column_indexint | None0-based column index (None for header/footer)
bboxtupleBounding box (x1, y1, x2, y2)
linestuple[TextLine, ...]Text lines in reading order

Layout-aware text extraction

extract_text_layout() combines layout analysis and text extraction into one call, returning a plain string in correct reading order:

text = doc.pages[0].extract_text_layout(
min_gutter_width=20.0,
)
print(text) # left column first, then right column

Layout-aware structure and Markdown

Pass layout_aware=True to extract_structure() and to_markdown() to respect multi-column layout:

# Structure extraction respecting columns
blocks = doc.extract_structure(layout_aware=True)

# Markdown conversion respecting columns
md = doc.to_markdown(layout_aware=True)

Practical example: two-column academic paper

import paperjam

doc = paperjam.open("ieee-paper.pdf")

for page in doc.pages:
layout = page.analyze_layout(min_gutter_width=15.0)

if layout.is_multi_column:
print(f"Page {page.number}: {layout.column_count}-column layout")
else:
print(f"Page {page.number}: single-column")

# Get body text in correct reading order, skipping headers/footers
body_text = []
for region in layout.regions:
if region.kind in ("body_column", "full_width"):
for line in region.lines:
body_text.append(line.text)

print("\n".join(body_text))

Getting all text in reading order

The PageLayout.text() helper returns all text in the order the regions were detected:

layout = page.analyze_layout()
full_text = layout.text()