~/refractive-errors/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/refractive-errors/Refractive_Errors_Quick_Reference.pdf"
# ── Colour palette ──────────────────────────────────────────────
NAVY = colors.HexColor("#1A3557")
TEAL = colors.HexColor("#0D7377")
GOLD = colors.HexColor("#F0A500")
LTBLUE = colors.HexColor("#D6E8F7")
LTGREEN = colors.HexColor("#D6F0E8")
LTYELLOW = colors.HexColor("#FFF8E1")
LTORANGE = colors.HexColor("#FDE8D8")
LTGRAY = colors.HexColor("#F4F6F9")
WHITE = colors.white
DARK = colors.HexColor("#1C1C1C")
MIDGRAY = colors.HexColor("#6B7280")
# ── Document ────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=1.8*cm, leftMargin=1.8*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm,
title="Refractive Errors – Quick Reference",
author="Orris Medical Library"
)
# ── Styles ──────────────────────────────────────────────────────
base = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
TITLE = S("TITLE",
fontSize=22, leading=28, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
SUBTITLE = S("SUBTITLE",
fontSize=11, leading=16, textColor=LTBLUE,
fontName="Helvetica", alignment=TA_CENTER)
H1 = S("H1",
fontSize=13, leading=17, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_LEFT,
spaceAfter=2)
H2 = S("H2",
fontSize=11, leading=15, textColor=NAVY,
fontName="Helvetica-Bold", alignment=TA_LEFT,
spaceBefore=6, spaceAfter=3)
BODY = S("BODY",
fontSize=9, leading=13, textColor=DARK,
fontName="Helvetica", alignment=TA_JUSTIFY,
spaceAfter=4)
SMALL = S("SMALL",
fontSize=8, leading=11, textColor=DARK,
fontName="Helvetica")
BOLD_SMALL = S("BOLD_SMALL",
fontSize=8.5, leading=12, textColor=NAVY,
fontName="Helvetica-Bold")
TIP = S("TIP",
fontSize=8.5, leading=12, textColor=colors.HexColor("#7B3F00"),
fontName="Helvetica-BoldOblique")
CELL = S("CELL",
fontSize=8, leading=11, textColor=DARK, fontName="Helvetica")
CELLB = S("CELLB",
fontSize=8, leading=11, textColor=NAVY, fontName="Helvetica-Bold")
HCELL = S("HCELL",
fontSize=8.5, leading=12, textColor=WHITE, fontName="Helvetica-Bold")
PAGE_W = A4[0] - 3.6*cm # usable width
# ── Helper: section header banner ───────────────────────────────
def section_banner(text, bg=NAVY):
data = [[Paragraph(text, H1)]]
t = Table(data, colWidths=[PAGE_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [4]),
]))
return t
# ── Helper: coloured info box ────────────────────────────────────
def info_box(title, lines, bg=LTBLUE, title_color=NAVY):
title_style = S("ib_title", fontSize=9, leading=12,
textColor=title_color, fontName="Helvetica-Bold")
body_style = S("ib_body", fontSize=8.5, leading=12,
textColor=DARK, fontName="Helvetica")
content = [Paragraph(title, title_style)]
for line in lines:
content.append(Paragraph(line, body_style))
data = [[c] for c in content]
t = Table([[Paragraph(title, title_style)]] +
[[Paragraph(l, body_style)] for l in lines],
colWidths=[PAGE_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (0,0), 6),
("BOTTOMPADDING", (0,-1),(-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("LINEBELOW", (0,0), (-1,0), 0.5, title_color),
]))
return t
# ── Helper: standard table ───────────────────────────────────────
def std_table(headers, rows, col_widths, bg_header=NAVY, alt=LTGRAY):
header_row = [Paragraph(h, HCELL) for h in headers]
data = [header_row]
for i, row in enumerate(rows):
styled = [Paragraph(str(c), CELL) for c in row]
data.append(styled)
t = Table(data, colWidths=col_widths, repeatRows=1)
style = [
("BACKGROUND", (0,0), (-1,0), bg_header),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#C5D5E8")),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, alt]),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
t.setStyle(TableStyle(style))
return t
# ════════════════════════════════════════════════════════════════
# BUILD STORY
# ════════════════════════════════════════════════════════════════
story = []
# ── COVER BANNER ────────────────────────────────────────────────
cover_data = [[
Paragraph("REFRACTIVE ERRORS", TITLE),
Paragraph("Quick Reference Guide • Ophthalmology", SUBTITLE),
Paragraph("Myopia • Hypermetropia • Astigmatism • Presbyopia", SUBTITLE),
]]
cover = Table([[
Paragraph("REFRACTIVE ERRORS", TITLE),
],
[
Paragraph("Quick Reference Guide • Ophthalmology", SUBTITLE),
],
[
Paragraph("Myopia • Hypermetropia • Astigmatism • Presbyopia", SUBTITLE),
]], colWidths=[PAGE_W])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,0), 18),
("BOTTOMPADDING", (0,-1),(-1,-1), 18),
("LEFTPADDING", (0,0), (-1,-1), 16),
("RIGHTPADDING", (0,0), (-1,-1), 16),
("ROUNDEDCORNERS",[6]),
]))
story.append(cover)
story.append(Spacer(1, 0.35*cm))
# ── DEFINITION BOX ──────────────────────────────────────────────
defn_rows = [
["Emmetropia", "Normal eye – parallel rays focus exactly on retina at rest"],
["Ametropia", "Any refractive error (myopia, hypermetropia, astigmatism)"],
["Accommodation","Ability of lens to increase power (increase curvature) for near vision"],
["Dioptre (D)", "Unit of lens power = 1 / focal length in metres"],
]
story.append(section_banner("▸ KEY DEFINITIONS"))
story.append(Spacer(1, 0.15*cm))
story.append(std_table(
["Term", "Meaning"],
defn_rows,
[4.5*cm, PAGE_W - 4.5*cm],
bg_header=TEAL
))
story.append(Spacer(1, 0.35*cm))
# ════════════════════════════════════════════
# SECTION 1 – MYOPIA
# ════════════════════════════════════════════
story.append(section_banner("1. MYOPIA (Short-sightedness / Near-sightedness)", bg=colors.HexColor("#1A3557")))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph("Definition & Optics", H2))
story.append(Paragraph(
"Parallel rays of light focus <b>in front of</b> the retina in a relaxed eye. "
"The eye is optically <b>too powerful</b> for its axial length (long eye or steep cornea). "
"Near objects can be seen clearly; distance vision is blurred.",
BODY))
story.append(Paragraph("Types by Cause", H2))
myopia_types = [
["Axial", "Eyeball too long (most common)"],
["Curvature", "Cornea / lens too curved (steep)"],
["Index", "Higher refractive index of lens (e.g. nuclear sclerotic cataract – 'second sight')"],
["Positional", "Lens displaced anteriorly"],
]
story.append(std_table(["Type", "Mechanism"], myopia_types,
[3.5*cm, PAGE_W-3.5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Degrees of Myopia", H2))
myopia_deg = [
["Low (mild)", "Up to −3.00 D"],
["Moderate", "−3.00 D to −6.00 D"],
["High (pathological)", "Greater than −6.00 D – risk of complications"],
]
story.append(std_table(["Grade", "Dioptre Range"], myopia_deg,
[4.5*cm, PAGE_W-4.5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Clinical Features", H2))
myopia_cf = [
["Blurred distance vision", "Near vision unaffected (or better than average)"],
["Squinting / half-closing eyes", "To reduce circle of confusion"],
["Large eyes / prominent globes", "In high axial myopia"],
["Fundus: large disc, temporal crescent", "Peripapillary atrophy, pale disc"],
["Posterior staphyloma", "Outpouching of posterior sclera in pathological myopia"],
]
story.append(std_table(["Feature", "Notes"], myopia_cf,
[5*cm, PAGE_W-5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Complications (High Myopia)", H2))
myopia_comp = [
["Retinal detachment", "Vitreous traction on peripheral retina → tears → detachment"],
["Lattice degeneration", "Peripheral retinal thinning → predisposes to breaks"],
["Posterior staphyloma", "Ectasia of posterior segment"],
["Choroidal neovascularisation (CNV)", "Subretinal membrane → vision loss"],
["Open-angle glaucoma", "Increased susceptibility"],
["Cataract (nuclear)", "Earlier onset in pathological myopia"],
["Macular haemorrhage (Fuchs spot)", "Subretinal haemorrhage at macula"],
]
story.append(std_table(["Complication", "Mechanism / Note"], myopia_comp,
[5.5*cm, PAGE_W-5.5*cm], bg_header=colors.HexColor("#8B0000")))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Correction / Treatment", H2))
myopia_rx = [
["Spectacles", "Concave (−) / diverging lenses"],
["Contact lenses", "Concave contact lenses"],
["LASIK", "Laser ablates central cornea to flatten it; most common refractive surgery"],
["PRK / LASEK", "Surface ablation – good for thin corneas"],
["Phakic IOL (ICL)", "Implantable collamer lens (−3 to −20.5 D); for high myopia"],
["Radial keratotomy (RK)", "Historical; incisions flatten cornea"],
["Orthokeratology", "Rigid contact lenses worn overnight to reshape cornea temporarily"],
]
story.append(std_table(["Method", "Details"], myopia_rx,
[4*cm, PAGE_W-4*cm], bg_header=TEAL))
story.append(Spacer(1, 0.15*cm))
story.append(info_box("⚠ Key Clinical Point",
["Concave (minus/diverging) lenses are used for myopia. In retinoscopy, myopia shows an AGAINST movement of the reflex."],
bg=LTYELLOW, title_color=colors.HexColor("#7B3F00")))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 2 – HYPERMETROPIA
# ════════════════════════════════════════════
story.append(section_banner("2. HYPERMETROPIA (Hyperopia / Far-sightedness)", bg=TEAL))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph("Definition & Optics", H2))
story.append(Paragraph(
"Parallel rays of light focus <b>behind</b> the retina in a relaxed eye. "
"The eye is optically <b>too weak</b> for its axial length (short eye or flat cornea). "
"Near vision is affected more than distance in moderate cases; both are blurred in high hypermetropia.",
BODY))
story.append(Paragraph("Types by Accommodation Status", H2))
hyp_types = [
["Latent", "Overcome entirely by accommodation; revealed only on cycloplegic refraction"],
["Manifest", "Cannot be overcome by max accommodation (= Facultative + Absolute)"],
["Facultative", "Part of manifest that CAN still be overcome by accommodation"],
["Absolute", "Cannot be overcome even by maximum accommodation"],
["Total", "Latent + Manifest (only seen under full cycloplegia)"],
]
story.append(std_table(["Type", "Description"], hyp_types,
[3.5*cm, PAGE_W-3.5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.1*cm))
story.append(Paragraph("Formula: Total = Latent + Manifest; Manifest = Facultative + Absolute", BOLD_SMALL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Clinical Features", H2))
hyp_cf = [
["Asthenopia", "Frontal headache, eye-strain, worse after near work / end of day"],
["Blurred near vision", "More affected than distance in moderate hypermetropia"],
["Both near & distance blurred", "In high hypermetropia (accommodation exhausted)"],
["Accommodative esotropia", "Excessive accommodation drives convergence → convergent squint"],
["Amblyopia", "From untreated esotropia or anisometropia"],
["Small crowded optic disc", "Pseudo-papilloedema appearance on fundoscopy"],
["Shallow anterior chamber", "Predisposes to angle-closure glaucoma"],
]
story.append(std_table(["Feature", "Notes"], hyp_cf,
[5*cm, PAGE_W-5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Hypermetropia in Children", H2))
hyp_kids = [
["Birth", "Most neonates are hypermetropic (+2 to +3 D) – physiological"],
["Up to age 6", "Hypermetropia may increase (emmetropization process)"],
["Age 6–8", "Levels off; decreases thereafter"],
["<4 D without squint", "Generally NOT corrected unless symptomatic"],
[">4 D without squint", "Give two-thirds correction"],
["Any + esotropia", "Full cycloplegic correction, even under age 2"],
]
story.append(std_table(["Age / Situation", "Action / Note"], hyp_kids,
[5*cm, PAGE_W-5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.15*cm))
story.append(info_box("⚠ Key Clinical Point",
["Cycloplegic refraction (cyclopentolate/atropine) is MANDATORY in children to reveal total hypermetropia including the latent component. Convex (+/plus) lenses correct hypermetropia. Retinoscopy shows a WITH movement."],
bg=LTGREEN, title_color=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Correction / Treatment", H2))
hyp_rx = [
["Spectacles", "Convex (+) / converging lenses"],
["Contact lenses", "Plus contact lenses"],
["Surface ablation (PRK/LASEK)", "Corrects low degrees"],
["LASIK", "Up to +4.00 D"],
["Conductive keratoplasty (CK)", "RF energy to corneal periphery → steepens centre; low-moderate hypermetropia; regression common"],
["Clear lens extraction + IOL", "High hypermetropia; IOL replaces natural lens"],
["Phakic IOL", "For high refractive errors; natural lens retained"],
]
story.append(std_table(["Method", "Details"], hyp_rx,
[4.5*cm, PAGE_W-4.5*cm], bg_header=TEAL))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 3 – ASTIGMATISM
# ════════════════════════════════════════════
story.append(section_banner("3. ASTIGMATISM", bg=colors.HexColor("#2E6B3E")))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph("Definition & Optics", H2))
story.append(Paragraph(
"The cornea (or lens) has <b>different curvatures in different meridians</b>. "
"Light rays in different planes come to focus at different points, producing a blurred or distorted image at all distances. "
"Instead of a point focus, two focal lines (focal conoid of Sturm) are formed.",
BODY))
story.append(Paragraph("Types of Astigmatism", H2))
astig_types = [
["Regular", "Principal meridians are 90° apart; correctable with cylindrical lenses"],
["Irregular", "Meridians not perpendicular (e.g. keratoconus, corneal scar); NOT correctable with standard lenses – needs RGP contact lenses"],
["With-the-rule (WTR)", "Vertical meridian steeper (more powerful); corrected with plus cylinder at 90°; common in young"],
["Against-the-rule (ATR)", "Horizontal meridian steeper; corrected with plus cylinder at 180°; common in elderly"],
["Oblique", "Principal meridians between 30°–60° and 120°–150°"],
]
story.append(std_table(["Type", "Description"], astig_types,
[4*cm, PAGE_W-4*cm], bg_header=colors.HexColor("#2E6B3E")))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Types by Refraction (Lenses Required)", H2))
astig_refr = [
["Simple myopic", "One meridian emmetropic, other myopic – one plane focused on retina, other in front"],
["Simple hypermetropic","One meridian emmetropic, other hypermetropic"],
["Compound myopic", "Both meridians myopic but different degrees"],
["Compound hypermetropic","Both meridians hypermetropic but different degrees"],
["Mixed", "One meridian myopic, other hypermetropic – focal lines on either side of retina"],
]
story.append(std_table(["Type", "Description"], astig_refr,
[5*cm, PAGE_W-5*cm], bg_header=colors.HexColor("#2E6B3E")))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Clinical Features & Correction", H2))
astig_cf = [
["Blurred vision at all distances", "Both near and far affected"],
["Distorted / tilted appearance", "Objects may appear stretched"],
["Asthenopia and headaches", "Effort to compensate"],
["Head tilting", "To align principal meridian for better focus"],
["Spectacles", "Cylindrical lenses (spherocylindrical prescription)"],
["Contact lenses", "Toric (hard RGP) lenses; RGP mandatory for irregular astigmatism"],
["LASIK / PRK", "For regular astigmatism"],
["Limbal relaxing incisions", "Surgical correction of corneal astigmatism"],
]
story.append(std_table(["Feature / Treatment", "Notes"], astig_cf,
[5.5*cm, PAGE_W-5.5*cm], bg_header=colors.HexColor("#2E6B3E")))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 4 – PRESBYOPIA
# ════════════════════════════════════════════
story.append(section_banner("4. PRESBYOPIA (Age-related loss of accommodation)", bg=colors.HexColor("#6B4226")))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph("Definition & Mechanism", H2))
story.append(Paragraph(
"Progressive, age-related <b>loss of accommodative amplitude</b> due to "
"hardening (sclerosis) of the crystalline lens and weakening of ciliary muscle. "
"The lens loses its elasticity and cannot change shape to focus near objects. "
"Onset typically around <b>40–45 years</b>.",
BODY))
presbyopia_data = [
["Onset", "~40–45 years; earlier in hypermetropes, later in myopes"],
["Mechanism", "Lens nucleus sclerosis → reduced elasticity → cannot accommodate"],
["Symptom", "Difficulty reading fine print; holds reading material further away ('arm's length reading')"],
["Associated", "Frontal headache after near work; need for brighter light"],
["Correction", "Convex (+) reading glasses (addition over distance correction)"],
["Addition power", "+1.00 D at 40 yrs → gradually increases to +3.00 D by age 60"],
["Bifocals", "Distance correction above + reading addition below in same lens"],
["Progressive lenses", "Gradual power change from distance to near; no visible line"],
["Reading glasses", "For emmetropes or those who remove distance glasses to read"],
["Surgical options", "Monovision LASIK, conductive keratoplasty, accommodating IOL, EDOF IOL"],
]
story.append(std_table(["Parameter", "Detail"], presbyopia_data,
[4*cm, PAGE_W-4*cm], bg_header=colors.HexColor("#6B4226")))
story.append(Spacer(1, 0.15*cm))
story.append(info_box("📌 Clinical Note on Presbyopia",
["Myopes may appear to develop presbyopia later because they remove their distance glasses to read. A new hypermetrope presenting after age 40 may appear to have suddenly developed presbyopia – the previously latent hypermetropia is now unmasked as accommodative amplitude decreases."],
bg=LTORANGE, title_color=colors.HexColor("#7B3F00")))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 5 – COMPARISON TABLE
# ════════════════════════════════════════════
story.append(section_banner("5. COMPARISON AT A GLANCE", bg=NAVY))
story.append(Spacer(1, 0.15*cm))
comp_headers = ["Feature", "Myopia", "Hypermetropia", "Astigmatism", "Presbyopia"]
comp_rows = [
["Focus", "In front of retina", "Behind retina", "Two focal lines", "Behind retina (near)"],
["Eye size", "Long axial length", "Short axial length", "Normal / abnormal cornea", "Normal"],
["Near vision", "Clear", "Blurred (moderate)", "Blurred", "Blurred"],
["Distance vision", "Blurred", "Clear (mild/moderate)", "Blurred", "Clear"],
["Correction lens", "Concave (−)", "Convex (+)", "Cylindrical (±)", "Convex (+) addition"],
["Retinoscopy", "Against movement", "With movement", "Scissors / mixed", "With movement (near)"],
["Squint risk", "Exotropia", "Esotropia", "Amblyopia risk", "None"],
["Glaucoma risk", "Open-angle ↑", "Angle-closure ↑", "Variable", "Low"],
["Fundus", "Large disc, staphyloma", "Small crowded disc", "Usually normal", "Normal"],
["Onset", "Childhood/teens", "Birth (physiological)", "Any age", "~40–45 years"],
]
col_w = [3.8*cm, 3.3*cm, 3.5*cm, 3.3*cm, 3.3*cm]
story.append(std_table(comp_headers, comp_rows, col_w, bg_header=NAVY))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 6 – ANISOMETROPIA
# ════════════════════════════════════════════
story.append(section_banner("6. ANISOMETROPIA & ANISEIKONIA", bg=colors.HexColor("#4A235A")))
story.append(Spacer(1, 0.15*cm))
aniso = [
["Anisometropia", "Unequal refractive error between the two eyes (>1 D difference)"],
["Significance", "Leads to unequal retinal image size → fusion difficulty → suppression → amblyopia"],
["Amblyopia risk", "High in children; >2.5 D anisometropia = significant risk"],
["Treatment", "Full correction of the difference; patching of dominant eye; atropine penalisation"],
["Aniseikonia", "Unequal image size perceived by the two eyes; worsened by spectacles in high anisometropia"],
["Contact lens advantage", "Reduce aniseikonia compared to spectacles (less vertex distance magnification difference)"],
]
story.append(std_table(["Term / Feature", "Description"], aniso,
[4.5*cm, PAGE_W-4.5*cm], bg_header=colors.HexColor("#4A235A")))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 7 – REFRACTIVE SURGERY OVERVIEW
# ════════════════════════════════════════════
story.append(section_banner("7. REFRACTIVE SURGERY – OVERVIEW", bg=colors.HexColor("#0D4C6E")))
story.append(Spacer(1, 0.15*cm))
surg_rows = [
["LASIK", "Flap + excimer laser ablation of stroma", "Myopia, hypermetropia, astigmatism", "Dry eye, flap complications, ectasia"],
["LASEK / PRK", "Surface ablation; epithelium removed/replaced", "Myopia (thin corneas)", "Slower recovery, haze"],
["SMILE", "Flapless – lenticule extracted through small incision", "Myopia, mild astigmatism", "Limited hypermetropia correction"],
["Phakic IOL (ICL)", "Lens implanted in front of natural lens", "High myopia/hypermetropia", "Cataract, uveitis, angle closure"],
["Clear lens extraction", "Natural lens removed, IOL implanted", "High hypermetropia; presbyopia", "Retinal detachment risk (myopia)"],
["Conductive keratoplasty", "RF energy to corneal periphery", "Low–moderate hypermetropia; presbyopia", "Regression, induced astigmatism"],
["Limbal relaxing incisions", "Arcuate corneal incisions", "Corneal astigmatism", "Under/overcorrection"],
]
story.append(std_table(
["Procedure", "Mechanism", "Indications", "Complications"],
surg_rows,
[3*cm, 4*cm, 4.5*cm, PAGE_W-11.5*cm],
bg_header=colors.HexColor("#0D4C6E")
))
story.append(Spacer(1, 0.4*cm))
# ════════════════════════════════════════════
# SECTION 8 – EXAM HIGH-YIELD POINTS
# ════════════════════════════════════════════
story.append(section_banner("8. HIGH-YIELD EXAM POINTS", bg=colors.HexColor("#8B0000")))
story.append(Spacer(1, 0.15*cm))
exam_rows = [
["Myopia correction", "Concave / minus / diverging lens"],
["Hypermetropia correction", "Convex / plus / converging lens"],
["Presbyopia correction", "Convex (+) addition (bifocals / reading glasses)"],
["Astigmatism correction", "Cylindrical lens; toric contact lens"],
["Irregular astigmatism correction", "Rigid gas-permeable (RGP) contact lenses only"],
["Retinoscopy: myopia reflex", "AGAINST movement"],
["Retinoscopy: hypermetropia reflex", "WITH movement"],
["Cycloplegia mandatory", "Children – to reveal latent hypermetropia"],
["Cycloplegic drug used", "Cyclopentolate 1% (children); Atropine 1% (young children)"],
["Hypermetropia → squint type", "Esotropia (convergent squint)"],
["Myopia → squint type", "Exotropia (divergent squint)"],
["Hypermetropia → glaucoma type", "Angle-closure glaucoma (shallow AC)"],
["Myopia → glaucoma type", "Open-angle glaucoma"],
["High myopia: lethal complication", "Retinal detachment, macular degeneration"],
["Accommodation formula", "Total H = Latent + Manifest; Manifest = Facultative + Absolute"],
["Presbyopia onset", "~40–45 years; due to lens sclerosis"],
["LASIK limit for hypermetropia", "Up to +4.00 D"],
["Neonates: default refraction", "Hypermetropic (+2 to +3 D) – physiological"],
["Emmetropization", "Process by which the eye grows to become emmetropic; complete ~age 6–8"],
["Second sight in myopia", "Nuclear cataract increases lens index → induced myopia → patient reads without glasses temporarily"],
["Aphakia", "Absence of lens → extreme hypermetropia; corrected with thick (+10 to +13 D) lenses or IOL"],
["Pseudophakia", "Artificial IOL in place of natural lens"],
]
story.append(std_table(
["Topic", "Key Fact"],
exam_rows,
[6*cm, PAGE_W-6*cm],
bg_header=colors.HexColor("#8B0000")
))
story.append(Spacer(1, 0.3*cm))
# ── FOOTER NOTE ─────────────────────────────────────────────────
footer_style = S("FOOTER",
fontSize=7.5, leading=11, textColor=MIDGRAY,
fontName="Helvetica-Oblique", alignment=TA_CENTER)
story.append(HRFlowable(width=PAGE_W, thickness=0.5, color=MIDGRAY))
story.append(Spacer(1, 0.1*cm))
story.append(Paragraph(
"Sources: Kanski's Clinical Ophthalmology 10th Ed. • Adams & Victor's Principles of Neurology 12th Ed. • "
"Plum & Posner's Diagnosis and Treatment of Stupor and Coma | "
"Generated by Orris Medical Library · For educational use only",
footer_style
))
# ── BUILD ────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved to: {OUTPUT}")