From pstuart-publishing
Professional book publishing from Markdown, Word, or text to PDF/EPUB. Use when creating books, converting manuscripts to PDF, generating covers (Kindle and paperback with spine), building indexes with real page numbers, creating table of contents, or formatting chapters. Handles trade paperback (5.5x8.5), two-pass TOC generation, EPUB conversion, and Amazon KDP requirements.
How this skill is triggered — by the user, by Claude, or both
Slash command
/pstuart-publishing:book-publisherThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Transform manuscripts into professional, Amazon-ready books. Supports multiple input formats (Markdown, Word, text) and outputs print-ready PDFs, Kindle EPUBs, and professional covers.
Makefilebookpub/__init__.pybookpub/build_book.pybookpub/config.pybookpub/cover.pybookpub/discovery.pybookpub/embed_cover.pybookpub/epub_engine.pybookpub/fonts.pybookpub/index.pybookpub/migrate.pybookpub/onix.pybookpub/pdf_engine.pybookpub/preflight.pybookpub/qa_report.pybookpub/screenplay.pybookpub/text.pybookpub/with_covers.pyconverters/docx_to_markdown.pyconverters/text_to_markdown.pyTransform manuscripts into professional, Amazon-ready books. Supports multiple input formats (Markdown, Word, text) and outputs print-ready PDFs, Kindle EPUBs, and professional covers.
IMPORTANT: When helping users create a book, ALWAYS gather their style preferences BEFORE generating any files. Each book should have a unique look that matches its content and audience.
Ask users these questions before starting:
Book Genre/Type: What type of book is this?
Color Scheme: What mood should the design convey?
Interior Style: How should the interior pages look?
Cover Elements: What should appear on your cover?
Back Cover Content: What information for the back?
STYLE_PRESETS = {
"navy_gold": {
"name": "Navy & Gold",
"background": (30, 58, 95),
"accent": (201, 162, 39),
"title_color": (255, 255, 255),
# Best for: Business, Leadership, Non-fiction
},
"burgundy_cream": {
"name": "Burgundy & Cream",
"background": (88, 24, 32),
"accent": (245, 235, 220),
# Best for: Memoir, Literary Fiction, Biography
},
"teal_coral": {
"name": "Teal & Coral",
"background": (0, 95, 115),
"accent": (255, 127, 102),
# Best for: Self-Help, Wellness, Contemporary
},
"black_silver": {
"name": "Black & Silver",
"background": (25, 25, 25),
"accent": (192, 192, 192),
# Best for: Thriller, Mystery, Drama
},
"earth_warm": {
"name": "Earth Warm",
"background": (89, 60, 31),
"accent": (218, 165, 32),
# Best for: Spirituality, Nature, Grounded Topics
},
"purple_gold": {
"name": "Purple & Gold",
"background": (60, 25, 85),
"accent": (218, 165, 32),
# Best for: Transformation, Luxury, Spiritual Growth
},
"forest_cream": {
"name": "Forest & Cream",
"background": (34, 85, 51),
"accent": (255, 248, 220),
# Best for: Sustainability, Nature, Wisdom
},
"minimal_white": {
"name": "Minimal White",
"background": (250, 250, 250),
"accent": (30, 30, 30),
# Best for: Modern, Clean, Contemporary
},
}
For custom colors, collect RGB values for:
# 1. Convert source files to markdown (if needed)
python3 converters/docx_to_markdown.py manuscript.docx
# 2. Compile chapters into single manuscript
python3 compile_book.py
# 3. Generate PDF (two-pass for accurate TOC)
python3 generate_pdf.py
# 4. Generate index and final PDF
python3 generate_index.py
# 5. Generate EPUB for Kindle
python3 generate_epub.py
# 6. Generate cover art (interactive — drafts prompts, approves per surface)
python3 generate_cover_art.py
# 7. Compose covers from approved art
python3 compose_paperback_wrap.py
python3 compose_kindle_cover.py
python3 compose_interior_art.py # optional — only if you want chapter motif
# 8. Add covers to PDF
python3 add_covers_to_pdf.py
# 9. Protect PDF (optional)
python3 protect_pdf.py "YourOwnerPassword"
BookProject/
├── manuscript/ # Source chapter files
│ ├── 00_INTRODUCTION.md
│ ├── 01_CHAPTER_ONE.md
│ ├── 02_CHAPTER_TWO.md
│ └── BACK_MATTER.md
├── publishing/ # Generated outputs
│ ├── compile_book.py # Manuscript compiler
│ ├── generate_pdf.py # PDF generator
│ ├── generate_epub.py # EPUB generator
│ ├── generate_index.py # Index generator
│ ├── create_kindle_cover.py # Kindle cover
│ ├── create_paperback_cover.py # Paperback wrap cover
│ ├── epub_styles.css # EPUB stylesheet
│ ├── manuscript_compiled.md # Compiled output
│ ├── BookTitle_v*.pdf # Generated PDFs
│ └── BookTitle_*.epub # Generated EPUBs
└── assets/ # Optional images, fonts
Each script uses configuration dictionaries for easy customization:
BOOK_CONFIG = {
"title": "Your Book Title",
"subtitle": "Your Compelling Subtitle",
"author": "Author Name",
"tagline": "A one-line hook for readers",
"website": "authorwebsite.com",
"price": "$24.99 US",
"category": "Category / Subcategory",
"year": "2025",
"dedication": "For those who believed...",
"hook": "Opening hook for back cover that draws readers in.",
"author_bio": "Author bio paragraph for back cover...",
}
COVER_COLORS = {
"gradient_start": (15, 32, 55), # Dark navy
"gradient_end": (25, 55, 95), # Deep blue
"accent": (218, 165, 32), # Gold
"accent_light": (255, 215, 100), # Light gold
"text_light": (240, 240, 240),
"text_muted": (200, 200, 200),
}
DESIGN_COLORS = {
"navy": (15, 32, 55), # Dark navy for headings
"navy_light": (35, 65, 115), # Lighter navy for accents
"gold": (178, 134, 11), # Gold for highlights
"gold_light": (255, 248, 220), # Light gold background
"gold_border": (218, 165, 32), # Gold for borders
"charcoal": (40, 40, 40), # Dark text
"gray_light": (245, 245, 245), # Light gray background
"gray_medium": (180, 180, 180), # Medium gray for rules
}
Accurate TOC page numbers via two rendering passes:
Built-in methods for visual elements:
life_framework_infographic() - 2x2 quadrant diagramsdaniel_decision_flowchart() - Step-by-step vertical flowstriage_flowchart() - Decision trees with yes/no branchespower_framework_infographic() - Vertical list with iconsno_decision_tree_flowchart() - Complex branching logicModern terminal appearance for code examples:
STYLE = {
"code_bg": (30, 30, 30), # Dark terminal background
"code_text": (220, 220, 220), # Light gray text
}
Conservative character estimation prevents text overflow in blockquotes and tip boxes:
# Use conservative chars_per_line for italic/variable-width text
chars_per_line = int(content_width * 1.5) # Not 2.0+ which causes overflow
num_lines = max(1, (len(text) // chars_per_line) + 2) # +2 for word wrap safety
box_height = max(14, text_height + 8) # Extra padding
Prevent headings at page bottom with no content following:
# Check remaining space before adding heading
def check_page_space(self, min_content_after: float = 25):
"""Check if enough space remains for heading + content."""
remaining = PAGE_HEIGHT - MARGIN_BOTTOM - self.get_y()
if remaining < min_content_after:
self.add_page()
Recommended min_content_after values (standard layout):
| Heading Level | Threshold | Notes |
|---|---|---|
| H1 (Chapters) | 35-50mm | Chapter starts get new page anyway |
| H2 (Sections) | 30-45mm | Major sections need visible content |
| H3 (Subsections) | 40-70mm | Higher values prevent orphans but increase whitespace |
| H4 (Minor) | 35-55mm | Balance based on content density |
Trade-off: Higher thresholds = fewer orphans but more wasted space. Use copy fitting to compensate.
Filter invalid chapter names from running headers:
INVALID_HEADER_PATTERNS = [
'my skill', 'invite multiple', 'best practices',
'troubleshooting', 'common issues', 'summary'
]
def _is_valid_chapter_name(title: str) -> bool:
title_lower = title.lower()
for pattern in INVALID_HEADER_PATTERNS:
if pattern in title_lower:
return False
return True
Optimized spacing for professional layout:
LINE_HEIGHT_BODY = 4.5 # Body text (tighter than default)
LINE_HEIGHT_CODE = 3.0 # Code blocks (compact terminal)
LINE_HEIGHT_LIST = 4.5 # List items
PARA_SPACING = 1.5 # After paragraphs (minimal)
SECTION_SPACING_BEFORE = 4 # Before headings
SECTION_SPACING_AFTER = 1 # After headings
Copy fitting adjusts typography to fit more content per page while maintaining readability. Use this when your PDF has too many pages or excessive whitespace.
# === STANDARD LAYOUT (comfortable reading) ===
MARGIN_H = 12 # Horizontal margins (mm)
MARGIN_TOP = 12 # Top margin
MARGIN_BOTTOM = 15 # Bottom margin
LINE_HEIGHT_BODY = 4.5 # Body text line height
LINE_HEIGHT_CODE = 3.0 # Code blocks
PARA_SPACING = 1.5 # After paragraphs
SECTION_SPACING_BEFORE = 4 # Before headings
SECTION_SPACING_AFTER = 1 # After headings
# === COPY-FITTED LAYOUT (20-25% more compact) ===
MARGIN_H = 10 # Tighter margins
MARGIN_TOP = 11
MARGIN_BOTTOM = 13
LINE_HEIGHT_BODY = 4.2 # Tighter line height
LINE_HEIGHT_CODE = 2.9 # Compact code blocks
PARA_SPACING = 1.2 # Minimal paragraph spacing
SECTION_SPACING_BEFORE = 3 # Less space before headings
SECTION_SPACING_AFTER = 0.8
Reduce font sizes slightly for denser layout:
# Standard → Copy-fitted
H2_FONT_SIZE = 13 → 12 # Section headings
H3_FONT_SIZE = 11 → 10 # Subsection headings
H4_FONT_SIZE = 10 → 9.5 # Minor headings
When tightening layout, also reduce orphan prevention thresholds proportionally:
# Standard orphan prevention (looser layout)
H1_MIN_CONTENT_AFTER = 50 # mm
H2_MIN_CONTENT_AFTER = 45
H3_MIN_CONTENT_AFTER = 70 # High value prevents orphans but wastes space
H4_MIN_CONTENT_AFTER = 55
# Copy-fitted orphan prevention (balanced)
H1_MIN_CONTENT_AFTER = 35 # Enough to prevent orphans
H2_MIN_CONTENT_AFTER = 30 # but not wasteful
H3_MIN_CONTENT_AFTER = 45
H4_MIN_CONTENT_AFTER = 35
Typical results from copy fitting:
Warning: Always verify readability after copy fitting by reviewing sample pages across the document.
from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject, DictionaryObject, BooleanObject
# Set 2-page layout
writer._root_object[NameObject("/PageLayout")] = NameObject("/TwoPageLeft")
# Encrypt with read-only permissions
writer.encrypt(
user_password="", # Empty = no password to read
owner_password=owner_pwd, # Required to edit
permissions_flag=0b0000, # No permissions (read-only)
)
.title-page, .copyright-page, .part-page
.chapter-header, .chapter-number, .chapter-title
.key-principle, .remember-box, .practical-tool
.decision-tree, .flowchart, .checklist
Cover art uses a two-step pipeline: AI-generated bitmap artwork (via local zgen / Draw Things CLI) composed with vector text overlay (via fpdf2). Vector text stays crisp at any zoom; the bitmap provides the visual identity.
generate_cover_art.py (interactive — drafts prompts, runs zgen serially,
contact-sheet approval, seed-locked refinement loop)
↓
cover-assets/{wrap_art,kindle_art,chapter_motif}.png (committed to git)
↓
┌─ compose_paperback_wrap.py → paperback_wrap.pdf (vector text over bitmap)
├─ compose_kindle_cover.py → kindle_cover.jpg (PDF → raster at final step)
└─ compose_interior_art.py → assets/chapter_motif.png (staged for generate_pdf.py)
zgen binary at /Users/pstuart/bin/zgen (Z Image Turbo via Draw Things CLI)Style presets define color palettes for vector text (title, body, accent). Choose from: navy_gold, burgundy_cream, teal_coral, black_silver, earth_warm, purple_gold, forest_cream, minimal_white. Custom palettes supported via COVER_STYLE dict in your BOOK_CONFIG.
| Surface | zgen canvas | Final output |
|---|---|---|
| Paperback wrap | 4992 × 2624 | PDF at 2·(5.5+0.125)+spine × 8.75 inches |
| Kindle | 1600 × 2560 | JPEG 1600 × 2560 |
| Chapter motif | 1664 × 2560 | PNG 1650 × 2550 (staged to assets/) |
spine_width_inches = page_count * paper_thickness
# paper_thickness = 0.002252 (white) or 0.002500 (cream)
Spine text is only drawn at KDP's 100-page minimum for spine text. (0.0625" is
merely the geometric floor where any spine exists; KDP requires ≥ 100 pages before
it will accept spine text. Use the 100-page rule.) See
references/amazon-kdp-requirements.md.
After compose_paperback_wrap.py, verify vector text survived:
pdftotext cover-assets/paperback_wrap.pdf -
# Expected: book title and author appear in the output
Zoom paperback_wrap.pdf to 400% in Preview — title text must stay crisp. If pixelated, composition went wrong.
INDEX_TERMS = {
"Leadership": [],
"Daniel Principle": [],
"Sabbath": [],
"Calendar": [],
# Page numbers populated automatically from PDF
}
1-3, 5, 7-9Final output merges:
Reads cover-assets/paperback_wrap.pdf produced by compose_paperback_wrap.py and extracts the front and back panels as vector PDF pages via pypdf mediabox transforms. Preserves both the vector text and the underlying bitmap — nothing is rasterised during extraction.
from pypdf import PdfReader, PdfWriter
from pypdf.generic import RectangleObject
# Front panel: crop the single-page wrap PDF to the front panel's x-range.
# Both vector text and bitmap survive the mediabox crop.
page.mediabox = RectangleObject((x0_pt, 0, x1_pt, wrap_h_pt))
page.cropbox = RectangleObject((x0_pt, 0, x1_pt, wrap_h_pt))
The final merged PDF is [front_panel | book_interior | back_panel] — KDP-upload ready.
python3 add_covers_to_pdf.py
# Automatically finds latest book PDF and cover-assets/paperback_wrap.pdf
# Output: BookTitle_v*_with_covers.pdf
Include a 00-preface.md file with:
# Preface
## About This Book
Brief introduction to what the book covers.
## Version Information
**Book Version:** 1.0
**Publication Date:** January 10, 2026
**Software Version:** 2.1.3
## Staying Current
- Check official docs at [product-docs-url]
- Find latest book version at [author-website/books]
- Available as PDF/EPUB or wherever books are sold
## About the Author
Author biography and credentials.
## Acknowledgments
Thanks to contributors and supporters.
Create VERSION.md to track book editions:
| Book Version | Date | Software Version | Notes |
|--------------|------|------------------|-------|
| 1.0 | Jan 10, 2026 | 2.1.3 | Initial release |
| 1.1 | TBD | TBD | Updates for new features |
python3 converters/docx_to_markdown.py input.docx -o output.md
--extract-images)python3 converters/text_to_markdown.py input.txt -o output.md
# CHAPTER 1
## The Chapter Title
Opening paragraph after chapter heading...
## Section Heading
Content with **bold** and *italic* formatting.
### Subsection Heading
More detailed content here.
# PART I: FOUNDATIONS
*The Principles That Ground Everything Else*
> Scripture or important quote here
> -- Attribution
- [ ] Unchecked checkbox item
- [x] Checked checkbox item
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Data | Data | Data |
**Key Takeaway:** Important point to remember.
# Core PDF generation
pip3 install fpdf2 pypdf
# EPUB generation
pip3 install ebooklib
# Cover generation (Pillow for bitmap ops, python-barcode for ISBN EAN-13)
pip3 install Pillow python-barcode
# Word document conversion
pip3 install python-docx
# PDF to image conversion (for validation)
brew install poppler
pip3 install pdf2image
| File | Purpose |
|---|---|
templates/compile_book_template.py | Compile chapters into manuscript |
templates/generate_pdf_template.py | Full PDF generation with styling |
templates/generate_epub_template.py | EPUB generation with ebooklib |
templates/generate_index_template.py | Index extraction and PDF merge |
templates/create_kindle_cover_template.py | Kindle front cover |
templates/create_paperback_cover_template.py | Full wrap cover with spine |
templates/add_covers_to_pdf_template.py | Extract covers from wrap, add to PDF |
templates/protect_pdf_template.py | PDF encryption and protection |
templates/epub_styles.css | Kindle-optimized stylesheet |
converters/docx_to_markdown.py | Word document converter |
converters/text_to_markdown.py | Plain text converter |
references/pdf-generation.md - Detailed PDF techniques, styling, infographicsreferences/epub-generation.md - EPUB structure, CSS, validationreferences/cover-generation.md - Cover design, dimensions, typographyreferences/index-generation.md - Term extraction, page search, formattingreferences/amazon-kdp-requirements.md - Full KDP specificationsreferences/manuscript-structure.md - File organization patterns| Issue | Cause | Solution |
|---|---|---|
| TOC page numbers wrong | Single-pass generation | Ensure two-pass approach in generate_pdf.py |
| Unicode errors | Non-latin1 characters | Use sanitize_text() or load TTF fonts |
| Cover text blurry | Low DPI or small fonts | Use 300 DPI, minimum 24pt for titles |
| EPUB not validating | Invalid HTML structure | Run through epubcheck, fix errors |
| Index terms missing | Variant spellings | Add alternate forms to INDEX_TERMS |
| Spine text cut off | Wrong page count | Recalculate spine width after final page count |
| Headers on chapter starts | Missing flag | Set is_chapter_start = True before add_page() |
| Tables overflow page | Wide content | Reduce column count or font size |
| Box text overflow | chars_per_line too high | Use content_width * 1.5 not * 2.0+, add +2 lines buffer |
| Orphaned headings | min_content_after too low | Use 25mm for H1, 20mm for H2, 15mm for H3 |
| Invalid running headers | Subsection titles in header | Add _is_valid_chapter_name() filter |
| Nearly blank pages | Short sections forcing breaks | Add _should_force_page_break() check |
| DeprecationWarning: ln=True | fpdf2 v2.5.2+ | Use new_x=XPos.LMARGIN, new_y=YPos.NEXT |
| Font subsetting warnings | macOS system fonts | Add suppress_stderr() context manager |
| Too much white space | Default spacing too loose | Reduce LINE_HEIGHT and PARA_SPACING values |
import os
from contextlib import contextmanager
@contextmanager
def suppress_stderr():
"""Suppress stderr during PDF output (fonttools noise)."""
devnull = os.open(os.devnull, os.O_WRONLY)
old_stderr = os.dup(2)
os.dup2(devnull, 2)
try:
yield
finally:
os.dup2(old_stderr, 2)
os.close(devnull)
os.close(old_stderr)
# Usage
with suppress_stderr():
pdf.output(str(output_path))
from fpdf.enums import XPos, YPos
# Old (deprecated):
self.cell(0, 10, "Text", ln=True)
# New:
self.cell(0, 10, "Text", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
Before publishing:
pdftotext paperback_wrap.pdf - — title and author appear# 1. Set up project structure
mkdir -p MyBook/{manuscript,publishing,assets}
cd MyBook
# 2. Copy templates
cp ~/.claude/skills/book-publisher/templates/*.py publishing/
cp ~/.claude/skills/book-publisher/templates/epub_styles.css publishing/
# 3. Write chapters in manuscript/
# 00-preface.md, 01-chapter.md, etc.
# 4. Edit publishing/compile_book.py
# - Update BOOK_METADATA
# - Update FILE_ORDER (include preface)
# 5. Edit publishing/generate_pdf.py
# - Update BOOK_CONFIG
# - Update COVER_COLORS / DESIGN_COLORS
# - Adjust LINE_HEIGHT values if needed
# 6. Run the pipeline
cd publishing
python3 compile_book.py
python3 generate_pdf.py
python3 generate_index.py
python3 generate_epub.py
python3 create_kindle_cover.py
python3 create_paperback_cover.py
# 7. Add covers to PDF
python3 add_covers_to_pdf.py
# 8. Protect PDF (optional)
python3 protect_pdf.py "YourSecurePassword"
# 9. Validate outputs
open MyBook_v*_with_covers.pdf
open MyBook_v*_protected.pdf
open MyBook_*.epub # In Kindle Previewer
For comprehensive quality assurance, export pages to images and review:
from pdf2image import convert_from_path
from pathlib import Path
pdf_path = "MyBook_v1.0_with_covers.pdf"
output_dir = Path("page_review")
output_dir.mkdir(exist_ok=True)
# Export all pages at 150 DPI for review
images = convert_from_path(pdf_path, dpi=150)
for i, img in enumerate(images, 1):
img.save(output_dir / f"page_{i:03d}.png", "PNG")
print(f"Exported page {i}")
# Review specific pages
problem_pages = [16, 86, 139] # Check these for issues
for page_num in problem_pages:
images = convert_from_path(pdf_path, first_page=page_num, last_page=page_num, dpi=200)
images[0].save(f"review_page_{page_num}.png", "PNG")
Comprehensive layout validation should check for these common issues:
| Issue | Description | How to Fix |
|---|---|---|
| Orphaned headers | Section heading at bottom of page with no content following | Increase min_content_after for that heading level |
| Large whitespace | Half-page or more of blank space | Reduce orphan thresholds or apply copy fitting |
| Unrendered markdown | Raw **text** or *text* appearing in output | Fix markdown parsing in content renderer |
| Text overflow | Text running outside margins or overlapping | Check box height calculations, reduce chars_per_line estimate |
| Navigation text | Web navigation links ([Previous], [Next]) in print output | Remove navigation markdown from source files |
| Bad pagination | Content split awkwardly across pages | Adjust min_content_after or add manual page breaks |
#!/usr/bin/env python3
"""Visual PDF layout validation via image export."""
from pdf2image import convert_from_path
from pathlib import Path
def validate_pdf_layout(pdf_path: str, output_dir: str = "validation"):
"""Export PDF to images for visual inspection."""
output = Path(output_dir)
output.mkdir(exist_ok=True)
images = convert_from_path(pdf_path, dpi=150)
issues = []
for i, img in enumerate(images, 1):
img_path = output / f"page_{i:03d}.png"
img.save(img_path, "PNG")
# Basic heuristics for common issues
# (Full validation requires visual inspection)
width, height = img.size
# Check for mostly white pages (potential large whitespace)
pixels = list(img.getdata())
white_count = sum(1 for p in pixels if sum(p[:3]) > 750)
white_ratio = white_count / len(pixels)
if white_ratio > 0.85:
issues.append(f"Page {i}: High whitespace ({white_ratio:.1%})")
print(f"Exported {len(images)} pages to {output}/")
if issues:
print("Potential issues:")
for issue in issues:
print(f" - {issue}")
return issues
if __name__ == "__main__":
import sys
pdf = sys.argv[1] if len(sys.argv) > 1 else "book.pdf"
validate_pdf_layout(pdf)
When fixing layout issues, always work from the last page to the first. This prevents earlier fixes from shifting page numbers and invalidating your issue list.
# Example workflow
1. Export all pages to images
2. Review and note issues: [page 220, 170, 160, 110, 70, 27, 24, 22, 13]
3. Fix page 220 first, then 170, then 160, etc.
4. Regenerate PDF after each batch of fixes
5. Verify fixes didn't introduce new issues
# After generating PDF, check:
□ First 5 pages (title, copyright, dedication, TOC)
□ Every 20th page for layout consistency
□ Last 3 pages (appendix, back matter)
□ Any pages with tables or code blocks
□ Part divider pages
□ Chapter start pages
npx claudepluginhub pstuart/pstuart --plugin pstuart-publishingConverts book drafts to EPUB/PDF/MOBI/TXT/Markdown formats using pandoc, manages metadata, cover design, title candidates, and marketing plan for publishing.
Creates professionally designed B2B SaaS e-books in HTML+CSS, exported as print-ready PDF. 3–10 pages, 9 style presets, 11 page layout types.
Manages multi-book content, voice, and production workflows for nonfiction authoring. Plan books, maintain consistent voice, structure manuscripts, and run end-to-end publishing pipelines.