Visual acuity examination with images
visual acuity examination Snellen chart techniques ophthalmology


- Kanski's Clinical Ophthalmology 10th, p. 17
- Adams and Victor's Principles of Neurology 12th, p. 251


| Level | Notation | Description |
|---|---|---|
| Counting Fingers | CF | Patient counts fingers held at a specified distance (usually 1 m) |
| Hand Movements | HM | Patient detects whether the examiner's hand is moving |
| Perception of Light | PL | Patient detects only light from a pen torch |
| No Perception of Light | NPL | Complete blindness |

| Feature | Snellen | LogMAR |
|---|---|---|
| Letters per line | Variable (unbalanced crowding) | Equal (5 letters per line) |
| Letter spacing | Irregular | Systematic, related to letter size |
| Testing distance | 6 m (needs long lane or mirror) | 4 m on many charts |
| Accuracy/consistency | Lower | Higher |
| Scoring | Straightforward | More complex |
| Suitability for research | Less suitable | Standard for research |
| Ease of use | Easy | Less user-friendly |
- Kanski's Clinical Ophthalmology 10th, pp. 17-18

- Kanski's Clinical Ophthalmology 10th, pp. 734-736
| Result | Meaning |
|---|---|
| 6/6 or 20/20 | Normal distance vision |
| 6/60 or 20/200 | Legal blindness threshold (in most countries) |
| CF 1 m | Can count fingers at 1 metre |
| HM | Hand movement only |
| PL | Light perception only |
| NPL | No perception of light |
Create a quick reference PDF on VA notation and conversion for clinical use
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/va-reference && echo "created"
~/va-reference/create_va_pdf.py
#!/usr/bin/env python3
"""
Visual Acuity Quick Reference PDF - Clinical Use
Uses ReportLab for rich PDF generation
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import BalancedColumns
from reportlab.lib.colors import HexColor, white, black
OUTPUT = "/home/daytona/workspace/va-reference/VA_Quick_Reference.pdf"
# ── Colour palette ─────────────────────────────────────────────────────────────
TEAL = HexColor("#006D6F") # header / accent
TEAL_LIGHT = HexColor("#E6F4F4") # table row shading
AMBER = HexColor("#E87722") # call-out stripe
AMBER_LIGHT= HexColor("#FFF3E6")
SLATE = HexColor("#2C3E50") # body text
GREY_MID = HexColor("#7F8C8D")
GREY_LIGHT = HexColor("#F2F4F4") # alternate rows
WHITE = white
RED_SOFT = HexColor("#C0392B")
GREEN_SOFT = HexColor("#27AE60")
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=15*mm, rightMargin=15*mm,
topMargin=14*mm, bottomMargin=14*mm,
title="Visual Acuity Quick Reference",
author="Orris Clinical Reference",
subject="VA Notation, Conversion & Clinical Grading"
)
W, H = A4
CONTENT_W = W - 30*mm
styles = getSampleStyleSheet()
# Custom styles
def S(name, **kw):
return ParagraphStyle(name, **kw)
ST_TITLE = S("T", fontName="Helvetica-Bold", fontSize=20, textColor=WHITE,
alignment=TA_CENTER, spaceAfter=2)
ST_SUBTITLE = S("Sub", fontName="Helvetica", fontSize=9.5, textColor=HexColor("#D0ECEC"),
alignment=TA_CENTER, spaceAfter=0)
ST_SECTION = S("Sec", fontName="Helvetica-Bold", fontSize=10, textColor=WHITE,
alignment=TA_LEFT, spaceAfter=0, spaceBefore=0,
leftIndent=4, leading=14)
ST_BODY = S("B", fontName="Helvetica", fontSize=8.4, textColor=SLATE,
spaceAfter=2, leading=12)
ST_BOLD = S("BB", fontName="Helvetica-Bold", fontSize=8.4, textColor=SLATE,
spaceAfter=2, leading=12)
ST_FOOT = S("Ft", fontName="Helvetica-Oblique", fontSize=7, textColor=GREY_MID,
alignment=TA_CENTER)
ST_NOTE = S("Note", fontName="Helvetica-Oblique", fontSize=7.8, textColor=SLATE,
spaceAfter=1, leading=11)
ST_CALLOUT = S("Call", fontName="Helvetica-Bold", fontSize=8, textColor=WHITE,
alignment=TA_CENTER)
ST_TH = S("TH", fontName="Helvetica-Bold", fontSize=8.2, textColor=WHITE,
alignment=TA_CENTER, leading=11)
ST_TD = S("TD", fontName="Helvetica", fontSize=8, textColor=SLATE,
alignment=TA_CENTER, leading=11)
ST_TD_L = S("TDL", fontName="Helvetica", fontSize=8, textColor=SLATE,
alignment=TA_LEFT, leading=11, leftIndent=3)
ST_TD_B = S("TDB", fontName="Helvetica-Bold", fontSize=8, textColor=SLATE,
alignment=TA_CENTER, leading=11)
# ── Helper: section header ──────────────────────────────────────────────────────
def section_header(text, color=TEAL):
data = [[Paragraph(text, ST_SECTION)]]
t = Table(data, colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("GRID", (0,0), (-1,-1), 0, color),
]))
return t
def th(text): return Paragraph(text, ST_TH)
def td(text): return Paragraph(text, ST_TD)
def tdl(text): return Paragraph(text, ST_TD_L)
def tdb(text): return Paragraph(text, ST_TD_B)
def styled_table(data, col_widths, header_rows=1, alt=True):
t = Table(data, colWidths=col_widths, repeatRows=header_rows)
ts = [
("BACKGROUND", (0,0), (-1, header_rows-1), TEAL),
("TEXTCOLOR", (0,0), (-1, header_rows-1), WHITE),
("FONTNAME", (0,0), (-1, header_rows-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("ROWBACKGROUND", (0, header_rows), (-1,-1),
[GREY_LIGHT, WHITE] if alt else [WHITE]),
("LINEBELOW", (0,0), (-1,-1), 0.4, HexColor("#CBD5D5")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("BOX", (0,0), (-1,-1), 0.6, TEAL),
]
t.setStyle(TableStyle(ts))
return t
# ── Build story ─────────────────────────────────────────────────────────────────
story = []
# ── TITLE BANNER ────────────────────────────────────────────────────────────────
banner_data = [[
Paragraph("VISUAL ACUITY", ST_TITLE),
Paragraph("Quick Reference · Notation & Conversion", ST_SUBTITLE),
]]
banner = Table(
[[Paragraph("VISUAL ACUITY — Quick Reference", ST_TITLE)],
[Paragraph("Notation · Conversion · Clinical Grading · Paediatric Testing", ST_SUBTITLE)]],
colWidths=[CONTENT_W]
)
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 1, TEAL),
]))
story.append(banner)
story.append(Spacer(1, 4*mm))
# ── ROW 1: Snellen notation formula + Pinhole key points ────────────────────────
# ── SECTION 1: VA NOTATION SYSTEMS ──────────────────────────────────────────────
story.append(section_header("1 VA NOTATION SYSTEMS"))
story.append(Spacer(1, 1.5*mm))
notation_data = [
[th("System"), th("Normal"), th("Blind Threshold"), th("Notes")],
[tdl("Snellen (metric)"), td("6/6"), td("6/60"), tdl("Numerator = test dist (m); denominator = dist at which normal eye reads same letter")],
[tdl("Snellen (feet)"), td("20/20"), td("20/200"), tdl("Same principle; used mainly in USA")],
[tdl("LogMAR"), td("0.00"), td("1.00"), tdl("Log₁₀(MAR); each 0.1 step = one chart line; negative = better than 6/6")],
[tdl("Decimal"), td("1.0"), td("0.1"), tdl("Reciprocal of MAR in minutes; 6/6 = 1.0; 6/60 = 0.1")],
[tdl("Jaeger (near card)"), td("J1"), td("J16"), tdl("Used at 14 in (35 cm); J1 ≈ 20/20; held bedside without distance chart")],
]
story.append(styled_table(notation_data,
col_widths=[38*mm, 22*mm, 28*mm, CONTENT_W-88*mm]))
story.append(Spacer(1, 4*mm))
# ── SECTION 2: MASTER CONVERSION TABLE ──────────────────────────────────────────
story.append(section_header("2 MASTER CONVERSION TABLE"))
story.append(Spacer(1, 1.5*mm))
# Columns: Snellen (m) | Snellen (ft) | Decimal | LogMAR | Jaeger | Clinical category
conv_data = [
[th("Snellen\n(6 m)"), th("Snellen\n(20 ft)"), th("Decimal"), th("LogMAR"), th("Jaeger\n(near)"), th("Clinical Category")],
[tdb("6/3"), tdb("20/10"), td("2.0"), td("−0.30"), td("—"), tdl("Supernormal")],
[tdb("6/4"), tdb("20/13"), td("1.5"), td("−0.18"), td("—"), tdl("Supernormal")],
[tdb("6/5"), tdb("20/16"), td("1.2"), td("−0.08"), td("J1+"),tdl("Excellent")],
[tdb("6/6"), tdb("20/20"), td("1.0"), td("0.00"), td("J1"), tdl("Normal — pass most driving standards")],
[tdb("6/7.5"), tdb("20/25"), td("0.8"), td("0.10"), td("J2"), tdl("Normal range")],
[tdb("6/9"), tdb("20/30"), td("0.67"), td("0.18"), td("J2"), tdl("Normal range")],
[tdb("6/12"), tdb("20/40"), td("0.5"), td("0.30"), td("J3"), tdl("Mild impairment — may fail driving standard")],
[tdb("6/18"), tdb("20/60"), td("0.33"), td("0.48"), td("J5"), tdl("Mild–moderate impairment")],
[tdb("6/24"), tdb("20/80"), td("0.25"), td("0.60"), td("J7"), tdl("Moderate impairment")],
[tdb("6/36"), tdb("20/120"), td("0.17"), td("0.78"), td("J9"), tdl("Moderate–severe")],
[tdb("6/60"), tdb("20/200"), td("0.10"), td("1.00"), td("J16"),tdl("Severe impairment — legal blindness (many countries)")],
[tdb("3/60"), tdb("10/200"), td("0.05"), td("1.30"), td("—"), tdl("Profound impairment")],
[tdb("1/60"), tdb("3/200"), td("0.017"),td("1.78"), td("—"), tdl("Near-total vision loss")],
[tdb("CF"), tdb("CF"), td("—"), td("—"), td("—"), tdl("Counting fingers (record distance, e.g. CF 1 m)")],
[tdb("HM"), tdb("HM"), td("—"), td("—"), td("—"), tdl("Hand movements only")],
[tdb("PL"), tdb("PL"), td("—"), td("—"), td("—"), tdl("Perception of light only")],
[tdb("NPL"), tdb("NPL"), td("—"), td("—"), td("—"), tdl("No perception of light — total blindness")],
]
t_conv = Table(conv_data,
colWidths=[18*mm, 18*mm, 17*mm, 17*mm, 17*mm, CONTENT_W-87*mm],
repeatRows=1)
row_colors = [
("BACKGROUND", (0,1),(−1,1), HexColor("#E8F8F5")), # supernormal
]
ts_conv = [
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.8),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING",(0,0), (-1,-1), 3),
("RIGHTPADDING",(0,0),(-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LINEBELOW", (0,0), (-1,-1), 0.3, HexColor("#CBD5D5")),
("BOX", (0,0), (-1,-1), 0.7, TEAL),
# Highlight 6/6 row (index 4)
("BACKGROUND", (0,4), (-1,4), HexColor("#D5F5E3")),
("FONTNAME", (0,4), (-1,4), "Helvetica-Bold"),
# Highlight 6/60 row (index 10)
("BACKGROUND", (0,10),(-1,10), HexColor("#FDEBD0")),
("FONTNAME", (0,10),(-1,10), "Helvetica-Bold"),
# CF/HM/PL/NPL rows: light pink
("BACKGROUND", (0,13),(-1,16), HexColor("#FDEDEC")),
# Alt rows
("ROWBACKGROUND", (0,1),(-1,-1), [GREY_LIGHT, WHITE]),
]
t_conv.setStyle(TableStyle(ts_conv))
story.append(t_conv)
story.append(Spacer(1, 1.5*mm))
# Footnote for bold rows
note_text = (
"<b>Green highlight</b> = 6/6 (20/20) normal benchmark "
"<b>Orange highlight</b> = 6/60 (20/200) legal blindness threshold "
"<b>Pink rows</b> = qualitative assessments (below Snellen chart range)"
)
story.append(Paragraph(note_text, ST_NOTE))
story.append(Spacer(1, 4*mm))
# ── SECTION 3: LOGMAR FORMULA + KEY FACTS ───────────────────────────────────────
story.append(KeepTogether([
section_header("3 LogMAR — KEY FACTS & FORMULA"),
Spacer(1, 1.5*mm),
]))
logmar_col1 = [
Paragraph("<b>Formula:</b> logMAR = log₁₀(MAR)", ST_BODY),
Spacer(1, 1*mm),
Paragraph("<b>MAR</b> = minimum angle of resolution (in minutes of arc)", ST_BODY),
Spacer(1, 1*mm),
Paragraph("<b>Quick conversion from Snellen (metres):</b>", ST_BODY),
Paragraph(" logMAR = log₁₀(denominator ÷ 6)", ST_BODY),
Spacer(1, 1*mm),
Paragraph("<b>Scoring:</b> 5 letters per line × 0.02 per letter = 0.1 per line", ST_BODY),
Paragraph(" Continue until ≥3 letters missed on a line", ST_BODY),
Spacer(1, 1*mm),
Paragraph("<b>Advantage over Snellen:</b> equal letters per row, systematic spacing, "
"each letter scored individually → more sensitive for monitoring change", ST_BODY),
]
logmar_col2 = [
Paragraph("<b>Common values at a glance:</b>", ST_BODY),
Spacer(1, 1*mm),
Paragraph(" 6/6 → logMAR <b>0.00</b>", ST_BODY),
Paragraph(" 6/7.5 → logMAR <b>0.10</b>", ST_BODY),
Paragraph(" 6/12 → logMAR <b>0.30</b>", ST_BODY),
Paragraph(" 6/18 → logMAR <b>0.48</b>", ST_BODY),
Paragraph(" 6/24 → logMAR <b>0.60</b>", ST_BODY),
Paragraph(" 6/60 → logMAR <b>1.00</b>", ST_BODY),
Spacer(1, 1*mm),
Paragraph("Better than 6/6 → <b>negative</b> logMAR value", ST_BODY),
]
lm_table = Table(
[[logmar_col1, logmar_col2]],
colWidths=[CONTENT_W*0.58, CONTENT_W*0.42]
)
lm_table.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING",(0,0), (-1,-1), 0),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
("LINEAFTER", (0,0), (0,-1), 0.5, HexColor("#CBD5D5")),
]))
story.append(lm_table)
story.append(Spacer(1, 4*mm))
# ── SECTION 4: SNELLEN PROCEDURE + PINHOLE ──────────────────────────────────────
story.append(KeepTogether([
section_header("4 EXAMINATION PROCEDURE & PINHOLE TEST"),
Spacer(1, 1.5*mm),
]))
proc_col1 = [
Paragraph("<b>Distance VA (Snellen) — Steps:</b>", ST_BODY),
Spacer(1, 1*mm),
Paragraph("1. Test the <b>worse eye first</b>, occlude the other", ST_BODY),
Paragraph("2. Use patient's habitual distance correction", ST_BODY),
Paragraph("3. Test at <b>6 m</b> (or 3 m with appropriate chart)", ST_BODY),
Paragraph("4. Encourage patient to read the <b>smallest line possible</b>", ST_BODY),
Paragraph("5. Record as corrected VA (e.g. 6/9 c)", ST_BODY),
Paragraph("6. If VA < 6/6 → repeat with <b>pinhole</b>", ST_BODY),
Paragraph("7. Also record unaided VA if clinically indicated", ST_BODY),
Spacer(1, 2*mm),
Paragraph("<b>Near VA (Jaeger card):</b>", ST_BODY),
Paragraph("Hold card at <b>14 in (35 cm)</b> — bedside or when no distance chart available", ST_BODY),
]
proc_col2 = [
Paragraph("<b>Pinhole Test:</b>", ST_BODY),
Spacer(1, 1*mm),
Paragraph("A 1 mm aperture eliminates refractive blur by admitting "
"only central, parallel rays.", ST_BODY),
Spacer(1, 1*mm),
Paragraph("✔ VA <b>improves</b> with PH →", ST_BOLD),
Paragraph(" Refractive error or media opacity (cornea, lens, vitreous)", ST_BODY),
Spacer(1, 1*mm),
Paragraph("✘ VA <b>does not improve</b> with PH →", ST_BOLD),
Paragraph(" Likely <b>retinal or neurological</b> cause", ST_BODY),
Spacer(1, 1*mm),
Paragraph("⚠ PH may worsen VA in <b>macular disease</b> and posterior lens opacities "
"— compare with spectacle VA.", ST_NOTE),
]
proc_table = Table(
[[proc_col1, proc_col2]],
colWidths=[CONTENT_W*0.52, CONTENT_W*0.48]
)
proc_table.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING",(0,0), (-1,-1), 0),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
("LINEAFTER", (0,0), (0,-1), 0.5, HexColor("#CBD5D5")),
]))
story.append(proc_table)
story.append(Spacer(1, 4*mm))
# ── SECTION 5: QUALITATIVE VA SCALE ─────────────────────────────────────────────
story.append(KeepTogether([
section_header("5 QUALITATIVE VA SCALE (Below Snellen Range)"),
Spacer(1, 1.5*mm),
]))
qual_data = [
[th("Notation"), th("Abbreviation"), th("Method"), th("Clinical Notes")],
[tdl("Counting Fingers"), td("CF"), tdl("Count fingers at specified distance (record: e.g. CF 1 m)"),
tdl("If no refractive or media cause, suggests very poor central or peripheral VA")],
[tdl("Hand Movements"), td("HM"), tdl("Detect if examiner's hand is moving in front of patient"),
tdl("Severe visual loss; check projection of light")],
[tdl("Perception of Light"), td("PL"), tdl("Can detect pen torch light — check all 4 quadrants for projection"),
tdl("If media opacity alone (e.g. cataract): should detect direction of light")],
[tdl("No Perception of Light"), td("NPL"), tdl("Cannot detect any light stimulus"),
tdl("Occlude fellow eye carefully; confirm in darkness")],
]
story.append(styled_table(qual_data,
col_widths=[35*mm, 22*mm, 62*mm, CONTENT_W-119*mm]))
story.append(Spacer(1, 4*mm))
# ── SECTION 6: PAEDIATRIC VA TESTING ────────────────────────────────────────────
story.append(KeepTogether([
section_header("6 PAEDIATRIC VA TESTING — BY AGE"),
Spacer(1, 1.5*mm),
]))
peds_data = [
[th("Age"), th("Method"), th("Key Points")],
[tdl("0–6 months"), tdl("Fixation & following; Rotation test"),
tdl("Face is the best target. Post-rotary nystagmus persists if vision severely impaired.")],
[tdl("6 m – 2 yr"), tdl("Preferential looking (Teller/Keeler/Cardiff acuity cards)"),
tdl("Infants prefer pattern over uniform field. Examiner is masked to target position.")],
[tdl("2 – 3 years"), tdl("Crowded Kay pictures (picture naming)"),
tdl("Must use crowded test if amblyopia suspected; single optotypes overestimate acuity.")],
[tdl("≥3 years"), tdl("Crowded letter matching (Keeler logMAR, Sonksen, Otago)"),
tdl("LogMAR preferred over Snellen for all children at risk of amblyopia. Test at 3–4 m.")],
[tdl("Any age"), tdl("VEP (pattern visual evoked potential)"),
tdl("Objective; used for optic neuropathy diagnosis, not routine acuity screening.")],
]
story.append(styled_table(peds_data,
col_widths=[28*mm, 55*mm, CONTENT_W-83*mm]))
story.append(Spacer(1, 4*mm))
# ── SECTION 7: DRIVING / LEGAL STANDARDS CALLOUT ────────────────────────────────
story.append(KeepTogether([
section_header("7 CLINICAL THRESHOLDS AT A GLANCE", color=AMBER),
Spacer(1, 1.5*mm),
]))
thresh_data = [
[th("Standard"), th("VA Threshold"), th("Notes")],
[tdl("Normal distance vision"), tdb("6/6 (20/20)"), tdl("logMAR 0.00 — benchmark for 'normal'")],
[tdl("UK driving standard (car)"), tdb("6/12 (20/40)"), tdl("Must read number plate at 20 m; logMAR 0.30")],
[tdl("WHO Moderate impairment"), tdb("< 6/18"), tdl("With best correction")],
[tdl("WHO Severe impairment"), tdb("< 6/60"), tdl("With best correction")],
[tdl("WHO Blindness"), tdb("< 3/60 (20/400)"),tdl("Or visual field < 10° — logMAR ≥ 1.30")],
[tdl("Legal blindness (US/many)"), tdb("6/60 (20/200)"), tdl("Best-corrected VA; or visual field ≤ 20°")],
[tdl("Profound impairment"), tdb("< 1/60"), tdl("CF, HM, PL, or NPL level")],
]
t_thresh = Table(thresh_data,
colWidths=[60*mm, 38*mm, CONTENT_W-98*mm], repeatRows=1)
t_thresh.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), AMBER),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 3.5),
("BOTTOMPADDING",(0,0), (-1,-1), 3.5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LINEBELOW", (0,0), (-1,-1), 0.3, HexColor("#CBD5D5")),
("BOX", (0,0), (-1,-1), 0.7, AMBER),
("ROWBACKGROUND",(0,1), (-1,-1), [AMBER_LIGHT, WHITE]),
# Highlight 6/6 row
("BACKGROUND", (0,1), (-1,1), HexColor("#D5F5E3")),
# Highlight blindness rows
("BACKGROUND", (0,4), (-1,5), HexColor("#FDEBD0")),
]))
story.append(t_thresh)
story.append(Spacer(1, 4*mm))
# ── FOOTER ───────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width=CONTENT_W, thickness=0.6, color=TEAL))
story.append(Spacer(1, 1.5*mm))
story.append(Paragraph(
"Sources: Kanski's Clinical Ophthalmology 10th ed. · Adams & Victor's Neurology 12th ed. · "
"WHO Visual Impairment Classification · StatPearls — Snellen Chart | "
"Generated by Orris Clinical Reference · For educational use",
ST_FOOT
))
# ── BUILD ────────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")