The Digital Waqif: Waqf, Faraid, and the Architecture of Legacy — 10 Clauses, Will & Testament Playbook
This commit is contained in:
+336
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Assemble 'The Digital Waqif: Waqf, Faraid, and the Architecture of Legacy' PDF
|
||||
Will & Testament Playbook — Legal-document aesthetic, 6×9in."""
|
||||
import os, re, sys
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
||||
from reportlab.lib.colors import HexColor
|
||||
from reportlab.platypus import (
|
||||
Paragraph, Spacer, PageBreak, Frame, PageTemplate, BaseDocTemplate
|
||||
)
|
||||
|
||||
OUTPUT_DIR = os.path.expanduser("~/odysseus_book/digital_waqif")
|
||||
CHAPTERS_DIR = os.path.join(OUTPUT_DIR, "chapters")
|
||||
PDF_PATH = os.path.join(OUTPUT_DIR, "pdf", "The_Digital_Waqif.pdf")
|
||||
|
||||
MARGIN = 0.85 * inch
|
||||
FOOTER_H = 0.4 * inch
|
||||
PAGE_W = 6 * inch
|
||||
PAGE_H = 9 * inch
|
||||
|
||||
NAVY = HexColor('#1B2A4A')
|
||||
GOLD = HexColor('#C8A951')
|
||||
DARK = HexColor('#1a1a1a')
|
||||
GREY = HexColor('#666666')
|
||||
INK = HexColor('#222222')
|
||||
ACCENT_RED = HexColor('#8B0000')
|
||||
|
||||
style_title = ParagraphStyle('Title', fontName='Helvetica-Bold', fontSize=24,
|
||||
leading=30, alignment=TA_CENTER, textColor=NAVY, spaceAfter=8)
|
||||
style_subtitle = ParagraphStyle('Subtitle', fontName='Helvetica-Oblique', fontSize=13,
|
||||
leading=17, alignment=TA_CENTER, textColor=GOLD, spaceAfter=20)
|
||||
style_author = ParagraphStyle('Author', fontName='Helvetica', fontSize=12,
|
||||
leading=16, alignment=TA_CENTER, textColor=GREY, spaceAfter=28)
|
||||
style_copyright = ParagraphStyle('Copyright', fontName='Helvetica', fontSize=9,
|
||||
leading=12, alignment=TA_CENTER, textColor=GREY)
|
||||
style_ch_num = ParagraphStyle('ChNum', fontName='Helvetica-Bold', fontSize=10,
|
||||
leading=13, alignment=TA_CENTER, textColor=GOLD, spaceAfter=4, spaceBefore=18)
|
||||
style_ch_title = ParagraphStyle('ChTitle', fontName='Helvetica-Bold', fontSize=17,
|
||||
leading=23, alignment=TA_LEFT, textColor=NAVY, spaceAfter=8, spaceBefore=4)
|
||||
style_subtitle_ch = ParagraphStyle('SubtitleCh', fontName='Helvetica-Oblique', fontSize=11,
|
||||
leading=15, alignment=TA_LEFT, textColor=ACCENT_RED, spaceAfter=14, spaceBefore=2)
|
||||
style_framework = ParagraphStyle('Framework', fontName='Helvetica-Bold', fontSize=10.5,
|
||||
leading=14, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=14,
|
||||
backColor=HexColor('#F4F6FA'), borderColor=GOLD, borderWidth=1, borderPadding=8)
|
||||
style_body = ParagraphStyle('Body', fontName='Times-Roman', fontSize=10,
|
||||
leading=14.5, alignment=TA_JUSTIFY, textColor=INK, spaceAfter=7, firstLineIndent=0)
|
||||
style_clause = ParagraphStyle('Clause', fontName='Times-Bold', fontSize=11,
|
||||
leading=15, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10,
|
||||
backColor=HexColor('#F4F6FA'), borderColor=NAVY, borderWidth=1, borderPadding=10)
|
||||
style_nass = ParagraphStyle('Nass', fontName='Times-Italic', fontSize=10,
|
||||
leading=14.5, alignment=TA_JUSTIFY, textColor=DARK, spaceAfter=6, leftIndent=24,
|
||||
backColor=HexColor('#FDFBF3'), borderColor=GOLD, borderWidth=0.5, borderPadding=8)
|
||||
style_faradi = ParagraphStyle('Faradi', fontName='Times-Bold', fontSize=10,
|
||||
leading=14.5, alignment=TA_LEFT, textColor=NAVY, spaceAfter=3, leftIndent=24)
|
||||
style_waqif = ParagraphStyle('Waqif', fontName='Times-Bold', fontSize=10,
|
||||
leading=14.5, alignment=TA_LEFT, textColor=GOLD, spaceAfter=3, leftIndent=24)
|
||||
style_principle = ParagraphStyle('Principle', fontName='Times-Bold', fontSize=11,
|
||||
leading=15, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10,
|
||||
backColor=HexColor('#F4F6FA'), borderColor=NAVY, borderWidth=1, borderPadding=10)
|
||||
style_protocol = ParagraphStyle('Protocol', fontName='Times-Roman', fontSize=10,
|
||||
leading=14.5, alignment=TA_LEFT, textColor=DARK, spaceAfter=6, leftIndent=36)
|
||||
style_hisbah = ParagraphStyle('Hisbah', fontName='Times-Italic', fontSize=10.5,
|
||||
leading=15, alignment=TA_CENTER, textColor=GREY, spaceAfter=16, spaceBefore=16,
|
||||
backColor=HexColor('#F8F9FA'), borderColor=GREY, borderWidth=0.5, borderPadding=12)
|
||||
style_toc_ch = ParagraphStyle('TOCCh', fontName='Helvetica-Bold', fontSize=11,
|
||||
leading=17, alignment=TA_LEFT, textColor=NAVY)
|
||||
style_toc_sub = ParagraphStyle('TOCSub', fontName='Helvetica', fontSize=9.5,
|
||||
leading=13, alignment=TA_LEFT, textColor=GREY)
|
||||
|
||||
CHAPTERS = [
|
||||
("Clause 1: The Wasiyyah — The Will That God Commanded", "The Will as Sunnah", "Wasiyyah Quadrant: Mandatory / Recommended / Permitted / Nullified — the 1/3 cap"),
|
||||
("Clause 2: The Faraid Matrix — The Fixed Shares Decoded", "God's Distribution Algorithm", "Faraid Matrix: Ashab al-Furud / 1/2, 1/4, 1/8, 2/3, 1/3, 1/6"),
|
||||
("Clause 3: The Waqf — The Three Exits of Wealth", "Wasiyyah vs Waqf vs Gift", "Exit Quadrant: Wasiyyah / Waqf / Hibah / Inheritance"),
|
||||
("Clause 4: The Digital Kingdom — Inventorying the Invisible Estate", "The Estate Nobody Can See", "Digital Inventory: Crypto / Domains / IP / SaaS / DAO / Social"),
|
||||
("Clause 5: The Keys — Custody and the Executor Who Cannot Be Bribed", "Access Is the Whole Game", "Custody Quadrant: Hot / Cold / Multi-sig / Inherited"),
|
||||
("Clause 6: The Amil — Choosing the Executor of the Digital Estate", "The Executor as Deputy of the Waqif", "Executor Quadrant: Family / Professional / Institutional / Hybrid"),
|
||||
("Clause 7: The Purification — Debt, Zakat, and Cleaning the Estate", "The Estate Must Be Clean Before It Is Shared", "Purification Order: Burial → Debts → Wasiyyah (1/3) → Faraid"),
|
||||
("Clause 8: The Token — Smart-Contract Faraid and On-Chain Waqf", "The Code That Distributes What We Cannot", "Tokenization Quadrant: Smart wills / Multi-sig / Time-locked / On-chain"),
|
||||
("Clause 9: The Successor — Business Continuity for the Digital Kingdom", "The Business That Survives Its Founder", "Continuity Quadrant: Sole / Partnership / Company / Waqf-owned"),
|
||||
("Clause 10: The Perpetuity — The Eternal Contract of the Digital Waqif", "From Legacy to Ongoing Charity", "Perpetuity Quadrant: Exit / Inheritance / Waqf / Sadaqah Jariyah"),
|
||||
]
|
||||
|
||||
def clean_text(text):
|
||||
# Minimal markdown→HTML conversion for reportlab
|
||||
import re
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
|
||||
text = re.sub(r'(?<!\*)\*([^*]+)\*(?!\*)', r'<i>\1</i>', text)
|
||||
text = re.sub(r'`([^`]+)`', r'<font face="Courier">\1</font>', text)
|
||||
text = text.replace('\\n', '\n')
|
||||
# Strip remaining markdown headers etc
|
||||
text = re.sub(r'^#{1,6}\s*', '', text, flags=re.MULTILINE)
|
||||
# Escape any stray angle brackets that would break reportlab
|
||||
text = text.replace('&', '&').replace('<b>', '<b>').replace('</b>', '</b>')
|
||||
text = text.replace('<i>', '<i>').replace('</i>', '</i>')
|
||||
text = text.replace('<font face="Courier">', '<font>').replace('</font>', '</font>')
|
||||
# Now re-apply our tags (since we escaped the originals)
|
||||
text = re.sub(r'<b>', '<b>', text)
|
||||
text = re.sub(r'</b>', '</b>', text)
|
||||
text = re.sub(r'<i>', '<i>', text)
|
||||
text = re.sub(r'</i>', '</i>', text)
|
||||
text = re.sub(r'<font>(.+?)</font>', r'<font face="Courier">\1</font>', text)
|
||||
text = re.sub(r'&', '&', text)
|
||||
return text
|
||||
|
||||
def parse_chapter(md_text):
|
||||
sections = {'clause': [], 'nass': [], 'faradi': [], 'waqif': [], 'declaration': [], 'execution': [], 'hisbah': []}
|
||||
current = None
|
||||
for line in md_text.split('\n'):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
low = stripped.lower()
|
||||
if low.startswith('# the clause') or low.startswith('## the clause') or low == 'the clause' or low.startswith('clause 1:'):
|
||||
current = 'clause'
|
||||
continue
|
||||
elif low.startswith('# the nass') or low.startswith('## the nass') or low.startswith('the nass') or low == 'nass':
|
||||
current = 'nass'
|
||||
continue
|
||||
elif low.startswith('# faradi') or low.startswith('## faradi') or low.startswith('faradi') or low.startswith('**faradi'):
|
||||
current = 'faradi'
|
||||
continue
|
||||
elif low.startswith('# waqif') or low.startswith('## waqif') or low.startswith('waqif') or low.startswith('**waqif'):
|
||||
current = 'waqif'
|
||||
continue
|
||||
elif low.startswith('# the declaration') or low.startswith('## the declaration') or low.startswith('the declaration') or low.startswith('declaration'):
|
||||
current = 'declaration'
|
||||
continue
|
||||
elif low.startswith('# the execution') or low.startswith('## the execution') or low.startswith('the execution') or low.startswith('execution'):
|
||||
current = 'execution'
|
||||
continue
|
||||
elif low.startswith('# the hisbah') or low.startswith('## the hisbah') or low.startswith('the hisbah') or low.startswith('hisbah') or low.startswith('muhasaba'):
|
||||
current = 'hisbah'
|
||||
continue
|
||||
if stripped.startswith('#'):
|
||||
continue
|
||||
if stripped == '---' or stripped == '***':
|
||||
continue
|
||||
if current:
|
||||
sections[current].append(stripped)
|
||||
for k in sections:
|
||||
sections[k] = '\n\n'.join(sections[k])
|
||||
return sections
|
||||
|
||||
class BookDoc(BaseDocTemplate):
|
||||
def __init__(self, *args, **kw):
|
||||
BaseDocTemplate.__init__(self, *args, **kw)
|
||||
|
||||
def footer_fn(canvas, doc):
|
||||
canvas.saveState()
|
||||
canvas.setFont('Helvetica', 8)
|
||||
canvas.setFillColor(HexColor('#999999'))
|
||||
canvas.drawCentredString(PAGE_W / 2, 0.35 * inch, f'— {doc.page} —')
|
||||
canvas.restoreState()
|
||||
|
||||
def build_pdf():
|
||||
os.makedirs(os.path.join(OUTPUT_DIR, 'pdf'), exist_ok=True)
|
||||
|
||||
frame = Frame(MARGIN, MARGIN + FOOTER_H,
|
||||
PAGE_W - 2 * MARGIN,
|
||||
PAGE_H - 2 * MARGIN - FOOTER_H, id='normal')
|
||||
doc = BookDoc(PDF_PATH, pagesize=(PAGE_W, PAGE_H),
|
||||
leftMargin=MARGIN, rightMargin=MARGIN,
|
||||
topMargin=MARGIN, bottomMargin=MARGIN,
|
||||
title="The Digital Waqif: Waqf, Faraid, and the Architecture of Legacy",
|
||||
author='Jauhari Che Wan')
|
||||
doc.addPageTemplates([PageTemplate(id='main', frames=frame, onPage=footer_fn)])
|
||||
|
||||
story = []
|
||||
|
||||
# Title page
|
||||
story.append(Spacer(1, PAGE_H * 0.16))
|
||||
story.append(Paragraph("The Digital Waqif", style_title))
|
||||
story.append(Paragraph("Waqf, Faraid, and the Architecture of Legacy", style_subtitle))
|
||||
story.append(Paragraph("10 Clauses of the Digital Will & Endowment", ParagraphStyle('Tagline',
|
||||
fontName='Helvetica-Oblique', fontSize=12, leading=15, alignment=TA_CENTER, textColor=HexColor('#666666'), spaceAfter=24)))
|
||||
story.append(Spacer(1, 14))
|
||||
story.append(Paragraph('A FARADI & WAQIF Playbook', ParagraphStyle('StyleLine',
|
||||
fontName='Helvetica', fontSize=10, leading=13, alignment=TA_CENTER, textColor=HexColor('#888888'), spaceAfter=20)))
|
||||
story.append(Spacer(1, 14))
|
||||
story.append(Paragraph('Jauhari Che Wan', style_author))
|
||||
story.append(Spacer(1, PAGE_H * 0.1))
|
||||
story.append(Paragraph('FARADI × WAQIF × MAQASID',
|
||||
ParagraphStyle('Tag', parent=style_copyright, fontName='Helvetica-Bold', fontSize=9, textColor=GOLD)))
|
||||
story.append(PageBreak())
|
||||
|
||||
# Copyright
|
||||
story.append(Spacer(1, PAGE_H * 0.28))
|
||||
for line in ['FARADI × WAQIF PLAYBOOK', '', 'The Digital Waqif', 'Jauhari Che Wan',
|
||||
'', 'Based on Ilm al-Faraid and Waqf Jurisprudence', 'Surah An-Nisa 4:11-12, 4:176', 'Maqasid al-Shariah',
|
||||
'', 'Copyright \u00a9 2025', 'All rights reserved',
|
||||
'', 'Typeset in Times / Helvetica', 'Printed in Malaysia']:
|
||||
if line == '':
|
||||
story.append(Spacer(1, 4))
|
||||
else:
|
||||
story.append(Paragraph(line, style_copyright))
|
||||
story.append(PageBreak())
|
||||
|
||||
# Dedication
|
||||
story.append(Spacer(1, PAGE_H * 0.28))
|
||||
story.append(Paragraph('For every founder with a seed phrase, a domain, and no will.', ParagraphStyle('Ded',
|
||||
fontName='Times-Italic', fontSize=11, leading=16, alignment=TA_CENTER, textColor=DARK, spaceAfter=24)))
|
||||
story.append(PageBreak())
|
||||
|
||||
# TOC
|
||||
story.append(Paragraph('Contents', ParagraphStyle('TOCHeader',
|
||||
fontName='Helvetica-Bold', fontSize=18, leading=24, alignment=TA_LEFT, textColor=NAVY, spaceAfter=20)))
|
||||
for i, (title, subtitle, _) in enumerate(CHAPTERS, 1):
|
||||
story.append(Paragraph(f'<b>Clause {i}</b>: {title}', style_toc_ch))
|
||||
story.append(Paragraph(f' <i>{subtitle}</i>', style_toc_sub))
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(PageBreak())
|
||||
|
||||
# Chapters
|
||||
for ch_num, (title, subtitle, framework) in enumerate(CHAPTERS, 1):
|
||||
fname = os.path.join(CHAPTERS_DIR, f"Clause_0{ch_num}.md") if ch_num < 10 else os.path.join(CHAPTERS_DIR, f"Clause_{ch_num}.md")
|
||||
if not os.path.exists(fname):
|
||||
print(f" WARNING: {fname} not found")
|
||||
continue
|
||||
|
||||
with open(fname, 'r', encoding='utf-8') as f:
|
||||
md = f.read()
|
||||
|
||||
print(f" Adding Clause #{ch_num}: {title}...")
|
||||
sections = parse_chapter(md)
|
||||
|
||||
# Chapter header
|
||||
story.append(Paragraph(f'Clause {ch_num}', style_ch_num))
|
||||
story.append(Paragraph(title, style_ch_title))
|
||||
story.append(Paragraph(subtitle, style_subtitle_ch))
|
||||
story.append(Paragraph(framework, style_framework))
|
||||
story.append(Spacer(1, 8))
|
||||
|
||||
# THE CLAUSE
|
||||
if sections['clause']:
|
||||
clause_text = clean_text(' '.join(sections['clause'].split('\n')))
|
||||
story.append(Paragraph(clause_text, style_clause))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
# THE NASS
|
||||
if sections['nass']:
|
||||
story.append(Paragraph('THE NASS — THE EVIDENCE', ParagraphStyle('NassHeader',
|
||||
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10)))
|
||||
for para in sections['nass'].split('\n\n'):
|
||||
clean = clean_text(para.strip())
|
||||
if clean:
|
||||
story.append(Paragraph(clean, style_nass))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
# FARADI'S READING
|
||||
if sections['faradi']:
|
||||
story.append(Paragraph('FARADI\u2019S READING — THE JUSTICE', ParagraphStyle('FaradiHeader',
|
||||
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10)))
|
||||
for para in sections['faradi'].split('\n\n'):
|
||||
clean = clean_text(para.strip())
|
||||
if not clean:
|
||||
continue
|
||||
low = clean.lower()
|
||||
if low.startswith('faradi:'):
|
||||
story.append(Paragraph(f'<b>FARADI:</b> {clean[7:].strip()}', style_faradi))
|
||||
else:
|
||||
story.append(Paragraph(clean, style_body))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
# WAQIF'S READING
|
||||
if sections['waqif']:
|
||||
story.append(Paragraph('WAQIF\u2019S READING — THE PERPETUITY', ParagraphStyle('WaqifHeader',
|
||||
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10)))
|
||||
for para in sections['waqif'].split('\n\n'):
|
||||
clean = clean_text(para.strip())
|
||||
if not clean:
|
||||
continue
|
||||
low = clean.lower()
|
||||
if low.startswith('waqif:'):
|
||||
story.append(Paragraph(f'<b>WAQIF:</b> {clean[6:].strip()}', style_waqif))
|
||||
else:
|
||||
story.append(Paragraph(clean, style_body))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
# THE DECLARATION
|
||||
if sections['declaration']:
|
||||
story.append(Paragraph('THE DECLARATION — THE LEGACY PRINCIPLE', ParagraphStyle('DeclHeader',
|
||||
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10)))
|
||||
for para in sections['declaration'].split('\n\n'):
|
||||
clean = clean_text(para.strip())
|
||||
if not clean:
|
||||
continue
|
||||
low = clean.lower()
|
||||
if low.startswith('hukm:'):
|
||||
story.append(Paragraph(f'<b>HUKM:</b> {clean[5:].strip()}', style_body))
|
||||
elif low.startswith('daleel:'):
|
||||
story.append(Paragraph(f'<b>DALEEL:</b> {clean[7:].strip()}', style_body))
|
||||
elif low.startswith('maqsad:'):
|
||||
story.append(Paragraph(f'<b>MAQSAD:</b> {clean[7:].strip()}', style_body))
|
||||
elif low.startswith('shurut:'):
|
||||
story.append(Paragraph(f'<b>SHURUT:</b> {clean[7:].strip()}', style_body))
|
||||
elif low.startswith('munkathirat:'):
|
||||
story.append(Paragraph(f'<b>MUNKATHIRAT:</b> {clean[12:].strip()}', style_body))
|
||||
else:
|
||||
story.append(Paragraph(clean, style_body))
|
||||
story.append(Spacer(1, 8))
|
||||
|
||||
# THE EXECUTION
|
||||
if sections['execution']:
|
||||
story.append(Paragraph('THE EXECUTION — DO THIS THIS WEEK', ParagraphStyle('ExecHeader',
|
||||
fontName='Helvetica-Bold', fontSize=12, leading=16, alignment=TA_LEFT, textColor=NAVY, spaceAfter=6, spaceBefore=10)))
|
||||
for i, para in enumerate(sections['execution'].split('\n\n'), 1):
|
||||
clean = clean_text(para.strip())
|
||||
if clean:
|
||||
if clean.lower().startswith(f'step {i}') or clean.lower().startswith(f'step {i}:'):
|
||||
clean = clean.split(':', 1)[-1].strip()
|
||||
story.append(Paragraph(f'<b>STEP {i}:</b> {clean}', style_protocol))
|
||||
story.append(Spacer(1, 8))
|
||||
|
||||
# THE HISBAH
|
||||
if sections['hisbah']:
|
||||
clean = clean_text(' '.join(sections['hisbah'].split('\n')))
|
||||
story.append(Paragraph(clean, style_hisbah))
|
||||
|
||||
story.append(PageBreak())
|
||||
|
||||
print('Building PDF...')
|
||||
doc.build(story)
|
||||
print(f'Done: {PDF_PATH}')
|
||||
|
||||
import subprocess
|
||||
result = subprocess.run(['wc', '-c', PDF_PATH], capture_output=True, text=True)
|
||||
size_kb = int(result.stdout.split()[0]) / 1024
|
||||
with open(PDF_PATH, 'rb') as f:
|
||||
data = f.read()
|
||||
pages = len(re.findall(rb'/Type\s*/Page[^s]', data))
|
||||
print(f'Pages: {pages}, Size: {size_kb:.0f} KB')
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_pdf()
|
||||
Reference in New Issue
Block a user