~/berry-aneurysm/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import HRFlowable
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
import os
# ── Colour palette ──────────────────────────────────────────────────
DEEP_RED = HexColor("#8B1A1A")
DARK_RED = HexColor("#C0392B")
LIGHT_RED = HexColor("#FADBD8")
PALE_GREY = HexColor("#F4F6F7")
MID_GREY = HexColor("#BDC3C7")
DARK_GREY = HexColor("#2C3E50")
ACCENT_BLUE = HexColor("#2E86AB")
WHITE = colors.white
BLACK = colors.black
OUTPUT = "/home/daytona/workspace/berry-aneurysm/Berry_Aneurysm_Summary.pdf"
# ── Page canvas callbacks (header / footer) ─────────────────────────
def on_page(canvas_obj, doc):
W, H = A4
# Top rule
canvas_obj.setStrokeColor(DEEP_RED)
canvas_obj.setLineWidth(2)
canvas_obj.line(1.5*cm, H - 1.2*cm, W - 1.5*cm, H - 1.2*cm)
# Footer
canvas_obj.setFont("Helvetica", 7)
canvas_obj.setFillColor(MID_GREY)
canvas_obj.drawString(1.5*cm, 0.8*cm, "Berry Aneurysm – Pathology & Morphology Summary")
canvas_obj.drawRightString(W - 1.5*cm, 0.8*cm, f"Page {doc.page}")
canvas_obj.setStrokeColor(MID_GREY)
canvas_obj.setLineWidth(0.5)
canvas_obj.line(1.5*cm, 1.2*cm, W - 1.5*cm, 1.2*cm)
def on_first_page(canvas_obj, doc):
on_page(canvas_obj, doc)
# ── Styles ───────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
"DocTitle",
parent=styles["Title"],
fontSize=26,
textColor=WHITE,
alignment=TA_CENTER,
spaceAfter=4,
fontName="Helvetica-Bold",
)
subtitle_style = ParagraphStyle(
"DocSubtitle",
parent=styles["Normal"],
fontSize=11,
textColor=HexColor("#FADBD8"),
alignment=TA_CENTER,
spaceAfter=2,
fontName="Helvetica",
)
source_style = ParagraphStyle(
"Source",
parent=styles["Normal"],
fontSize=8,
textColor=HexColor("#FADBD8"),
alignment=TA_CENTER,
fontName="Helvetica-Oblique",
)
h1 = ParagraphStyle(
"H1",
parent=styles["Heading1"],
fontSize=14,
textColor=WHITE,
fontName="Helvetica-Bold",
spaceBefore=14,
spaceAfter=6,
leftIndent=0,
borderPad=6,
)
h2 = ParagraphStyle(
"H2",
parent=styles["Heading2"],
fontSize=11,
textColor=DEEP_RED,
fontName="Helvetica-Bold",
spaceBefore=10,
spaceAfter=4,
borderColor=DEEP_RED,
)
body = ParagraphStyle(
"Body",
parent=styles["Normal"],
fontSize=9.5,
textColor=DARK_GREY,
fontName="Helvetica",
spaceAfter=5,
leading=14,
alignment=TA_JUSTIFY,
)
bullet = ParagraphStyle(
"Bullet",
parent=body,
leftIndent=14,
bulletIndent=4,
spaceBefore=1,
spaceAfter=3,
)
caption = ParagraphStyle(
"Caption",
parent=styles["Normal"],
fontSize=8,
textColor=HexColor("#566573"),
fontName="Helvetica-Oblique",
alignment=TA_CENTER,
spaceAfter=8,
spaceBefore=3,
)
small = ParagraphStyle(
"Small",
parent=body,
fontSize=8,
textColor=HexColor("#566573"),
)
# ── Helper: coloured section header banner ────────────────────────────
def section_header(text, bg=DEEP_RED):
data = [[Paragraph(f"<font color='white'><b>{text}</b></font>", h1)]]
t = Table(data, colWidths=[17.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [4]),
]))
return t
# ── Helper: two-column info box ───────────────────────────────────────
def info_table(rows, col_widths=(6*cm, 11.5*cm)):
data = []
for label, value in rows:
data.append([
Paragraph(f"<b>{label}</b>", small),
Paragraph(value, small),
])
t = Table(data, colWidths=col_widths)
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), PALE_GREY),
("BACKGROUND", (1, 0), (1, -1), WHITE),
("GRID", (0, 0), (-1, -1), 0.4, MID_GREY),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]))
return t
# ── Helper: styled data table ─────────────────────────────────────────
def data_table(headers, rows, col_widths=None):
header_row = [Paragraph(f"<b><font color='white'>{h}</font></b>", small) for h in headers]
body_rows = [[Paragraph(str(c), small) for c in row] for row in rows]
all_rows = [header_row] + body_rows
if col_widths is None:
col_widths = [17.5*cm / len(headers)] * len(headers)
t = Table(all_rows, colWidths=col_widths, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), DEEP_RED),
("BACKGROUND", (0, 1), (-1, -1), WHITE),
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, PALE_GREY]),
("GRID", (0, 0), (-1, -1), 0.4, MID_GREY),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 5),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("FONTSIZE", (0, 0), (-1, -1), 8.5),
]))
return t
# ── Helper: highlight box ────────────────────────────────────────────
def highlight_box(text, bg=LIGHT_RED, border=DARK_RED):
data = [[Paragraph(text, ParagraphStyle("hb", parent=body, fontSize=9, spaceAfter=0))]]
t = Table(data, colWidths=[17.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("LINEAFTER", (0,0), (0,-1), 4, border),
("BOX", (0,0), (-1,-1), 0.5, border),
]))
return t
# ════════════════════════════════════════════════════════════════════
# BUILD STORY
# ════════════════════════════════════════════════════════════════════
story = []
W, H = A4
# ── COVER BANNER ────────────────────────────────────────────────────
cover_data = [[
Paragraph("Berry (Saccular) Aneurysm", title_style),
Paragraph("Pathology & Morphology – Summary Reference", subtitle_style),
Paragraph("Sources: Robbins, Cotran & Kumar Pathologic Basis of Disease | Harrison's Principles of Internal Medicine 22e | Neuroanatomy through Clinical Cases 3e", source_style),
]]
cover = Table(cover_data, colWidths=[17.5*cm])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DEEP_RED),
("TOPPADDING", (0,0), (-1,-1), 22),
("BOTTOMPADDING", (0,0), (-1,-1), 22),
("LEFTPADDING", (0,0), (-1,-1), 16),
("RIGHTPADDING", (0,0), (-1,-1), 16),
("ROUNDEDCORNERS", [6]),
]))
story.append(cover)
story.append(Spacer(1, 0.5*cm))
# ── 1. DEFINITION ───────────────────────────────────────────────────
story.append(section_header("1. Definition"))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"A <b>berry (saccular) aneurysm</b> is a thin-walled, round or lobulated outpouching arising at the "
"<b>bifurcation points of intracranial arteries</b>, most commonly those forming the Circle of Willis. "
"It is the <b>most common type of intracranial aneurysm</b> and the leading cause of spontaneous "
"subarachnoid haemorrhage (SAH). The term 'berry' refers to its rounded, berry-like gross appearance.",
body
))
story.append(Spacer(1, 0.3*cm))
# ── 2. EPIDEMIOLOGY ─────────────────────────────────────────────────
story.append(section_header("2. Epidemiology"))
story.append(Spacer(1, 0.2*cm))
story.append(info_table([
("Prevalence", "~2% of the adult population; ~4 million persons in the United States"),
("SAH incidence", "6–11 per 100,000 person-years; ~25,000–30,000 cases/year in the US"),
("Peak age", "Fifth decade (40s–50s); incidence increases with age"),
("Sex", "Slightly more frequent in females (1.3× relative risk)"),
("Multiple aneurysms", "20–30% of cases have multiple aneurysms, many at mirror sites bilaterally"),
("Location", "~90% in anterior circulation; 89% on or near Circle of Willis"),
("Mortality (1st rupture)", "25–50% die immediately or before reaching hospital; overall SAH mortality ~35%"),
]))
story.append(Spacer(1, 0.4*cm))
# ── 3. SITES / FREQUENCY ────────────────────────────────────────────
story.append(section_header("3. Common Sites of Occurrence"))
story.append(Spacer(1, 0.2*cm))
# two columns: table left, image right
img_path = "/home/daytona/workspace/berry-aneurysm/aneurysm_sites.png"
site_rows = [
["Anterior communicating artery (AComm)", "38% – Most common"],
["Anterior cerebral artery (ACA)", "36%"],
["Middle cerebral artery (MCA) bifurcation","21%"],
["Basilar artery / vertebrobasilar", "5%"],
["Posterior communicating artery (PComm)", "Clinically important – CN III palsy"],
["Internal carotid artery (ICA) terminus", "Giant aneurysms common here"],
]
site_table = data_table(
["Location", "Frequency / Notes"],
site_rows,
col_widths=[8.5*cm, 5*cm]
)
img_w = 4*cm
try:
from PIL import Image as PILImage
pil = PILImage.open(img_path)
orig_w, orig_h = pil.size
img_h = img_w * orig_h / orig_w
except Exception:
img_h = 5*cm
site_img = Image(img_path, width=img_w, height=img_h) if os.path.exists(img_path) else Spacer(1,1)
col_layout = Table(
[[site_table, site_img]],
colWidths=[13.5*cm, 4*cm]
)
col_layout.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
]))
story.append(col_layout)
story.append(Paragraph(
"Fig. 1. Common sites of saccular (berry) aneurysm in the Circle of Willis "
"(Bailey & Love; Robbins PBD Fig. 28.15).",
caption
))
# ── 4. PATHOGENESIS ─────────────────────────────────────────────────
story.append(section_header("4. Pathogenesis"))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("<b>Structural Wall Defect:</b>", h2))
story.append(Paragraph(
"Saccular aneurysms are not present at birth but develop over time due to an <b>underlying defect "
"in the tunica media</b> of the vessel wall. The arterial internal elastic lamina <b>disappears at the "
"base of the neck</b>. The media thins and smooth-muscle cells are replaced by connective tissue. "
"At the site of rupture (usually the dome), the wall is at its thinnest and the tear "
"allowing bleeding is often ≤0.5 mm long.",
body
))
story.append(Paragraph("<b>Haemodynamic Stress:</b>", h2))
story.append(Paragraph(
"Turbulent blood flow at arterial bifurcation points creates focal endothelial dysfunction. "
"Over time this progressive haemodynamic stress, combined with the structural wall defect, "
"promotes outpouching and enlargement. <b>Hypertension</b> (present in ~50% of affected individuals) "
"and <b>smoking</b> are major acquired risk factors that accelerate this process.",
body
))
story.append(Paragraph("<b>Molecular / Genetic Factors:</b>", h2))
story.append(Paragraph(
"The majority of saccular aneurysms occur sporadically. However, genetic factors are important: "
"there is an increased incidence in first-degree relatives of affected individuals, and a "
"significantly higher incidence in the following Mendelian disorders:",
body
))
genetic_rows = [
["Autosomal dominant polycystic kidney disease (ADPKD)", "Most strongly associated; connective tissue defect in arterial walls"],
["Ehlers-Danlos syndrome type IV", "Defective Type III collagen"],
["Marfan syndrome", "Fibrillin-1 mutation; connective tissue weakness"],
["Neurofibromatosis type 1 (NF1)", "Vascular dysplasia"],
["Fibromuscular dysplasia", "Non-inflammatory arterial disease"],
["Coarctation of the aorta", "Altered haemodynamics and hypertension"],
]
story.append(data_table(
["Associated Condition", "Mechanism"],
genetic_rows,
col_widths=[8*cm, 9.5*cm]
))
story.append(Spacer(1, 0.3*cm))
story.append(highlight_box(
"<b>Key Point:</b> Although called 'congenital', saccular aneurysms are NOT present at birth. "
"They develop over decades from an underlying medial defect combined with haemodynamic wear at bifurcation points."
))
story.append(Spacer(1, 0.4*cm))
# ── 5. MORPHOLOGY ──────────────────────────────────────────────────
story.append(section_header("5. Morphology"))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("<b>Gross Appearance:</b>", h2))
morph_rows = [
("Shape", "Round or lobulated thin-walled outpouching; 'berry-like'"),
("Size", "Few mm to 2–3 cm in diameter. Giant aneurysms >25 mm (5% of cases)"),
("Surface", "Bright red, shiny, translucent wall"),
("Location", "Arise at arterial branch point; have a neck (narrow or wide) and a dome"),
("Neck", "Site where aneurysm meets parent artery; no media or elastic lamina"),
("Dome", "Thinnest part; site of rupture; tear usually ≤0.5 mm"),
("Contents", "Atheromatous plaques, calcification, or mural thrombi may be present in wall/lumen"),
("Prior bleeds","Orange-brown discolouration of adjacent brain and meninges"),
]
story.append(info_table(morph_rows))
story.append(Spacer(1, 0.3*cm))
# Gross image
img2_path = "/home/daytona/workspace/berry-aneurysm/berry_gross.png"
if os.path.exists(img2_path):
try:
from PIL import Image as PILImage
pil2 = PILImage.open(img2_path)
o2w, o2h = pil2.size
iw2 = 14*cm
ih2 = iw2 * o2h / o2w
except Exception:
iw2, ih2 = 14*cm, 5*cm
story.append(Image(img2_path, width=iw2, height=ih2))
story.append(Paragraph(
"Fig. 2. Berry aneurysm – gross and microscopic morphology. "
"(A) Base of brain with aneurysm of ACA (arrow). "
"(B) Dissected Circle of Willis showing large aneurysm. "
"(C) Histology: hyalinized fibrous wall (H&E). "
"Source: Robbins, Cotran & Kumar PBD, Fig. 28.16.",
caption
))
story.append(Paragraph("<b>Histological Appearance:</b>", h2))
histo_rows = [
("Normal artery wall", "Intima + Internal elastic lamina + Tunica media (smooth muscle) + Adventitia"),
("Aneurysm neck", "Intimal thickening; media attenuated; elastic lamina absent"),
("Aneurysm sac (dome)", "Thickened hyalinized intima + adventitia ONLY. No media. No elastic lamina."),
("Rupture site", "Wall thinning to near transparency; tear ≤0.5 mm at dome apex"),
("Secondary changes", "Intraluminal thrombus; atheromatous change; calcification in chronic cases"),
]
story.append(info_table(histo_rows))
story.append(Spacer(1, 0.3*cm))
story.append(highlight_box(
"<b>Histological Key:</b> The aneurysm sac wall consists of <b>thickened hyalinized intima</b> "
"covered only by <b>adventitia</b>. There is a complete <b>absence of smooth muscle and internal "
"elastic lamina</b> – this distinguishes it from a normal artery or atherosclerotic fusiform aneurysm."
))
story.append(Spacer(1, 0.4*cm))
# ── 6. RUPTURE AND SAH ─────────────────────────────────────────────
story.append(section_header("6. Rupture and Subarachnoid Haemorrhage (SAH)"))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("<b>Risk of Rupture:</b>", h2))
rupture_rows = [
["Aneurysm <3 mm", "Rarely bleeds; risk essentially 0% at 5 years"],
["Aneurysm <7 mm", "Very low annual risk in most locations"],
["Aneurysm 7–10 mm", "Intermediate risk; surveillance vs. treatment decision"],
["Aneurysm >10 mm", "~50% risk per year; treatment strongly recommended"],
["Giant >25 mm", "8–10% annual rupture risk; may also cause mass effect"],
["Overall (all sizes)","1.3% per year"],
]
story.append(data_table(
["Size", "Rupture Risk"],
rupture_rows,
col_widths=[5*cm, 12.5*cm]
))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("<b>Triggers of Rupture:</b>", h2))
for item in [
"Acute rises in intracranial pressure: straining at stool, sexual orgasm, heavy lifting (~1/3 of cases)",
"Physical exertion or Valsalva manoeuvre",
"Spontaneous rupture during sleep or rest",
"Uncontrolled hypertension",
]:
story.append(Paragraph(f"• {item}", bullet))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("<b>Consequences of Rupture:</b>", h2))
story.append(Paragraph(
"Blood under <b>arterial pressure</b> is forced into the subarachnoid space (basal cisterns) "
"and sometimes into brain parenchyma. This causes sudden, severe intracranial hypertension "
"and the characteristic clinical picture.",
body
))
conseq_rows = [
["Acute SAH", "Blood fills subarachnoid space; basal cisterns packed with clot"],
["Intracerebral haematoma", "Blood may track into parenchyma adjacent to aneurysm"],
["Intraventricular haemorrhage","Blood enters ventricles; worsens prognosis"],
["Brain herniation", "Acute rise in ICP may cause transtentorial herniation; death"],
["Vasospasm (days 4–14)", "Narrowing of Circle of Willis arteries → cerebral ischaemia (30% of survivors)"],
["Hydrocephalus", "Acute (CSF obstruction) or chronic (meningeal scarring)"],
["Hyponatremia", "Cerebral salt wasting; first 2 weeks"],
]
story.append(data_table(
["Complication", "Details"],
conseq_rows,
col_widths=[5.5*cm, 12*cm]
))
story.append(Spacer(1, 0.4*cm))
# ── 7. CLINICAL FEATURES ──────────────────────────────────────────
story.append(section_header("7. Clinical Features"))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("<b>Presentation of Ruptured Aneurysm:</b>", h2))
for item in [
'<b>Thunderclap headache</b> – sudden, excruciating ("worst headache of my life")',
"<b>Loss of consciousness</b> in ~50% of cases",
"<b>Vomiting</b> in ~70% of cases",
"<b>Seizure</b> in ~10% of cases",
"<b>Meningism</b> (neck stiffness, photophobia) – develops over hours",
"<b>Sentinel bleed</b> – sudden unexplained headache days before major rupture; must not be missed",
]:
story.append(Paragraph(f"• {item}", bullet))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("<b>Location-Specific Signs:</b>", h2))
location_rows = [
["Posterior communicating artery (PComm)", "Painful 3rd nerve palsy (CN III) – ptosis, dilated pupil, eye 'down and out'"],
["Anterior communicating artery (AComm)", "Abulia, leg weakness, memory disturbance"],
["Middle cerebral artery (MCA)", "Contralateral hemiplegia + aphasia (dominant side)"],
["Basilar artery tip", "Altered consciousness; 3rd nerve palsy; top-of-basilar syndrome"],
["Internal carotid artery", "Visual field defects; unilateral blindness if ophthalmic involved"],
]
story.append(data_table(
["Aneurysm Site", "Clinical Sign"],
location_rows,
col_widths=[6.5*cm, 11*cm]
))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("<b>Grading Scales (Hunt-Hess):</b>", h2))
hh_rows = [
["Grade 1", "Asymptomatic or mild headache", "Excellent"],
["Grade 2", "Moderate to severe headache; nuchal rigidity; no deficit except CN palsy", "Good"],
["Grade 3", "Drowsiness, confusion, mild focal deficit", "Fair"],
["Grade 4", "Stupor, moderate to severe hemiparesis", "Poor (mortality ~60%)"],
["Grade 5", "Deep coma, decerebrate rigidity, moribund", "Very poor"],
]
story.append(data_table(
["Grade", "Description", "Prognosis"],
hh_rows,
col_widths=[2.5*cm, 10*cm, 5*cm]
))
story.append(Spacer(1, 0.4*cm))
# ── 8. TYPES OF INTRACRANIAL ANEURYSM ─────────────────────────────
story.append(section_header("8. Types of Intracranial Aneurysm – Comparison"))
story.append(Spacer(1, 0.2*cm))
types_rows = [
["Saccular (berry)", "Circle of Willis bifurcations", "Most common; subarachnoid haemorrhage", "Congenital medial defect + haemodynamic stress"],
["Fusiform (atherosclerotic)", "Basilar artery (mostly)", "Ischaemic stroke; mass effect", "Atherosclerosis; hypertension"],
["Mycotic", "Distal to 1st bifurcation of CoW vessels", "Haemorrhage; infarction", "Septic emboli; infective endocarditis"],
["Traumatic", "Peripheral branches", "Delayed haemorrhage", "Direct injury or shear forces"],
["Dissecting", "Any intracranial vessel", "Ischaemic or haemorrhagic stroke", "Intimal tear; blood in vessel wall"],
]
story.append(data_table(
["Type", "Location", "Main Complication", "Mechanism"],
types_rows,
col_widths=[3.5*cm, 4.5*cm, 4.5*cm, 5*cm]
))
story.append(Spacer(1, 0.4*cm))
# ── 9. INVESTIGATIONS ─────────────────────────────────────────────
story.append(section_header("9. Investigations"))
story.append(Spacer(1, 0.2*cm))
inv_rows = [
("Non-contrast CT brain (immediate)", "Hyperdense blood in subarachnoid space; sensitivity >95% within 6 hours of SAH"),
("Lumbar puncture (if CT negative)", "Xanthochromia (yellow CSF due to bilirubin from haemoglobin breakdown) after 12 hours; highly sensitive"),
("CT angiography (CTA)", "Non-invasive; detects aneurysms >3 mm; images Circle of Willis; vasospasm assessment"),
("Digital subtraction angiography (DSA)","Gold standard; shows aneurysm anatomy (neck, dome, parent vessel); guides treatment planning"),
("MRI / MRA", "Useful for unruptured aneurysm detection; less sensitive than DSA for small lesions"),
("Transcranial Doppler (TCD)", "Daily monitoring for vasospasm; detects elevated MCA/ACA flow velocities"),
]
story.append(info_table(inv_rows, col_widths=[5.5*cm, 12*cm]))
story.append(Spacer(1, 0.4*cm))
# ── 10. TREATMENT ─────────────────────────────────────────────────
story.append(section_header("10. Treatment"))
story.append(Spacer(1, 0.2*cm))
treat_rows = [
["Surgical clipping", "Gold standard for anterior circulation aneurysms; metallic clip across neck", "Immediate exclusion; durable", "Craniotomy required; higher surgical risk in poor-grade patients"],
["Endovascular coiling (GDC)", "Platinum coils packed into aneurysm dome via catheter", "Less invasive; suitable for posterior circulation", "Higher rebleed rate long-term; may need re-treatment"],
["Flow diversion (pipeline embolization)", "Stent redirects flow away from aneurysm", "Giant or fusiform aneurysms", "Delayed occlusion; antiplatelet therapy required"],
["Nimodipine (calcium channel blocker)", "Pharmacological prevention of vasospasm", "All SAH patients", "Does not reduce vasospasm but improves neurological outcome"],
["Triple-H therapy (historical)", "Hypertension + Hypervolaemia + Haemodilution", "Symptomatic vasospasm", "Now replaced by euvolaemia + BP management"],
]
story.append(data_table(
["Modality", "Description", "Best For", "Limitation"],
treat_rows,
col_widths=[4*cm, 5.5*cm, 3.5*cm, 4.5*cm]
))
story.append(Spacer(1, 0.4*cm))
# ── 11. KEY FACTS BOX ─────────────────────────────────────────────
story.append(section_header("11. Examination Key Points"))
story.append(Spacer(1, 0.2*cm))
kf_data = [
["Most common type of intracranial aneurysm", "Saccular (berry) aneurysm"],
["Prevalence in general population", "~2%"],
["Anterior circulation involvement", "~90%"],
["Most common single site", "Anterior communicating artery (AComm) – 38%"],
["Classic presentation", "Thunderclap headache ('worst of my life')"],
["CN palsy associated with PComm aneurysm", "Oculomotor (CN III) palsy – dilated pupil, ptosis, 'down and out' eye"],
["Key histological finding", "No smooth muscle, no elastic lamina in sac wall – hyalinized intima + adventitia only"],
["Vasospasm peak", "Day 4–14 post-SAH"],
["Vasospasm treatment", "Nimodipine (oral); endovascular balloon/chemical angioplasty"],
["Rebleed risk (untreated)", "~30% in first month; 50% mortality with rebleed"],
["Overall mortality with first rupture", "25–50%"],
["Genetic associations", "ADPKD, Ehlers-Danlos IV, Marfan, NF1, coarctation of aorta"],
]
kf_table = Table(
[[Paragraph(f"<b>{r[0]}</b>", small), Paragraph(r[1], small)] for r in kf_data],
colWidths=[8*cm, 9.5*cm]
)
kf_table.setStyle(TableStyle([
("ROWBACKGROUNDS", (0,0), (-1,-1), [PALE_GREY, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(kf_table)
story.append(Spacer(1, 0.4*cm))
# ── REFERENCE FOOTER ─────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=MID_GREY, spaceAfter=6))
story.append(Paragraph(
"<b>References:</b> (1) Robbins, Cotran & Kumar – Pathologic Basis of Disease, 10th Ed., "
"ISBN 9780443264528, pp. 1157–1159. "
"(2) Harrison's Principles of Internal Medicine, 22nd Ed. (2025), ISBN 9781265977061, Chapter 440. "
"(3) Neuroanatomy through Clinical Cases, 3rd Ed., ISBN 9781605359625, p. 417. "
"(4) Bailey & Love's Short Practice of Surgery, 28th Ed., Chapter 48.",
ParagraphStyle("ref", parent=small, fontSize=7.5, textColor=HexColor("#7F8C8D"))
))
# ════════════════════════════════════════════════════════════════════
# RENDER PDF
# ════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=1.6*cm,
bottomMargin=1.8*cm,
leftMargin=1.5*cm,
rightMargin=1.5*cm,
title="Berry Aneurysm – Pathology & Morphology Summary",
author="Orris Medical AI",
subject="Berry Aneurysm Pathology",
)
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_page)
print(f"PDF generated: {OUTPUT}")