Add complete Bible PDF export feature

New endpoint /bible/pdf generates a comprehensive PDF of the entire
King James Bible with all cross-references and word studies.

Features:
- Title page with edition information
- Table of contents (Old Testament & New Testament)
- All 66 books with proper page breaks
- All 31,102 verses with footnotes
- Cross-references grouped by description
- Word studies with Greek/Hebrew terms
- Running headers with book names
- Page numbers
- Professional typography

PDF structure:
- Title page
- Table of contents (2 columns)
- Genesis through Malachi (Old Testament)
- Matthew through Revelation (New Testament)
- Footer page

This will generate a substantial PDF (likely 1000+ pages) with
complete study content suitable for printing or offline study.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 16:27:36 -05:00
parent b1dff9f2fb
commit 2f1d192d7e
2 changed files with 343 additions and 0 deletions
+92
View File
@@ -1142,7 +1142,11 @@ def format_numbered_lists(text):
templates.env.filters['format_lists'] = format_numbered_lists
def number_format(value):
"""Format a number with commas (e.g., 31102 -> 31,102)"""
return f"{value:,}"
templates.env.filters['number_format'] = number_format
def get_biblical_timeline_context():
@@ -2269,6 +2273,94 @@ async def chapter_pdf(request: Request, book: str, chapter: int):
)
@app.get("/bible/pdf")
async def bible_pdf(request: Request):
"""Generate a PDF export of the complete Bible with cross-references and word studies."""
if not WEASYPRINT_AVAILABLE:
raise HTTPException(
status_code=503,
detail="PDF generation is not available. WeasyPrint system libraries are not installed."
)
from .data import bible_metadata
books = bible.get_books()
old_testament = bible_metadata.old_testament_books
new_testament = bible_metadata.new_testament_books
# Prepare data for all books
books_data = []
total_verses = 0
for book_name in books:
chapters_dict = {}
commentaries_dict = {}
shown_words = set() # Track word studies per book
# Get all chapters for this book
chapters = bible.get_chapters_for_book(book_name)
for chapter_num in chapters:
verses = bible.get_verses_by_book_chapter(book_name, chapter_num)
chapters_dict[chapter_num] = verses
total_verses += len(verses)
# Generate commentaries for this chapter
chapter_commentaries = {}
for verse in verses:
commentary = generate_commentary(book_name, chapter_num, verse)
# Add word study sidenotes (avoiding repetition within book)
word_studies = generate_word_study_sidenotes(
verse.text, book_name, chapter_num, verse.verse, shown_words
)
commentary['word_studies'] = word_studies
for study in word_studies:
shown_words.add(study['word'].lower())
# Add cross-references grouped by description
from collections import defaultdict
cross_refs = get_cross_references(book_name, chapter_num, verse.verse)
grouped_refs = defaultdict(list)
for ref in cross_refs:
description = ref['note'] if ref['note'] else 'Related'
grouped_refs[description].append(ref['ref'])
commentary['cross_reference_groups'] = [
{'description': desc, 'refs': refs}
for desc, refs in grouped_refs.items()
]
chapter_commentaries[verse.verse] = commentary
commentaries_dict[chapter_num] = chapter_commentaries
books_data.append({
'name': book_name,
'chapter_count': len(chapters),
'chapters': chapters_dict,
'commentaries': commentaries_dict
})
# Render HTML
html_content = templates.get_template("bible_pdf.html").render(
books=books_data,
old_testament=old_testament,
new_testament=new_testament,
total_verses=total_verses,
)
# Generate PDF (this will take a while for the full Bible!)
pdf_buffer = await render_html_to_pdf_async(html_content)
filename = "kjv-complete-bible.pdf"
return StreamingResponse(
pdf_buffer,
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename={filename}"}
)
@app.get("/book/{book}/chapter/{chapter}/verse/{verse_num}", response_class=HTMLResponse)
def read_verse(request: Request, book: str, chapter: int, verse_num: int):
"""Display a single verse with detailed commentary"""
+251
View File
@@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>King James Version Bible (Complete)</title>
<style>
@page {
size: letter;
margin: 0.75in 0.85in;
@top-center {
content: string(book-title);
font-family: "et-book", Georgia, serif;
font-size: 9pt;
color: #888;
font-style: italic;
}
@bottom-center {
content: counter(page);
font-family: "et-book", Georgia, serif;
font-size: 10pt;
color: #666;
}
@footnote {
border-top: 0.5pt solid #666;
padding-top: 6pt;
margin-top: 12pt;
}
}
@page :first {
@top-center { content: none; }
}
@page toc {
@top-center { content: "Table of Contents"; }
}
body {
font-family: "et-book", Georgia, "Times New Roman", serif;
font-size: 10.5pt;
line-height: 1.6;
color: #111;
}
/* Title page */
.title-page {
page-break-after: always;
text-align: center;
padding-top: 2.5in;
}
.title-page h1 {
font-size: 36pt;
font-weight: normal;
letter-spacing: 0.08em;
margin: 0;
}
.title-page .subtitle {
font-size: 18pt;
font-style: italic;
color: #666;
margin: 0.3in 0;
}
.title-page .edition {
font-size: 12pt;
color: #888;
margin-top: 1in;
}
/* Table of contents */
.toc {
page: toc;
page-break-after: always;
}
.toc h2 {
font-size: 20pt;
font-weight: normal;
margin-bottom: 0.3in;
}
.toc-section {
margin-bottom: 0.25in;
}
.toc-section h3 {
font-size: 12pt;
font-weight: bold;
margin: 0.15in 0 0.1in 0;
color: #444;
}
.toc-books {
columns: 2;
column-gap: 0.3in;
}
.toc-book {
break-inside: avoid;
margin-bottom: 0.08in;
font-size: 10pt;
}
/* Book and chapter styles */
.book {
page-break-before: always;
string-set: book-title content();
}
.book-title {
font-size: 24pt;
font-weight: normal;
letter-spacing: 0.05em;
margin: 0 0 0.15in 0;
}
.book-subtitle {
font-size: 11pt;
font-style: italic;
color: #666;
margin-bottom: 0.3in;
}
.chapter {
margin-bottom: 0.3in;
}
.chapter-number {
font-size: 16pt;
font-weight: normal;
color: #444;
margin: 0.2in 0 0.1in 0;
}
.verses {
text-align: justify;
}
.verse {
display: inline;
}
.verse-number {
font-variant-numeric: lining-nums;
font-size: 8pt;
vertical-align: super;
color: #888;
margin-right: 2pt;
}
/* Footnote styles */
.footnote {
float: footnote;
font-size: 8pt;
line-height: 1.4;
}
.footnote-marker {
vertical-align: super;
font-size: 7pt;
color: #666;
}
::footnote-marker {
font-size: 7pt;
color: #666;
}
::footnote-call {
content: counter(footnote);
font-size: 7pt;
vertical-align: super;
color: #666;
}
/* Footer */
.bible-footer {
page-break-before: always;
text-align: center;
padding-top: 2in;
font-size: 10pt;
color: #888;
}
</style>
</head>
<body>
<!-- Title Page -->
<div class="title-page">
<h1>HOLY BIBLE</h1>
<p class="subtitle">Authorized King James Version</p>
<p class="edition">
1769 Cambridge Edition<br>
Complete with Cross-References and Word Studies<br>
{{ total_verses|number_format }} verses across 66 books
</p>
</div>
<!-- Table of Contents -->
<div class="toc">
<h2>Table of Contents</h2>
<div class="toc-section">
<h3>Old Testament (39 books)</h3>
<div class="toc-books">
{% for book in old_testament %}
<div class="toc-book">{{ book }}</div>
{% endfor %}
</div>
</div>
<div class="toc-section">
<h3>New Testament (27 books)</h3>
<div class="toc-books">
{% for book in new_testament %}
<div class="toc-book">{{ book }}</div>
{% endfor %}
</div>
</div>
</div>
<!-- Bible Content -->
{% for book_data in books %}
<div class="book">
<h1 class="book-title">{{ book_data.name }}</h1>
<p class="book-subtitle">{{ book_data.chapter_count }} {% if book_data.chapter_count == 1 %}chapter{% else %}chapters{% endif %}</p>
{% for chapter_num, verses in book_data.chapters.items() %}
<div class="chapter">
<h2 class="chapter-number">Chapter {{ chapter_num }}</h2>
<div class="verses">
{% for verse in verses %}
{% set commentary = book_data.commentaries.get(chapter_num, {}).get(verse.verse) %}
<span class="verse">
<span class="verse-number">{{ verse.verse }}</span>{{ verse.text }}{% if commentary %}{% if commentary.cross_reference_groups %}<span class="footnote-marker">*</span>{% for group in commentary.cross_reference_groups %}<span class="footnote"><strong>{{ group.description }}:</strong> {% for ref in group.refs %}{{ ref }}{% if not loop.last %}; {% endif %}{% endfor %}.</span>{% endfor %}{% endif %}{% if commentary.word_studies %}{% for study in commentary.word_studies %}<span class="footnote-marker"></span><span class="footnote"><strong>{{ study.word }}:</strong> {{ study.term }} ({{ study.translit }}) — {{ study.meaning }}.</span>{% endfor %}{% endif %}{% endif %}
</span>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
<!-- Footer -->
<div class="bible-footer">
<p>Complete King James Version Bible</p>
<p>From KJV Study &bull; kjvstudy.org</p>
</div>
</body>
</html>