mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
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:
@@ -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"""
|
||||
|
||||
@@ -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 • kjvstudy.org</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user