Make a pdf with utmost creativity and images (animated cartoonish) with all the causes of painless sudden loss of vision (LOV), painful sudden loss of vision, painless gradual loss of vision and painful gradual loss of vision from the khurana textbook of ophthalmology
Cute cartoon eye character with a lightning bolt and clock, surrounded by floating icons of retinal detachment (peeling retina), blood clot blocking an artery, vitreous hemorrhage (red splash), optic neuritis (inflamed nerve), amaurosis fugax (hourglass), CRAO, CRVO - all depicted as cartoonish symbols. Bright, colorful, medical education style. Title banner: "PAINLESS SUDDEN LOSS OF VISION"
Cute cartoon eye character crying with pain stars around it, lightning bolt, surrounded by floating cartoonish icons: inflamed eye (uveitis), green eye with pressure meter (acute angle-closure glaucoma), red angry optic nerve (optic neuritis), brain with headache waves (migraine), corneal scratches, endophthalmitis bacteria. Bright vivid colors, medical cartoon style. Title banner: "PAINFUL SUDDEN LOSS OF VISION"
Cute cartoon eye character slowly fading with a gentle gradient, hourglass nearby, surrounded by cartoonish symbols: cloudy lens (cataract), sugar cube with diabetic retinopathy (retinal blood spots), macular degeneration (central blur spot), glaucoma optic cup, retinitis pigmentosa (tunnel vision cone), slowly growing tumor, optic atrophy. Soft pastel colors, medical education cartoon style. Title banner: "PAINLESS GRADUAL LOSS OF VISION"
Cute cartoon eye character wincing in chronic pain with slow clock, surrounded by cartoonish icons: red scleritis eye (scleritis), inflammatory uveitis with cells floating, painful corneal ulcer (green bacteria on cornea), dry eye syndrome (desert cactus near eye), keratoconus cone shape, chronic angle closure pressure meter, thyroid eye with bulging. Warm orange and red color scheme, medical cartoon style. Title banner: "PAINFUL GRADUAL LOSS OF VISION"
Try again
Cute cartoon eye character looking shocked with a lightning bolt, surrounded by floating cartoonish medical icons: a retina peeling away like wallpaper, a red blood clot blocking a tiny artery, a splash of red vitreous hemorrhage, an inflamed optic nerve glowing orange, an hourglass with a tiny eye inside representing transient vision loss. Bright cheerful colors, chibi anime style, white background, medical education illustration.
Cute cartoon eye character with red tears and pain stars bursting around it, surrounded by floating cartoonish icons: a green eye with a pressure gauge maxed out representing acute angle closure glaucoma, a spiky angry cornea with scratches, a brain with lightning headache waves, inflamed red uvea with tiny bacteria floating, sharp needle near eye. Vivid red and orange color palette, chibi anime style, white background, medical education illustration.
Cute cartoon eye character slowly fading and becoming cloudy with a gentle hourglass beside it, surrounded by soft pastel floating icons: a foggy crystalline lens representing cataract, a retina with tiny hemorrhage dots for diabetic retinopathy, a bullseye macular degeneration pattern, a shrinking visual field tunnel for retinitis pigmentosa, an enlarged optic cup for glaucoma. Soft pastel blues and purples, chibi anime style, white background, medical education illustration.
Cute cartoon eye character wincing in slow chronic pain with a calendar showing many days passing, surrounded by cartoonish warm-colored icons: an angry red scleritis eye, a corneal ulcer with a green germ on it, dry cracked eye with a tiny desert cactus, a cone-shaped keratoconus distortion, a pressure meter creeping upward for chronic glaucoma, a thyroid with a bulging eye. Warm orange red color palette, chibi anime style, white background, medical education illustration.
Do it without images
mkdir -p /home/daytona/workspace/lov-pdf && ls /home/daytona/workspace/lov-pdf/
~/lov-pdf/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import Flowable
from reportlab.pdfgen import canvas as pdfcanvas
import math
OUTPUT = "/home/daytona/workspace/lov-pdf/Loss_of_Vision_Khurana.pdf"
# ── Color Palette ──────────────────────────────────────────────────────────────
COL_BG_DARK = colors.HexColor("#0D1B2A") # deep navy
COL_PAINLESS_S = colors.HexColor("#00B4D8") # cyan – painless sudden
COL_PAINFUL_S = colors.HexColor("#E63946") # red – painful sudden
COL_PAINLESS_G = colors.HexColor("#2EC4B6") # teal – painless gradual
COL_PAINFUL_G = colors.HexColor("#FF9F1C") # amber – painful gradual
COL_ACCENT = colors.HexColor("#F1FAEE") # near-white
COL_TEXT_DARK = colors.HexColor("#1D2D44")
COL_BADGE_TXT = colors.white
COL_CARD_BG = colors.HexColor("#F8F9FA")
COL_DIVIDER = colors.HexColor("#DEE2E6")
W, H = A4
# ── Custom Flowables ───────────────────────────────────────────────────────────
class ColorBand(Flowable):
"""Full-width color banner with big emoji + text."""
def __init__(self, color, emoji, title, subtitle, width=None):
Flowable.__init__(self)
self.color = color
self.emoji = emoji
self.title = title
self.subtitle = subtitle
self.band_h = 2.8 * cm
self.width = width or (W - 2 * cm)
def wrap(self, *args):
return self.width, self.band_h + 0.4 * cm
def draw(self):
c = self.canv
# Rounded rect background
c.setFillColor(self.color)
c.roundRect(0, 0.2 * cm, self.width, self.band_h, radius=12, fill=1, stroke=0)
# Emoji circle
c.setFillColor(colors.white)
c.circle(1.6 * cm, 0.2 * cm + self.band_h / 2, 0.9 * cm, fill=1, stroke=0)
c.setFillColor(self.color)
c.setFont("Helvetica-Bold", 22)
c.drawCentredString(1.6 * cm, 0.2 * cm + self.band_h / 2 - 0.32 * cm, self.emoji)
# Title
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 16)
c.drawString(3.0 * cm, 0.2 * cm + self.band_h / 2 + 0.15 * cm, self.title)
# Subtitle
c.setFont("Helvetica", 10)
c.drawString(3.0 * cm, 0.2 * cm + self.band_h / 2 - 0.45 * cm, self.subtitle)
class DotBullet(Flowable):
"""Colored dot bullet row."""
def __init__(self, text, dot_color, font_size=10.5, width=None):
Flowable.__init__(self)
self.text = text
self.dot_color = dot_color
self.font_size = font_size
self.row_h = font_size * 1.9
self.width = width or (W - 2 * cm)
def wrap(self, *args):
return self.width, self.row_h
def draw(self):
c = self.canv
# Dot
dot_r = 0.18 * cm
dot_x = dot_r + 0.1 * cm
dot_y = self.row_h / 2
c.setFillColor(self.dot_color)
c.circle(dot_x, dot_y, dot_r, fill=1, stroke=0)
# Text
c.setFillColor(COL_TEXT_DARK)
c.setFont("Helvetica", self.font_size)
c.drawString(dot_x + dot_r + 0.35 * cm, dot_y - self.font_size * 0.35, self.text)
class SectionCard(Flowable):
"""Light card background for a sub-section label."""
def __init__(self, label, color, width=None):
Flowable.__init__(self)
self.label = label
self.color = color
self.card_h = 0.9 * cm
self.width = width or (W - 2 * cm)
def wrap(self, *args):
return self.width, self.card_h + 0.3 * cm
def draw(self):
c = self.canv
# Soft bg strip
c.setFillColor(self.color)
c.setFillAlpha(0.15)
c.roundRect(0, 0.15 * cm, self.width, self.card_h, radius=6, fill=1, stroke=0)
c.setFillAlpha(1)
# Left accent bar
c.setFillColor(self.color)
c.rect(0, 0.15 * cm, 0.3 * cm, self.card_h, fill=1, stroke=0)
# Label text
c.setFillColor(self.color)
c.setFont("Helvetica-Bold", 11)
c.drawString(0.55 * cm, 0.15 * cm + 0.27 * cm, self.label)
# ── Cover Page ─────────────────────────────────────────────────────────────────
def cover_page(canvas, doc):
canvas.saveState()
# Background gradient simulation with rectangles
band_count = 40
for i in range(band_count):
r = 0.0 + i * (13.0 / band_count) / 255
g = 27.0 + i * (35.0 / band_count) / 255
b = 42.0 + i * (80.0 / band_count) / 255
canvas.setFillColorRGB(r, g, b)
canvas.rect(0, i * (H / band_count), W, H / band_count + 1, fill=1, stroke=0)
# Decorative circles
canvas.setFillColor(colors.HexColor("#00B4D8"))
canvas.setFillAlpha(0.12)
canvas.circle(W * 0.85, H * 0.9, 3.5 * cm, fill=1, stroke=0)
canvas.circle(W * 0.1, H * 0.1, 2.8 * cm, fill=1, stroke=0)
canvas.setFillAlpha(0.07)
canvas.circle(W * 0.5, H * 0.5, 5 * cm, fill=1, stroke=0)
canvas.setFillAlpha(1)
# Eye emoji large
canvas.setFont("Helvetica-Bold", 80)
canvas.setFillColor(colors.white)
canvas.drawCentredString(W / 2, H * 0.68, "👁")
# Title
canvas.setFont("Helvetica-Bold", 28)
canvas.setFillColor(colors.white)
canvas.drawCentredString(W / 2, H * 0.57, "LOSS OF VISION")
# Subtitle
canvas.setFont("Helvetica-Bold", 14)
canvas.setFillColor(colors.HexColor("#ADE8F4"))
canvas.drawCentredString(W / 2, H * 0.51, "A Complete Classification Guide")
# Source
canvas.setFont("Helvetica", 11)
canvas.setFillColor(colors.HexColor("#90E0EF"))
canvas.drawCentredString(W / 2, H * 0.46, "Based on Khurana's Textbook of Ophthalmology")
# Four colored pills
pills = [
(COL_PAINLESS_S, "⚡ Painless Sudden"),
(COL_PAINFUL_S, "🔴 Painful Sudden"),
(COL_PAINLESS_G, "🌿 Painless Gradual"),
(COL_PAINFUL_G, "🔥 Painful Gradual"),
]
pill_w = 3.8 * cm
pill_h = 0.75 * cm
total_w = len(pills) * (pill_w + 0.4 * cm) - 0.4 * cm
start_x = (W - total_w) / 2
py = H * 0.35
for i, (col, lbl) in enumerate(pills):
px = start_x + i * (pill_w + 0.4 * cm)
canvas.setFillColor(col)
canvas.roundRect(px, py, pill_w, pill_h, radius=pill_h / 2, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 8)
canvas.setFillColor(colors.white)
canvas.drawCentredString(px + pill_w / 2, py + 0.22 * cm, lbl)
# Bottom bar
canvas.setFillColor(colors.HexColor("#00B4D8"))
canvas.rect(0, 0, W, 1.2 * cm, fill=1, stroke=0)
canvas.setFont("Helvetica", 9)
canvas.setFillColor(colors.white)
canvas.drawCentredString(W / 2, 0.45 * cm, "Khurana Textbook of Ophthalmology • Ophthalmology Quick Reference")
canvas.restoreState()
def later_page(canvas, doc):
canvas.saveState()
# Top bar
canvas.setFillColor(COL_BG_DARK)
canvas.rect(0, H - 1.0 * cm, W, 1.0 * cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 9)
canvas.setFillColor(colors.white)
canvas.drawString(1 * cm, H - 0.68 * cm, "👁 Loss of Vision — Khurana's Classification")
canvas.drawRightString(W - 1 * cm, H - 0.68 * cm, f"Page {doc.page}")
# Bottom bar
canvas.setFillColor(COL_BG_DARK)
canvas.rect(0, 0, W, 0.8 * cm, fill=1, stroke=0)
canvas.setFont("Helvetica", 8)
canvas.setFillColor(colors.HexColor("#ADE8F4"))
canvas.drawCentredString(W / 2, 0.28 * cm, "Based on Khurana's Textbook of Ophthalmology")
canvas.restoreState()
# ── Content Data ───────────────────────────────────────────────────────────────
SECTIONS = [
{
"emoji": "⚡",
"title": "PAINLESS SUDDEN LOSS OF VISION",
"subtitle": "Acute onset • No pain • Urgent evaluation required",
"color": COL_PAINLESS_S,
"mnemonic": "Remember: 'VASCULAR + RETINAL events strike silently and suddenly'",
"subsections": [
{
"label": "🩸 Vascular Causes",
"items": [
"Central Retinal Artery Occlusion (CRAO) — sudden, complete, painless monocular blindness; cherry-red spot at macula",
"Branch Retinal Artery Occlusion (BRAO) — sectoral visual field loss; pale retina in affected zone",
"Central Retinal Vein Occlusion (CRVO) — sudden blurring; 'blood and thunder' fundus with flame haemorrhages",
"Branch Retinal Vein Occlusion (BRVO) — sectoral haemorrhages along vein distribution",
"Ischaemic Optic Neuropathy (Anterior/Posterior — AION/PION) — disc oedema; altitudinal field defect",
"Amaurosis Fugax — transient monocular blindness (TIA of the eye); carotid emboli",
"Carotid Artery Occlusion — may cause monocular or binocular visual loss",
]
},
{
"label": "👁 Retinal Causes",
"items": [
"Rhegmatogenous Retinal Detachment — sudden shower of floaters + flashes, then a 'curtain' or 'shadow' over vision",
"Vitreous Haemorrhage — sudden onset of floaters, red haze, or complete visual loss; loss of red reflex",
"Macular Hole (acute) — sudden central scotoma; distortion at macula",
"Commotio Retinae (Berlin's oedema) — post-traumatic whitening of retina",
]
},
{
"label": "🧠 Neurological Causes",
"items": [
"Occipital Lobe Infarction / CVA — homonymous hemianopia; bilateral cortical blindness",
"Optic Neuritis (demyelinating — painless variant) — rare; disc oedema may be absent (retrobulbar)",
"Pituitary Apoplexy — sudden bitemporal hemianopia with headache (can be painless initially)",
"Migraine with Aura (visual aura) — transient scintillating scotoma; fully reversible",
]
},
{
"label": "💊 Toxic / Systemic Causes",
"items": [
"Quinine toxicity — sudden bilateral visual loss; arteriolar spasm",
"Methanol poisoning — rapid bilateral blindness; disc hyperaemia then atrophy",
"Hypertensive crisis — retinal arteriolar spasm; hypertensive retinopathy grade IV",
"Hypotension / shock — optic nerve ischaemia",
]
},
{
"label": "📐 Others",
"items": [
"Acute Angle-Closure Glaucoma (can present with minimal pain in elderly)",
"Non-organic (functional/psychogenic) visual loss",
"Sudden haemorrhage into optic nerve sheath",
]
},
]
},
{
"emoji": "🔴",
"title": "PAINFUL SUDDEN LOSS OF VISION",
"subtitle": "Acute onset • Pain is prominent • Emergency",
"color": COL_PAINFUL_S,
"mnemonic": "Remember: 'GLAUCOMA + INFLAMMATION + CORNEAL emergencies cause painful sudden LOV'",
"subsections": [
{
"label": "🔺 Intraocular Pressure Emergencies",
"items": [
"Acute Angle-Closure Glaucoma (AACG) — severe eye/brow pain, haloes around lights, nausea/vomiting, mid-dilated fixed pupil, rock-hard eye",
"Secondary Acute Glaucoma — post-traumatic, neovascular, phacolytic, phacomorphic",
]
},
{
"label": "🔥 Inflammatory / Infectious Causes",
"items": [
"Acute Iridocyclitis (Anterior Uveitis) — photophobia, ciliary flush, keratic precipitates (KPs), flare and cells in AC",
"Panophthalmitis / Endophthalmitis — severe deep pain, proptosis, hypopyon; vision may be hand movements only",
"Corneal Ulcer (bacterial/fungal/viral) — photophobia, lacrimation, blepharospasm, corneal opacity",
"Acute Dacryocystitis with corneal involvement — periorbital pain, swelling, discharge",
"Herpes Zoster Ophthalmicus — vesicular rash along V1 dermatome, corneal dendrites, uveitis",
]
},
{
"label": "🩺 Vascular / Neurological (with pain)",
"items": [
"Giant Cell Arteritis (Temporal Arteritis) — sudden visual loss + scalp tenderness, jaw claudication, elevated ESR/CRP; AION",
"Optic Neuritis (typical) — retrobulbar pain worsened by eye movement, RAPD, central scotoma; young females, MS association",
"Cavernous Sinus Thrombosis — proptosis, chemosis, ophthalmoplegia, severe headache",
"Orbital Cellulitis — painful proptosis, restricted EOM, fever, reduced vision",
]
},
{
"label": "🩸 Trauma",
"items": [
"Chemical Burns (acid/alkali) — immediate pain + vision loss; alkali worse (saponification)",
"Perforating eye injury — obvious history; uveal prolapse, hypotony",
"Hyphaema — blood in anterior chamber; pain + blurred vision after blunt trauma",
"Traumatic Iritis — post-blunt trauma; ciliary spasm, photophobia",
]
},
]
},
{
"emoji": "🌿",
"title": "PAINLESS GRADUAL LOSS OF VISION",
"subtitle": "Slow onset • No pain • Often bilateral",
"color": COL_PAINLESS_G,
"mnemonic": "Remember: 'CATARACT, GLAUCOMA, DEGENERATIONS — the silent thieves of sight'",
"subsections": [
{
"label": "🔭 Lens Disorders",
"items": [
"Senile Cataract — most common cause of gradual painless LOV worldwide; nuclear, cortical, posterior subcapsular types",
"Complicated Cataract — secondary to uveitis, diabetes, steroid use, radiation",
"Congenital Cataract — in children; leukocoria, strabismus, nystagmus",
]
},
{
"label": "🌀 Glaucoma",
"items": [
"Primary Open-Angle Glaucoma (POAG) — insidious onset, elevated IOP, cup:disc ratio >0.6, arcuate scotomas; 'snuff out' of peripheral vision",
"Normal Tension Glaucoma — same as POAG but IOP normal; vascular hypothesis",
"Secondary Open-Angle Glaucoma — pigment dispersion, pseudoexfoliation, steroid-induced",
]
},
{
"label": "🩺 Retinal / Macular Disorders",
"items": [
"Age-Related Macular Degeneration (dry AMD) — drusen, RPE changes, gradual central vision loss; Amsler grid distortion",
"Diabetic Retinopathy — dot-blot haemorrhages, hard exudates, macular oedema, neovascularisation",
"Hypertensive Retinopathy — AV nipping, flame haemorrhages, cotton-wool spots",
"Retinitis Pigmentosa — ring scotoma → tunnel vision → blindness; bone-spicule pigmentation, attenuated vessels, waxy disc pallor",
"Choroidal Atrophy / Myopic Degeneration — high myopia, Fuchs' spots, lacquer cracks",
"Central Serous Chorioretinopathy (CSCR) — sub-retinal fluid at macula; young males, steroids",
"Macular Dystrophies (Stargardt, Best disease)",
]
},
{
"label": "🧠 Optic Nerve / Neurological",
"items": [
"Optic Atrophy (primary / secondary / consecutive) — pale disc, RAPD, field defects",
"Chronic Papilloedema (raised ICP) — progressive field constriction; enlarged blind spot",
"Compressive Optic Neuropathy — orbital tumour, thyroid eye disease, optic nerve meningioma",
"Nutritional Amblyopia / Tobacco-Alcohol Amblyopia — centrocaecal scotoma; B12/folate deficiency",
"Leber's Hereditary Optic Neuropathy (LHON) — young males; bilateral sequential painless acute-on-gradual central vision loss",
"Chiasmal / Retrochiasmal Lesions — pituitary adenoma; bitemporal hemianopia",
]
},
{
"label": "🌐 Cornea / Media Opacities",
"items": [
"Corneal Scar / Leucoma — post-infective or post-traumatic stromal opacity",
"Band-Shaped Keratopathy — calcium deposits in Bowman's layer; hypercalcaemia, chronic uveitis",
"Corneal Dystrophies (Fuchs', macular, granular) — bilateral, hereditary, slowly progressive",
"Vitreous Degeneration / Chronic Vitreous Haemorrhage — slowly progressive floaters and haziness",
]
},
{
"label": "🔬 Toxic / Metabolic",
"items": [
"Chloroquine / Hydroxychloroquine Retinopathy — bull's-eye maculopathy; dose-related",
"Ethambutol Optic Neuropathy — colour vision loss first; reversible if stopped early",
"Vitamin A Deficiency — xerophthalmia, night blindness, Bitot's spots",
"Thyroid Eye Disease (inactive phase) — compressive optic neuropathy",
]
},
]
},
{
"emoji": "🔥",
"title": "PAINFUL GRADUAL LOSS OF VISION",
"subtitle": "Slow onset • Persistent pain • Often inflammatory",
"color": COL_PAINFUL_G,
"mnemonic": "Remember: 'CHRONIC INFLAMMATION and RAISED PRESSURE dominate painful gradual LOV'",
"subsections": [
{
"label": "🔺 Chronic Glaucoma (with pain)",
"items": [
"Absolute Glaucoma — end-stage glaucoma; painful blind eye; no perception of light; stony-hard eye",
"Chronic Angle-Closure Glaucoma — repeated sub-acute attacks; brow ache, haloes; gradual field loss",
"Secondary Glaucoma (rubeosis iridis / NVG) — pain from high IOP; neovascularisation of iris; diabetic/CRVO",
]
},
{
"label": "🔥 Chronic Inflammatory Conditions",
"items": [
"Chronic Anterior Uveitis — prolonged dull ache, photophobia, posterior synechiae, band keratopathy; JIA-associated",
"Chronic Posterior Uveitis / Panuveitis — choroiditis, vitritis; gradual blurring with discomfort",
"Scleritis (anterior / posterior) — deep boring eye pain; scleral nodule or diffuse engorgement; associated with RA, SLE, IBD",
"Episcleritis (severe recurrent) — sectoral redness, mild-moderate pain; usually self-limiting but can progress",
"Sympathetic Ophthalmia — bilateral granulomatous uveitis following penetrating eye injury; 'exciting' vs 'sympathising' eye",
]
},
{
"label": "🦷 Orbital / Adnexal Causes",
"items": [
"Orbital Tumours (slow-growing) — meningioma, lacrimal gland tumour, lymphoma; proptosis + ache + gradual LOV",
"Thyroid Eye Disease (active inflammatory phase) — retro-orbital pain, proptosis, lid retraction, restrictive myopathy",
"Chronic Dacryocystitis — epiphora, medial canthal mass; indirect corneal complications",
"Carotid-Cavernous Fistula (dural/low-flow) — arterialized conjunctival vessels, raised IOP, pulsatile proptosis",
]
},
{
"label": "🧠 Neurological (with pain)",
"items": [
"Painful Compressive Optic Neuropathy — sphenoid wing meningioma, orbital apex syndrome",
"Chronic Raised Intracranial Pressure — headache (worse in morning), papilloedema, gradually constricting fields",
"Tolosa-Hunt Syndrome — painful ophthalmoplegia; granulomatous inflammation of cavernous sinus",
]
},
{
"label": "💊 Toxic / Systemic (with discomfort)",
"items": [
"Chronic Angle-Closure from Sulpha Drugs — topiramate, sulphonamides cause ciliary body oedema → forward lens displacement",
"Sarcoidosis-related Uveitis — mutton-fat KPs, snowball vitreous opacities, periphlebitis",
"Vogt-Koyanagi-Harada (VKH) Syndrome — bilateral panuveitis with meningismus, poliosis, vitiligo",
]
},
]
},
]
# ── Build Story ────────────────────────────────────────────────────────────────
def build():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1 * cm,
rightMargin=1 * cm,
topMargin=1.3 * cm,
bottomMargin=1.1 * cm,
)
styles = getSampleStyleSheet()
body_style = ParagraphStyle(
"body",
fontSize=9.5,
leading=14,
textColor=COL_TEXT_DARK,
spaceAfter=2,
)
mnem_style = ParagraphStyle(
"mnem",
fontSize=9,
leading=13,
textColor=colors.white,
backColor=COL_BG_DARK,
borderPadding=(6, 10, 6, 10),
spaceAfter=6,
)
note_style = ParagraphStyle(
"note",
fontSize=8.5,
leading=12,
textColor=colors.HexColor("#555555"),
italic=True,
spaceAfter=2,
)
story = []
# ── Cover (blank first page rendered via onFirstPage) ──────────────────
story.append(PageBreak()) # This triggers cover via onFirstPage
for sec in SECTIONS:
col = sec["color"]
# Section banner
story.append(Spacer(1, 0.3 * cm))
story.append(ColorBand(col, sec["emoji"], sec["title"], sec["subtitle"]))
story.append(Spacer(1, 0.25 * cm))
# Mnemonic box
story.append(Paragraph(f"💡 {sec['mnemonic']}", mnem_style))
story.append(Spacer(1, 0.2 * cm))
for sub in sec["subsections"]:
story.append(SectionCard(sub["label"], col))
story.append(Spacer(1, 0.05 * cm))
for item in sub["items"]:
# Split on em-dash for bolding the condition name
if "—" in item:
parts = item.split("—", 1)
cond = parts[0].strip()
detail = parts[1].strip()
p = Paragraph(f"<b>{cond}</b> — {detail}", body_style)
else:
p = Paragraph(item, body_style)
story.append(DotBullet(item.split("—")[0].strip() + (" — " + item.split("—")[1].strip() if "—" in item else ""), col))
story.append(Spacer(1, 0.15 * cm))
story.append(HRFlowable(width="100%", thickness=1, color=COL_DIVIDER, spaceAfter=8))
story.append(PageBreak())
# ── Summary Comparison Table ───────────────────────────────────────────
story.append(Spacer(1, 0.4 * cm))
heading_style = ParagraphStyle(
"tabhead", fontSize=14, leading=18, alignment=TA_CENTER,
textColor=COL_BG_DARK, fontName="Helvetica-Bold", spaceAfter=8
)
story.append(Paragraph("📋 Quick Reference Summary Table", heading_style))
story.append(Spacer(1, 0.3 * cm))
table_data = [
[
Paragraph("<b>Category</b>", ParagraphStyle("th", fontSize=9, textColor=colors.white, fontName="Helvetica-Bold", alignment=TA_CENTER)),
Paragraph("<b>Key Examples</b>", ParagraphStyle("th", fontSize=9, textColor=colors.white, fontName="Helvetica-Bold", alignment=TA_CENTER)),
Paragraph("<b>Hallmark Feature</b>", ParagraphStyle("th", fontSize=9, textColor=colors.white, fontName="Helvetica-Bold", alignment=TA_CENTER)),
Paragraph("<b>Urgent?</b>", ParagraphStyle("th", fontSize=9, textColor=colors.white, fontName="Helvetica-Bold", alignment=TA_CENTER)),
],
[
Paragraph("⚡ Painless Sudden", ParagraphStyle("c1", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("CRAO, CRVO, Retinal Detachment, Vitreous Haemorrhage, AION, Occipital stroke", ParagraphStyle("c1", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("No pain, sudden — vascular/retinal event", ParagraphStyle("c1", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("🚨 YES", ParagraphStyle("c1", fontSize=8.5, textColor=colors.red, fontName="Helvetica-Bold", alignment=TA_CENTER)),
],
[
Paragraph("🔴 Painful Sudden", ParagraphStyle("c2", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("AACG, Optic Neuritis, GCA, Corneal Ulcer, Chemical Burn, Endophthalmitis", ParagraphStyle("c2", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("Severe pain + rapid LOV = emergency", ParagraphStyle("c2", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("🚨 YES", ParagraphStyle("c2", fontSize=8.5, textColor=colors.red, fontName="Helvetica-Bold", alignment=TA_CENTER)),
],
[
Paragraph("🌿 Painless Gradual", ParagraphStyle("c3", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("Cataract, POAG, Dry AMD, Diabetic Retinopathy, Retinitis Pigmentosa, Optic Atrophy", ParagraphStyle("c3", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("Slow silent progression — often bilateral", ParagraphStyle("c3", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("⏱ Routine\n(urgent if rapid)", ParagraphStyle("c3", fontSize=8.5, textColor=COL_TEXT_DARK, alignment=TA_CENTER)),
],
[
Paragraph("🔥 Painful Gradual", ParagraphStyle("c4", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("Chronic Uveitis, Scleritis, Absolute Glaucoma, Thyroid Eye Disease, VKH, Orbital Tumours", ParagraphStyle("c4", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("Deep dull ache + slow LOV — inflammation / raised IOP", ParagraphStyle("c4", fontSize=8.5, textColor=COL_TEXT_DARK)),
Paragraph("⚠ Soon", ParagraphStyle("c4", fontSize=8.5, textColor=colors.HexColor("#CC5500"), fontName="Helvetica-Bold", alignment=TA_CENTER)),
],
]
col_widths = [3.8 * cm, 6.8 * cm, 5.2 * cm, 2.0 * cm]
tbl = Table(table_data, colWidths=col_widths, repeatRows=1)
tbl.setStyle(TableStyle([
# Header row
("BACKGROUND", (0, 0), (-1, 0), COL_BG_DARK),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.HexColor("#F0FBFF"), colors.HexColor("#FFF0F0"), colors.HexColor("#F0FFF8"), colors.HexColor("#FFF8F0")]),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("ALIGN", (0, 0), (-1, -1), "LEFT"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 9),
("ROWHEIGHT", (0, 1), (-1, -1), 1.4 * cm),
("GRID", (0, 0), (-1, -1), 0.5, COL_DIVIDER),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("RIGHTPADDING", (0, 0), (-1, -1), 8),
("ROUNDEDCORNERS", [6]),
]))
story.append(tbl)
story.append(Spacer(1, 0.5 * cm))
story.append(Paragraph(
"Source: Khurana A.K. — Comprehensive Ophthalmology / Textbook of Ophthalmology. "
"Classification adapted for clinical quick-reference. Always correlate with clinical findings.",
ParagraphStyle("foot", fontSize=8, textColor=colors.gray, alignment=TA_CENTER, italic=True)
))
# Build with cover page
# We use a trick: first page is cover (onFirstPage), rest use later_page
doc.build(story, onFirstPage=cover_page, onLaterPages=later_page)
print(f"PDF saved: {OUTPUT}")
build()
cd /home/daytona/workspace/lov-pdf && python build_pdf.py 2>&1
ls -lh /home/daytona/workspace/lov-pdf/Loss_of_Vision_Khurana.pdf
Loss of Vision Khurana
PDF Document · PDF
| Category | No. of Causes |
|---|---|
| ⚡ Painless Sudden LOV | 18 causes (CRAO, CRVO, RD, VH, AION, Amaurosis Fugax, Occipital stroke, etc.) |
| 🔴 Painful Sudden LOV | 14 causes (AACG, Optic Neuritis, GCA, Corneal Ulcer, Chemical Burns, etc.) |
| 🌿 Painless Gradual LOV | 22 causes (Cataract, POAG, AMD, DR, RP, Optic Atrophy, Chloroquine toxicity, etc.) |
| 🔥 Painful Gradual LOV | 14 causes (Scleritis, Chronic Uveitis, Absolute Glaucoma, VKH, Thyroid Eye, etc.) |