Convert Markdown to polished documents with reusable themes
Project description
docu-craft
Stop writing PDFs by hand. Write Markdown, get a polished document.
import docu_craft
docu_craft.render("report.md", theme="scholar")
What it does
You write content in Markdown. docu-craft handles everything else:
- Picks a theme — fonts, colors, margins, tables, code blocks, all styled consistently across every output format
- Named document styles — styles are registered as first-class named styles in DOCX and ODT, visible in Word's Styles pane and LibreOffice's Styles panel, not just inline formatting
- Multiple output formats — PDF, HTML, DOCX, ODT, LaTeX from the same source file
- Handles emoji — system font fallback or image replacement via downloadable emoji sets
- Validates structure — checks that your document has the sections it needs before rendering
- Finds your assets — themes, skeletons, and emoji sets from the package, your home folder, or any mounted path
No more pasting CSS into scripts. No more fighting with PDF libraries.
Install
pip install docu-craft
# with DOCX support
pip install "docu-craft[docx]"
# with HTML → Markdown conversion
pip install "docu-craft[html]"
# with PDF → Markdown extraction
pip install "docu-craft[pymupdf]"
# everything
pip install "docu-craft[all]"
Conversion paths
docu-craft uses a weighted DAG to resolve conversions. Ask for any path — it finds the route automatically.
| From | To | Engine | Notes |
|---|---|---|---|
md |
pdf |
WeasyPrint (default) | via HTML — full CSS, emoji font support |
md |
pdf |
LaTeX | via pdflatex/xelatex — requires LaTeX install |
md |
html |
— | embedded CSS from theme |
md |
docx |
— | named styles visible in Word's Styles pane |
md |
odt |
— | named styles visible in LibreOffice's Styles panel |
md |
latex |
— | direct Markdown → LaTeX source |
html |
md |
— | extracts article body, strips chrome, preserves images |
pdf |
md |
— | extracts structured text, infers headings from font size |
doc = docu_craft.Document("report.md")
doc.apply_theme("scholar")
doc.render(format="pdf", output="report.pdf")
doc.render(format="docx", output="report.docx")
doc.render(format="html", output="report.html")
# Convert a web paper to Markdown
doc = docu_craft.Document("paper.html")
doc.render(format="md", output="paper.md", img_dir="figures/", base_url="https://example.com/paper/")
# Extract text from a PDF
doc = docu_craft.Document("paper.pdf")
doc.render(format="md", output="paper.md")
Themes
Themes define the full visual identity of a document — fonts, colors, spacing, and every named style — applied consistently across all output formats.
| Theme | Best for |
|---|---|
scholar |
Academic articles, PhD documents |
handout |
Course materials, workshops |
tech-doc |
API docs, technical references |
official |
Institutional letters, formal reports |
docu_craft.render("thesis.md", theme="scholar")
docu_craft.render("class_notes.md", theme="handout")
Drop your own theme in ~/docu_craft/themes/mytheme/ and use it the same way.
Theme schema
Every theme defines a styles block with named semantic styles — body, heading1–heading6, code_block, code_inline, table_header, table_cell, list_item, quote. Each style references a font stack by name (body, header, or mono) and specifies size, color, weight, spacing, and background.
# theme.yaml
style:
fonts:
body: ["Georgia", "Times New Roman", "serif"]
header: ["Arial", "Helvetica", "sans-serif"]
mono: ["Courier New", "Courier", "monospace"]
emoji: ["Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji"]
styles:
heading1:
font: header
size: 16
color: "#1a1a2e"
bold: true
code_block:
font: mono
size: 9
background: "#f4f4f4"
The same style definitions drive all renderers — Word's "DC Heading 1", LibreOffice's "DC Body Text", and the CSS h1 rule all come from the same source.
Emoji
System emoji (font fallback)
By default docu-craft passes emoji through and lets the output application render them using its own font stack. For PDF via WeasyPrint, emoji fonts are resolved automatically:
# theme.yaml
style:
fonts:
emoji: ["Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", "Twemoji Mozilla"]
docu-craft probes the filesystem for each font (in order: Linux → macOS → Windows) and emits @font-face rules pointing to the actual font files. On WSL2, it finds Windows fonts via /mnt/c/Windows/Fonts/ automatically.
Custom emoji sets (image replacement)
For consistent, platform-independent emoji across all readers, use an image-based emoji set. Each emoji character is replaced with a PNG from the set.
docu_craft.render("notes.md", theme="scholar", emoji_set="twemoji")
Download sets with the built-in downloader:
python -m docu_craft.emoji.downloader twemoji
python -m docu_craft.emoji.downloader noto
Twemoji
The recommended custom set. Twemoji (CC-BY 4.0, by Twitter/jdecked) draws every glyph on the same grid at the same size with the same line weights. When you place several emoji side by side they read as a unified visual system — consistent weight, consistent optical size, no surprises. This is the same discipline a monospaced font applies to letterforms. If visual consistency across emoji matters in your documents, Twemoji is the right choice.
Other available sets:
| Set | License | Coverage | Character |
|---|---|---|---|
twemoji |
CC-BY 4.0 | 3800+ | Grid-locked, uniform, consistent |
noto |
Apache 2.0 | 3000+ | Disciplined, clean, Google design system |
Skeletons
Skeletons define what sections a document should have. docu-craft tells you if something is missing before you render.
doc = docu_craft.Document("thesis.md")
doc.apply_skeleton("academic_article").validate()
doc.render()
Built-in skeletons: academic_article, plan_trabajo, tech_doc, official_letter, course_handout.
Config layers
Set defaults once, override whenever you need:
# ~/docu_craft/config.yaml — your personal defaults
defaults:
theme: scholar
emoji_set: twemoji
# .docu_craft.yaml — per-project overrides
defaults:
theme: handout
---
theme: official
---
# Per-document frontmatter overrides everything above
# Explicit argument wins over all of the above
docu_craft.render("file.md", theme="tech-doc")
Extended storage
Point docu-craft at any folder — a team share, a mounted drive, a network path. It searches all of them for themes, skeletons, and emoji sets.
docu_craft.add_extended_store("/mnt/team/docu_craft-assets", name="team")
# .docu_craft.yaml
extended_stores:
- /mnt/team/docu_craft-assets
- path: G:/Shared/styles
name: gdrive
Pluggable renderers
docu_craft.register_renderer(
format="pdf",
module_path="mypackage.renderer:MyRenderer",
engine="myengine",
package="mypackage",
install="pip install mypackage",
)
doc.render(format="pdf", engine="myengine")
The renderer graph is a weighted DAG — preference between equivalent paths (e.g. md→html→pdf vs md→latex→pdf) is configured per project via the engine setting.
Demo
The demo/ folder contains a ready-to-run example:
cd demo
python render.py
render.py exercises every major conversion path — PDF, HTML, DOCX, ODT, LaTeX, and the html→md and pdf→md extractors. Output files land in demo/ alongside the script.
License
Apache 2.0 © Christian A. Servin Lozano
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file docu_craft-0.3.1.tar.gz.
File metadata
- Download URL: docu_craft-0.3.1.tar.gz
- Upload date:
- Size: 380.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74b7796562e9cd21ee5acff5b4ec62925ef8a5a36f59e34e875fbf070f092659
|
|
| MD5 |
dc47e95dc5abbf7a5d74c53e945a91d2
|
|
| BLAKE2b-256 |
6088e3f9214bffe1baa7ad1c7aa30f63e65ff0945b16fa0c60ce874cd2a2f9e1
|
Provenance
The following attestation bundles were made for docu_craft-0.3.1.tar.gz:
Publisher:
publish.yml on CServinL/docu-craft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docu_craft-0.3.1.tar.gz -
Subject digest:
74b7796562e9cd21ee5acff5b4ec62925ef8a5a36f59e34e875fbf070f092659 - Sigstore transparency entry: 1825696551
- Sigstore integration time:
-
Permalink:
CServinL/docu-craft@3a1ee1f5edab2d8d6bf6a7cbf3c3825a89c15935 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/CServinL
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3a1ee1f5edab2d8d6bf6a7cbf3c3825a89c15935 -
Trigger Event:
push
-
Statement type:
File details
Details for the file docu_craft-0.3.1-py3-none-any.whl.
File metadata
- Download URL: docu_craft-0.3.1-py3-none-any.whl
- Upload date:
- Size: 501.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95af69bcbf41a5bc6cdd4bfa723ec0579705cc0660c03ad31c9fe25a663cc666
|
|
| MD5 |
347692381bb12131eea23721f01570b6
|
|
| BLAKE2b-256 |
b3d8dab57554c2d0dc2eaa5c4064c118a8f9bfba01f983a88ea1aa6a9e7a3bd8
|
Provenance
The following attestation bundles were made for docu_craft-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on CServinL/docu-craft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docu_craft-0.3.1-py3-none-any.whl -
Subject digest:
95af69bcbf41a5bc6cdd4bfa723ec0579705cc0660c03ad31c9fe25a663cc666 - Sigstore transparency entry: 1825696572
- Sigstore integration time:
-
Permalink:
CServinL/docu-craft@3a1ee1f5edab2d8d6bf6a7cbf3c3825a89c15935 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/CServinL
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3a1ee1f5edab2d8d6bf6a7cbf3c3825a89c15935 -
Trigger Event:
push
-
Statement type: