Skip to main content

respacer

Fix missing spaces in text mangled by PDF-to-Word conversion — while preserving formatting.

ThisisaparagraphaboutAI  -->  This is a paragraph about AI

If you've ever converted a PDF to Word and gotten a wall of ThisIsWhatItLooksLikeInsteadOfNormalText, this tool fixes it — for the whole document, in one pass, without wrecking your headings, bold text, or tables.

Why this happens

PDFs don't store "words" — they store individual characters positioned at exact X/Y coordinates on the page. Converters have to guess where word boundaries are from the gaps between characters. Certain fonts, tight kerning, justified text, or PDFs generated by non-standard tools make word gaps look identical to letter gaps, so the converter merges everything together.

How it works

respacer uses a two-stage pipeline:

Stage 1 — Dictionary segmentation (free, fast, always on) Uses frequency-based Viterbi word segmentation (via wordninja) to propose splits, then runs every candidate through a confidence gate before accepting it:

  • Only touches tokens that look genuinely broken (long, pure-alphabetic, no digits/underscores)
  • Never touches URLs, emails, or snake_case/kebab-case/code-like identifiers
  • Rejects any split that loses or changes characters
  • Rejects splits that produce junk single-letter fragments
  • Merges runs like A + I back into AI (mis-split acronyms)

Stage 2 — LLM enhancement (optional, requires your own Groq API key) Anything Stage 1 isn't confident about (ambiguous proper nouns, unusual jargon) can optionally be sent to an LLM for a context-aware second opinion. This is opt-in only — respacer never makes a network call unless you explicitly provide an API key.

Formatting preservation For .docx input, respacer never rebuilds the document. It walks each paragraph's existing runs (Word's internal unit of "text with one consistent style") and rewrites only the text inside each run, in place. Since a run's style is never touched, bold stays bold, headings stay headings, fonts/colors/tables are all untouched.

For .pdf input, there's no equivalent of a "run" to preserve, so respacer extracts text and rebuilds a clean .docx with basic structure (paragraphs, guessed headings). If you already have a converted .docx that looks wrong, fix that file directly — it preserves 100% of the original formatting, which a fresh PDF extraction cannot.

Installation

git clone https://github.com/abhi1628/respacer.git
cd respacer
pip install -r requirements.txt
pip install -e .

Or once published to PyPI:

pip install respacer

Usage

Command line

# Fix a broken docx
respacer fix input.docx -o output.docx

# Fix directly from a PDF
respacer fix input.pdf -o output.docx

# See exactly what changed
respacer fix input.docx -o output.docx --verbose

# Only touch tokens 15+ characters long (default is 12)
respacer fix input.docx -o output.docx --min-length 15

Python API

from respacer import respace_text

text = "ThisisaparagraphaboutAI and machine learning."
print(respace_text(text))
# -> "This is a paragraph about AI and machine learning."
from respacer.docx_handler import fix_docx

# Fixes formatting-preserving, returns a report of every change made
report = fix_docx("input.docx", "output.docx")
for change in report:
    print(change["original"], "->", change["fixed"], change["confidence"])
from respacer.pdf_handler import fix_pdf

fix_pdf("input.pdf", "output.docx")

Optional: LLM-enhanced mode

from respacer.llm_enhancer import respace_text_with_llm

fixed = respace_text_with_llm(
    text,
    api_key="your-groq-api-key",   # or os.environ["GROQ_API_KEY"]
)

This only calls the LLM on paragraphs the dictionary pass couldn't confidently resolve, so API usage scales with how ambiguous your document actually is — not its total length.

What it will NOT touch (by design)

  • URLs and email addresses
  • snake_case, kebab-case, and anything containing digits (looks code-like)
  • Words shorter than min_length (default 12 chars) — too risky to guess on short strings
  • Any split where the confidence score comes back low (better to leave a word broken than corrupt a correct one)

Known limitations

  • CamelCase ambiguity: CamelCaseVariable (a code identifier) and BaderiaInstitute (a broken Title Case heading) look identical to the algorithm. Stage 1 will attempt to split both. If your document mixes prose and code identifiers, review the --verbose report or use LLM-enhanced mode, which uses surrounding context to tell them apart.
  • Very short glued words (e.g., "ofthe") below min_length are intentionally left alone, since short strings are far more likely to produce false-positive splits.
  • PDF input rebuilds the document structure from scratch — it does not preserve the original PDF's fonts/styling, only paragraph-level structure.

Running tests

pip install -e ".[dev]"
pytest tests/ -v

Project structure

respacer/
├── respacer/
│   ├── __init__.py        # public API
│   ├── detector.py         # decides which tokens are safe to touch
│   ├── segmenter.py         # dictionary-based segmentation + confidence gate
│   ├── docx_handler.py     # in-place run-level fixing for .docx
│   ├── pdf_handler.py      # extraction + rebuild for .pdf
│   ├── llm_enhancer.py     # optional Groq-based second pass
│   └── cli.py              # command-line interface
├── tests/
│   └── test_segmenter.py
├── examples/
├── requirements.txt
├── pyproject.toml
└── LICENSE

Roadmap

  • Batch mode (fix an entire folder of files)
  • Preserve font/style info when extracting directly from PDF (via character-level bounding boxes)
  • Web demo / hosted API
  • Support for .pptx (same problem shows up in slide decks)
  • Multi-language dictionary support (currently English-only)

Contributing

Issues and PRs welcome. If you hit a document where respacer makes a wrong call, please open an issue with the (anonymized) before/after text — real failure cases are the best way to improve the confidence heuristics.

License

MIT — see LICENSE.

Metadata

Release files for respacer 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for respacer 0.1.0
File Size Uploaded Provenance Actions
respacer-0.1.0.tar.gz 14.5 kB With provenance Details for respacer-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for respacer 0.1.0
File Interpreter ABI Platform Actions
respacer-0.1.0-py3-none-any.whl Python 3 none any Details for respacer-0.1.0-py3-none-any.whl

Total release size: 28.5 kB

Release files / respacer-0.1.0.tar.gz

Download URL respacer-0.1.0.tar.gz
Size 14.5 kB
Tags Source
SHA-256 checksum
How to use checksums
1c6e4ee11462994e89dba6c944680365f2f059779f58e58f917627f0f42a37f6
BLAKE2b-256 checksum
How to use checksums
bef111cbbf7275e346c36a04797320ec8caf838b858f59f80cb30859f9dd4961
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release files / respacer-0.1.0-py3-none-any.whl

Download URL respacer-0.1.0-py3-none-any.whl
Size 14.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4c2c461a3f84b941c3a7c76b843610c2a25a25c2b8726b741e05c137ece2433b
BLAKE2b-256 checksum
How to use checksums
73b58eb803b759a99d628c157282d66b35d80d88d4b807dadff88d9e8097ab4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Security for respacer 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for respacer-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page