#!/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'\1', text) text = re.sub(r'(?\1', text) text = re.sub(r'`([^`]+)`', r'\1', 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>').replace('', '</b>') text = text.replace('', '<i>').replace('', '</i>') text = text.replace('', '<font>').replace('', '</font>') # Now re-apply our tags (since we escaped the originals) text = re.sub(r'<b>', '', text) text = re.sub(r'</b>', '', text) text = re.sub(r'<i>', '', text) text = re.sub(r'</i>', '', text) text = re.sub(r'<font>(.+?)</font>', r'\1', 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'Clause {i}: {title}', style_toc_ch)) story.append(Paragraph(f' {subtitle}', 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'FARADI: {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'WAQIF: {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'HUKM: {clean[5:].strip()}', style_body)) elif low.startswith('daleel:'): story.append(Paragraph(f'DALEEL: {clean[7:].strip()}', style_body)) elif low.startswith('maqsad:'): story.append(Paragraph(f'MAQSAD: {clean[7:].strip()}', style_body)) elif low.startswith('shurut:'): story.append(Paragraph(f'SHURUT: {clean[7:].strip()}', style_body)) elif low.startswith('munkathirat:'): story.append(Paragraph(f'MUNKATHIRAT: {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'STEP {i}: {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()