~/bns-rape-provisions/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/bns-rape-provisions/BNS_Rape_Provisions_Quick_Reference.pdf"
# ── Colour palette ──────────────────────────────────────────────
SAFFRON = colors.HexColor("#FF6600")
NAVY = colors.HexColor("#1A237E")
TEAL = colors.HexColor("#00695C")
RED_DARK = colors.HexColor("#B71C1C")
GREY_BG = colors.HexColor("#F5F5F5")
GREY_HEAD = colors.HexColor("#E8EAF6")
GOLD = colors.HexColor("#F9A825")
WHITE = colors.white
BLACK = colors.HexColor("#212121")
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=15*mm, leftMargin=15*mm,
topMargin=15*mm, bottomMargin=15*mm,
title="BNS Rape Provisions - Quick Reference",
author="Orris Study Aid",
)
W, H = A4
styles = getSampleStyleSheet()
# ── Custom Styles ────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
sTitle = S("sTitle", fontSize=22, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", leading=28)
sSubtitle = S("sSubtitle", fontSize=11, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica", leading=16)
sNote = S("sNote", fontSize=8.5, textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, fontName="Helvetica-Oblique", leading=12)
sSectionNum = S("sSectionNum", fontSize=13, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_LEFT, leading=16)
sSectionTitle = S("sSectionTitle", fontSize=11, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_LEFT, leading=14)
sBody = S("sBody", fontSize=9, textColor=BLACK, fontName="Helvetica",
leading=13, spaceAfter=3)
sBullet = S("sBullet", fontSize=9, textColor=BLACK, fontName="Helvetica",
leading=13, leftIndent=12, bulletIndent=4, spaceAfter=2)
sHeading2 = S("sHeading2", fontSize=11, textColor=NAVY,
fontName="Helvetica-Bold", leading=14, spaceBefore=6, spaceAfter=3)
sImportant = S("sImportant", fontSize=9, textColor=RED_DARK,
fontName="Helvetica-Bold", leading=13)
sFooter = S("sFooter", fontSize=7.5, textColor=colors.grey,
alignment=TA_CENTER, fontName="Helvetica-Oblique", leading=10)
sTableHead = S("sTableHead", fontSize=8.5, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11)
sTableCell = S("sTableCell", fontSize=8.5, textColor=BLACK,
fontName="Helvetica", alignment=TA_CENTER, leading=11)
sTableCellL = S("sTableCellL", fontSize=8.5, textColor=BLACK,
fontName="Helvetica", alignment=TA_LEFT, leading=11)
sTableCellB = S("sTableCellB", fontSize=8.5, textColor=BLACK,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11)
sLabelRed = S("sLabelRed", fontSize=8.5, textColor=RED_DARK,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11)
sLabelGreen = S("sLabelGreen", fontSize=8.5, textColor=TEAL,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11)
story = []
# ════════════════════════════════════════════════════════════════
# HEADER BANNER
# ════════════════════════════════════════════════════════════════
banner_data = [[
Paragraph("BHARATIYA NYAYA SANHITA, 2023", sTitle),
]]
banner = Table(banner_data, colWidths=[W - 30*mm])
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 14),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [6]),
]))
story.append(banner)
story.append(Spacer(1, 2*mm))
sub_data = [[
Paragraph("QUICK REFERENCE: RAPE PROVISIONS (Sections 63–73)", sSubtitle),
]]
sub_table = Table(sub_data, colWidths=[W - 30*mm])
sub_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), SAFFRON),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
story.append(sub_table)
story.append(Spacer(1, 2*mm))
note_data = [[
Paragraph("Chapter V | In force from July 1, 2024 | Replaces IPC Sections 375–376E", sNote),
]]
note_table = Table(note_data, colWidths=[W - 30*mm])
note_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
]))
story.append(note_table)
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# HELPER: Section card builder
# ════════════════════════════════════════════════════════════════
def section_card(sec_num, sec_title, ipc_ref, bg_color, content_rows):
"""Build a coloured section card with header + content rows."""
items = []
# Header row
header_data = [[
Paragraph(sec_num, sSectionNum),
Paragraph(sec_title, sSectionTitle),
Paragraph(ipc_ref, S("sIPC", fontSize=8, textColor=colors.HexColor("#BBDEFB"),
fontName="Helvetica-Oblique", alignment=TA_LEFT, leading=11)),
]]
header_table = Table(header_data, colWidths=[28*mm, 90*mm, 52*mm])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg_color),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
items.append(header_table)
# Content
for row in content_rows:
items.append(row)
items.append(Spacer(1, 3*mm))
return KeepTogether(items)
# ════════════════════════════════════════════════════════════════
# SECTION 63 – DEFINITION
# ════════════════════════════════════════════════════════════════
def body_table(rows, col_widths, header=True):
t = Table(rows, colWidths=col_widths)
style = [
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BDBDBD")),
("VALIGN",(0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]
if header:
style += [
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8.5),
]
t.setStyle(TableStyle(style))
return t
# ── Section 63 ──────────────────────────────────────────────────
s63_content = []
s63_content.append(Spacer(1, 2*mm))
s63_content.append(Paragraph("A man commits rape if he penetrates/inserts/applies mouth:", sBody))
acts = [
"Penis into vagina, mouth, urethra, or anus of a woman",
"Any object or body part (not penis) into vagina, urethra, or anus",
"Manipulates any body part to cause penetration",
"Applies mouth to vagina, anus, or urethra",
]
for a in acts:
s63_content.append(Paragraph(f"• {a}", sBullet))
s63_content.append(Spacer(1, 2*mm))
s63_content.append(Paragraph(
"<b>WITHOUT CONSENT</b> or <b>AGAINST HER WILL</b> — includes: intoxication, unsoundness of mind, "
"misrepresentation, impersonation, coercion, or consent given by a woman under 18.", sBody))
consent_data = [
[Paragraph("CONSENT DEFINED", sTableHead),
Paragraph("EXCEPTIONS", sTableHead)],
[Paragraph("Unequivocal voluntary agreement communicated by words, gestures, "
"or non-verbal communication.\nAbsence of physical resistance ≠ consent.", sTableCellL),
Paragraph("1. Medical procedure or intervention\n2. Sexual intercourse by husband with wife "
"(wife not below 18 years)", sTableCellL)],
]
s63_content.append(body_table(consent_data, [85*mm, 85*mm]))
story.append(section_card(
"§ 63", "RAPE — DEFINITION", "(IPC § 375)",
NAVY, s63_content
))
# ── Section 64 ──────────────────────────────────────────────────
s64_content = []
s64_content.append(Spacer(1, 2*mm))
s64_table = [
[Paragraph("Sub-section", sTableHead),
Paragraph("Category", sTableHead),
Paragraph("Minimum", sTableHead),
Paragraph("Maximum", sTableHead)],
[Paragraph("64(1)", sTableCell),
Paragraph("General / Simple Rape", sTableCellL),
Paragraph("10 Years RI", sTableCellB),
Paragraph("Life Imprisonment + Fine", sTableCellB)],
[Paragraph("64(2)", sTableCell),
Paragraph("Aggravated circumstances (see below)", sTableCellL),
Paragraph("10 Years RI", sTableCellB),
Paragraph("Life (natural life) + Fine", sTableCellB)],
]
s64_content.append(body_table(s64_table, [22*mm, 68*mm, 30*mm, 50*mm]))
s64_content.append(Spacer(1, 2*mm))
s64_content.append(Paragraph("<b>Aggravated circumstances under § 64(2):</b>", sBody))
circs = [
"Police officer (within jurisdiction/premises/custody)",
"Public servant or armed forces personnel",
"Staff of jail, remand home, or similar institution",
"Staff/management of a hospital (victim in that hospital)",
"Person in position of trust or authority (relative, guardian, teacher)",
"During communal or sectarian violence",
"Rape of a pregnant woman",
"Rape of a woman incapable of giving consent (mental/physical disability)",
"Repeated rape of the same woman",
"Rape of a woman under 16 years of age",
]
for c in circs:
s64_content.append(Paragraph(f"• {c}", sBullet))
story.append(section_card(
"§ 64", "PUNISHMENT FOR RAPE", "(IPC § 376)",
colors.HexColor("#1565C0"), s64_content
))
# ── Section 65 ──────────────────────────────────────────────────
s65_content = []
s65_content.append(Spacer(1, 2*mm))
s65_table = [
[Paragraph("Sub-section", sTableHead),
Paragraph("Victim's Age", sTableHead),
Paragraph("Minimum", sTableHead),
Paragraph("Maximum", sTableHead)],
[Paragraph("65(1)", sTableCell),
Paragraph("Under 16 years", sTableCell),
Paragraph("20 Years RI", sTableCellB),
Paragraph("Life (natural life) + Fine", sTableCellB)],
[Paragraph("65(2)", sTableCell),
Paragraph("Under 12 years", sTableCell),
Paragraph("20 Years RI", sTableCellB),
Paragraph("<font color='#B71C1C'><b>DEATH PENALTY</b></font>\nor Life (natural life) + Fine",
sTableCellB)],
]
s65_content.append(body_table(s65_table, [22*mm, 40*mm, 35*mm, 73*mm]))
s65_content.append(Spacer(1, 1*mm))
s65_content.append(Paragraph(
"<b>Note:</b> Fine must cover medical expenses and rehabilitation of the victim "
"and is paid directly to the victim.", sBody))
story.append(section_card(
"§ 65", "RAPE OF MINORS", "(IPC § 376AB / 376(3))",
RED_DARK, s65_content
))
# ── Section 66 ──────────────────────────────────────────────────
s66_content = []
s66_content.append(Spacer(1, 2*mm))
s66_content.append(Paragraph(
"Where rape results in the <b>death of the victim</b> or leaves the victim "
"in a <b>persistent vegetative state</b>:", sBody))
s66_table = [
[Paragraph("Minimum Sentence", sTableHead),
Paragraph("Maximum Sentence", sTableHead)],
[Paragraph("20 Years Rigorous Imprisonment", sTableCellB),
Paragraph("<font color='#B71C1C'><b>DEATH PENALTY</b></font> or Life Imprisonment",
sTableCellB)],
]
s66_content.append(body_table(s66_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 66", "RAPE CAUSING DEATH / VEGETATIVE STATE", "(IPC § 376A)",
colors.HexColor("#6A0000"), s66_content
))
# ── Section 67 ──────────────────────────────────────────────────
s67_content = []
s67_content.append(Spacer(1, 2*mm))
s67_content.append(Paragraph(
"Sexual intercourse by a <b>husband with his wife during judicial separation</b> "
"(without her consent):", sBody))
s67_table = [
[Paragraph("Minimum", sTableHead), Paragraph("Maximum", sTableHead)],
[Paragraph("2 Years + Fine", sTableCellB), Paragraph("7 Years + Fine", sTableCellB)],
]
s67_content.append(body_table(s67_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 67", "MARITAL RAPE DURING SEPARATION", "(IPC § 376B)",
TEAL, s67_content
))
# ── Section 68 ──────────────────────────────────────────────────
s68_content = []
s68_content.append(Spacer(1, 2*mm))
s68_content.append(Paragraph(
"Sexual intercourse (not amounting to rape) by a person in <b>position of authority</b> "
"who abuses that position (public servant, jail superintendent, hospital management, etc.):",
sBody))
s68_table = [
[Paragraph("Minimum", sTableHead), Paragraph("Maximum", sTableHead)],
[Paragraph("5 Years + Fine", sTableCellB), Paragraph("10 Years + Fine", sTableCellB)],
]
s68_content.append(body_table(s68_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 68", "SEXUAL INTERCOURSE BY PERSON IN AUTHORITY", "(IPC § 376C)",
colors.HexColor("#00695C"), s68_content
))
# ── Section 69 ──────────────────────────────────────────────────
s69_content = []
s69_content.append(Spacer(1, 2*mm))
s69_content.append(Paragraph(
"Sexual intercourse by <b>deceitful means</b> or <b>false promise of marriage</b> "
"(not amounting to rape):", sBody))
s69_content.append(Paragraph(
'<b>Deceitful means includes:</b> false promise of employment/promotion, '
'inducement, or marrying after suppressing identity.', sBody))
s69_table = [
[Paragraph("Minimum", sTableHead), Paragraph("Maximum", sTableHead)],
[Paragraph("No minimum specified", sTableCell),
Paragraph("10 Years + Fine", sTableCellB)],
]
s69_content.append(body_table(s69_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 69", "DECEITFUL MEANS / FALSE PROMISE OF MARRIAGE", "(NEW — No IPC equivalent)",
colors.HexColor("#4527A0"), s69_content
))
# ── Section 70 ──────────────────────────────────────────────────
s70_content = []
s70_content.append(Spacer(1, 2*mm))
s70_content.append(Paragraph(
"Where a woman is raped by <b>one or more persons constituting a group</b> or acting in "
"furtherance of a common intention — each person deemed to have committed rape:", sBody))
s70_table = [
[Paragraph("Sub-section", sTableHead),
Paragraph("Category", sTableHead),
Paragraph("Minimum", sTableHead),
Paragraph("Maximum", sTableHead)],
[Paragraph("70(1)", sTableCell),
Paragraph("Gang Rape (general)", sTableCellL),
Paragraph("20 Years RI", sTableCellB),
Paragraph("Life (natural life) + Fine\n(paid to victim)", sTableCellB)],
[Paragraph("70(2)", sTableCell),
Paragraph("Gang Rape of woman under 18", sTableCellL),
Paragraph("Life (natural life)", sTableCellB),
Paragraph("<font color='#B71C1C'><b>DEATH PENALTY</b></font>\n+ Fine (paid to victim)",
sTableCellB)],
]
s70_content.append(body_table(s70_table, [22*mm, 55*mm, 30*mm, 63*mm]))
story.append(section_card(
"§ 70", "GANG RAPE", "(IPC § 376D)",
colors.HexColor("#880E4F"), s70_content
))
# ── Section 71 ──────────────────────────────────────────────────
s71_content = []
s71_content.append(Spacer(1, 2*mm))
s71_content.append(Paragraph(
"Person <b>previously convicted</b> under §64, §65, §66, or §70 "
"and subsequently convicted again under any of those sections:", sBody))
s71_table = [
[Paragraph("Minimum", sTableHead), Paragraph("Maximum", sTableHead)],
[Paragraph("Life Imprisonment (natural life)", sTableCellB),
Paragraph("<font color='#B71C1C'><b>DEATH PENALTY</b></font>", sTableCellB)],
]
s71_content.append(body_table(s71_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 71", "REPEAT OFFENDERS", "(IPC § 376E)",
colors.HexColor("#BF360C"), s71_content
))
# ── Section 72 ──────────────────────────────────────────────────
s72_content = []
s72_content.append(Spacer(1, 2*mm))
s72_content.append(Paragraph(
"Printing/publishing name, address, photograph, or any matter that could lead to "
"identification of rape victim (in print or electronic media):", sBody))
s72_table = [
[Paragraph("Punishment", sTableHead), Paragraph("Exceptions", sTableHead)],
[Paragraph("Up to 2 Years Imprisonment + Fine", sTableCellB),
Paragraph("1. Disclosure by police officer during investigation\n"
"2. Written consent of victim\n"
"3. Disclosure to next of kin (limited)", sTableCellL)],
]
s72_content.append(body_table(s72_table, [85*mm, 85*mm]))
story.append(section_card(
"§ 72", "DISCLOSURE OF VICTIM IDENTITY", "(IPC § 228A)",
colors.HexColor("#004D40"), s72_content
))
# ── Section 73 ──────────────────────────────────────────────────
s73_content = []
s73_content.append(Spacer(1, 2*mm))
s73_content.append(Paragraph(
"Printing/publishing matter relating to court proceedings in rape cases "
"without court authorisation:", sBody))
s73_table = [
[Paragraph("Punishment", sTableHead)],
[Paragraph("Up to 2 Years Imprisonment + Fine", sTableCellB)],
]
s73_content.append(body_table(s73_table, [170*mm]))
story.append(section_card(
"§ 73", "PUBLISHING COURT PROCEEDINGS", "(IPC § 327)",
colors.HexColor("#37474F"), s73_content
))
# ════════════════════════════════════════════════════════════════
# MASTER SUMMARY TABLE
# ════════════════════════════════════════════════════════════════
story.append(Spacer(1, 4*mm))
story.append(HRFlowable(width="100%", thickness=1.5, color=SAFFRON))
story.append(Spacer(1, 2*mm))
story.append(Paragraph("MASTER QUICK REFERENCE TABLE", sHeading2))
summary_data = [
[Paragraph("Section", sTableHead),
Paragraph("Offence", sTableHead),
Paragraph("IPC Ref", sTableHead),
Paragraph("Min. Punishment", sTableHead),
Paragraph("Max. Punishment", sTableHead)],
["§ 63", Paragraph("Definition of Rape", sTableCellL),
"§ 375", "-", "-"],
["§ 64(1)", Paragraph("Simple/General Rape", sTableCellL),
"§ 376", Paragraph("10 Yrs RI + Fine", sTableCellB),
Paragraph("Life + Fine", sTableCellB)],
["§ 64(2)", Paragraph("Aggravated Rape\n(authority/trust/pregnancy etc.)", sTableCellL),
"§ 376", Paragraph("10 Yrs RI + Fine", sTableCellB),
Paragraph("Life (natural) + Fine", sTableCellB)],
["§ 65(1)", Paragraph("Rape of girl under 16", sTableCellL),
"§ 376(3)", Paragraph("20 Yrs RI + Fine", sTableCellB),
Paragraph("Life (natural) + Fine", sTableCellB)],
["§ 65(2)", Paragraph("Rape of girl under 12", sTableCellL),
"§ 376AB", Paragraph("20 Yrs RI + Fine", sTableCellB),
Paragraph(u"DEATH / Life (natural)", sLabelRed)],
["§ 66", Paragraph("Rape causing death or\npersistent vegetative state", sTableCellL),
"§ 376A", Paragraph("20 Yrs RI", sTableCellB),
Paragraph(u"DEATH / Life", sLabelRed)],
["§ 67", Paragraph("Husband + wife during\njudicial separation", sTableCellL),
"§ 376B", Paragraph("2 Yrs + Fine", sTableCellB),
Paragraph("7 Yrs + Fine", sTableCellB)],
["§ 68", Paragraph("Sex by person in\nauthority (not rape)", sTableCellL),
"§ 376C", Paragraph("5 Yrs + Fine", sTableCellB),
Paragraph("10 Yrs + Fine", sTableCellB)],
["§ 69", Paragraph("Deceitful means /\nfalse marriage promise", sTableCellL),
"NEW", Paragraph("No minimum", sTableCell),
Paragraph("10 Yrs + Fine", sTableCellB)],
["§ 70(1)", Paragraph("Gang Rape (general)", sTableCellL),
"§ 376D", Paragraph("20 Yrs RI + Fine", sTableCellB),
Paragraph("Life (natural) + Fine\n(to victim)", sTableCellB)],
["§ 70(2)", Paragraph("Gang Rape of under-18", sTableCellL),
"§ 376D", Paragraph("Life (natural) + Fine", sTableCellB),
Paragraph(u"DEATH + Fine\n(to victim)", sLabelRed)],
["§ 71", Paragraph("Repeat Offender", sTableCellL),
"§ 376E", Paragraph("Life (natural)", sTableCellB),
Paragraph(u"DEATH", sLabelRed)],
["§ 72", Paragraph("Disclosure of victim identity", sTableCellL),
"§ 228A", "-",
Paragraph("2 Yrs + Fine", sTableCellB)],
["§ 73", Paragraph("Publishing court proceedings", sTableCellL),
"§ 327", "-",
Paragraph("2 Yrs + Fine", sTableCellB)],
]
# Alternate row shading
sum_style = [
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BDBDBD")),
("BACKGROUND", (0,0), (-1, 0), NAVY),
("FONTNAME", (0,0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (0, -1), "CENTER"),
("FONTNAME", (0,1), (0, -1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0, -1), NAVY),
]
for i in range(1, len(summary_data)):
if i % 2 == 0:
sum_style.append(("BACKGROUND", (0, i), (-1, i), GREY_BG))
# Highlight death penalty rows
death_rows = [6, 7, 12, 13] # 65(2), 66, 70(2), 71
for r in death_rows:
sum_style.append(("BACKGROUND", (0, r), (-1, r), colors.HexColor("#FFEBEE")))
sum_table = Table(summary_data, colWidths=[16*mm, 46*mm, 16*mm, 40*mm, 52*mm])
sum_table.setStyle(TableStyle(sum_style))
story.append(sum_table)
# ════════════════════════════════════════════════════════════════
# KEY NOTES BOX
# ════════════════════════════════════════════════════════════════
story.append(Spacer(1, 4*mm))
key_notes = [
[Paragraph("KEY POINTS TO REMEMBER", S("kh", fontSize=10, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=14))],
[Paragraph(
"<b>1. RI</b> = Rigorous Imprisonment (hard labour mandatory) | "
"<b>Life (natural)</b> = imprisonment for remainder of natural life (not 14 yrs) | "
"<b>Fine</b> in §§65,70 must be paid directly to the victim for medical/rehabilitation expenses.\n"
"<b>2. In force:</b> BNS came into force on <b>July 1, 2024</b>. All offences registered after this date are under BNS, not IPC.\n"
"<b>3. Death penalty</b> is available under: §65(2) [under-12 rape], §66 [rape causing death/PVS], §70(2) [gang rape of under-18], §71 [repeat offender].\n"
"<b>4. Marital rape exception</b> (§63 Exception 2) applies only if wife is 18+ and parties are NOT under judicial separation.\n"
"<b>5. §69</b> (false promise of marriage) is a new BNS provision with no direct IPC equivalent.",
S("kn", fontSize=8.5, textColor=BLACK, fontName="Helvetica", leading=13)
)],
]
kn_table = Table(key_notes, colWidths=[170*mm])
kn_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#1A237E")),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#EDE7F6")),
("BOX", (0,0), (-1,-1), 1, NAVY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
story.append(kn_table)
# ── Footer ───────────────────────────────────────────────────────
story.append(Spacer(1, 4*mm))
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey))
story.append(Spacer(1, 1*mm))
story.append(Paragraph(
"For academic/study purposes only. Always verify with the official Gazette of India notification. "
"Bharatiya Nyaya Sanhita, 2023 (No. 45 of 2023) | Ministry of Home Affairs, Government of India",
sFooter))
# ════════════════════════════════════════════════════════════════
# BUILD
# ════════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF created: {OUTPUT}")