Make a pdf on the topic dry eye - defination , classification, evaluation and step wise management - take reference from the standard dews
DEWS II 2017 dry eye definition classification management stepwise guidelines
"dry eye" AND "DEWS" AND classification management
"dry eye disease" AND "TFOS DEWS"
mkdir -p /tmp/workspace/dry-eye-pdf && ls /tmp/workspace/dry-eye-pdf/
~/dry-eye-pdf/generate_dry_eye_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, white, black
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
from reportlab.lib import colors
# ─── COLOR PALETTE ───────────────────────────────────────────────────────────
DARK_BLUE = HexColor("#0D3B66")
MID_BLUE = HexColor("#1565C0")
LIGHT_BLUE = HexColor("#E3F2FD")
TEAL = HexColor("#00838F")
TEAL_LIGHT = HexColor("#E0F7FA")
ORANGE = HexColor("#E65100")
ORANGE_SOFT = HexColor("#FFF3E0")
GREY_DARK = HexColor("#37474F")
GREY_MID = HexColor("#78909C")
GREY_LIGHT = HexColor("#ECEFF1")
GREEN = HexColor("#2E7D32")
GREEN_LIGHT = HexColor("#E8F5E9")
RED_LIGHT = HexColor("#FFEBEE")
RED = HexColor("#C62828")
PAGE_W, PAGE_H = A4
# ─── STYLES ──────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent='Normal', **kwargs):
s = ParagraphStyle(name, parent=styles[parent], **kwargs)
return s
title_style = make_style('TitleMain',
fontSize=26, leading=32, textColor=white,
fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=4)
subtitle_style = make_style('TitleSub',
fontSize=13, leading=18, textColor=HexColor("#BBDEFB"),
fontName='Helvetica', alignment=TA_CENTER, spaceAfter=2)
date_style = make_style('DateStyle',
fontSize=10, leading=14, textColor=HexColor("#90CAF9"),
fontName='Helvetica-Oblique', alignment=TA_CENTER)
h1_style = make_style('H1',
fontSize=16, leading=22, textColor=white,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=4, spaceAfter=4)
h2_style = make_style('H2',
fontSize=12, leading=17, textColor=DARK_BLUE,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=8, spaceAfter=3)
h3_style = make_style('H3',
fontSize=10.5, leading=15, textColor=TEAL,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=6, spaceAfter=2)
body_style = make_style('Body',
fontSize=9.5, leading=14, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_JUSTIFY,
spaceBefore=2, spaceAfter=2)
bullet_style = make_style('Bullet',
fontSize=9.5, leading=14, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_LEFT,
leftIndent=14, firstLineIndent=-10,
spaceBefore=1, spaceAfter=1)
sub_bullet_style = make_style('SubBullet',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_LEFT,
leftIndent=28, firstLineIndent=-10,
spaceBefore=1, spaceAfter=1)
tip_style = make_style('Tip',
fontSize=9.5, leading=14, textColor=DARK_BLUE,
fontName='Helvetica-Oblique', alignment=TA_LEFT,
leftIndent=8, rightIndent=8,
spaceBefore=4, spaceAfter=4)
ref_style = make_style('Ref',
fontSize=8, leading=11, textColor=GREY_MID,
fontName='Helvetica-Oblique', alignment=TA_LEFT,
spaceBefore=1, spaceAfter=1)
footer_style = make_style('Footer',
fontSize=7.5, leading=10, textColor=GREY_MID,
fontName='Helvetica', alignment=TA_CENTER)
caption_style = make_style('Caption',
fontSize=8.5, leading=12, textColor=GREY_MID,
fontName='Helvetica-Oblique', alignment=TA_CENTER,
spaceBefore=2, spaceAfter=4)
# ─── HELPERS ─────────────────────────────────────────────────────────────────
def section_header(text, color=DARK_BLUE):
"""Blue banner header for major sections."""
data = [[Paragraph(text, h1_style)]]
t = Table(data, colWidths=[PAGE_W - 4*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('ROWPADDING', (0,0), (-1,-1), [8, 6, 8, 6]),
('BOX', (0,0), (-1,-1), 0, color),
]))
return t
def info_box(title, items, bg=LIGHT_BLUE, title_color=MID_BLUE, bullet='•'):
"""Colored box with title and bullet list."""
content = [Paragraph(f'<b>{title}</b>', make_style('BoxTitle',
fontSize=10, leading=14, textColor=title_color,
fontName='Helvetica-Bold', spaceBefore=0, spaceAfter=4))]
for item in items:
content.append(Paragraph(f'{bullet} {item}', make_style('BoxBullet',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, firstLineIndent=0,
spaceBefore=1, spaceAfter=1)))
data = [[content]]
t = Table(data, colWidths=[PAGE_W - 4*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg),
('ROWPADDING', (0,0), (-1,-1), [10, 8, 10, 8]),
('BOX', (0,0), (-1,-1), 1.5, title_color),
('ROUNDEDCORNERS', [4]),
]))
return t
def two_col_table(left_items, right_items, left_title, right_title,
left_bg=LIGHT_BLUE, right_bg=TEAL_LIGHT,
left_tc=MID_BLUE, right_tc=TEAL):
def make_cell(title, items, bg, tc):
cell = [Paragraph(f'<b>{title}</b>', make_style('CT',
fontSize=10, leading=14, textColor=tc,
fontName='Helvetica-Bold', spaceBefore=0, spaceAfter=4))]
for i in items:
cell.append(Paragraph(f'• {i}', make_style('CB',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, spaceBefore=1, spaceAfter=1)))
return cell
data = [[make_cell(left_title, left_items, left_bg, left_tc),
make_cell(right_title, right_items, right_bg, right_tc)]]
col_w = (PAGE_W - 4*cm - 0.4*cm) / 2
t = Table(data, colWidths=[col_w, col_w], hAlign='LEFT')
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), left_bg),
('BACKGROUND', (1,0), (1,-1), right_bg),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), [8, 6, 8, 6]),
('BOX', (0,0), (0,-1), 1.5, left_tc),
('BOX', (1,0), (1,-1), 1.5, right_tc),
('LEFTPADDING', (0,0), (-1,-1), 8),
('RIGHTPADDING', (0,0), (-1,-1), 8),
]))
return t
def level_box(level_num, title, items, colors_map):
bg, tc, border = colors_map.get(level_num, (GREY_LIGHT, GREY_DARK, GREY_MID))
label = Paragraph(f'<b>LEVEL {level_num}</b>', make_style(f'L{level_num}Label',
fontSize=22, leading=26, textColor=tc,
fontName='Helvetica-Bold', alignment=TA_CENTER))
sub = Paragraph(title, make_style(f'L{level_num}Sub',
fontSize=10, leading=13, textColor=tc,
fontName='Helvetica-Bold', alignment=TA_CENTER))
bullets = []
for i in items:
bullets.append(Paragraph(f'• {i}', make_style(f'L{level_num}B',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, spaceBefore=1, spaceAfter=1)))
left_cell = [label, Spacer(1, 4), sub]
right_cell = bullets
col_w_l = 3.5*cm
col_w_r = (PAGE_W - 4*cm) - col_w_l - 0.2*cm
data = [[left_cell, right_cell]]
t = Table(data, colWidths=[col_w_l, col_w_r])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), bg),
('BACKGROUND', (1,0), (1,-1), white),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('VALIGN', (1,0), (1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), [8, 8, 8, 8]),
('BOX', (0,0), (-1,-1), 2, border),
('LINEAFTER', (0,0), (0,-1), 1, border),
('LEFTPADDING', (0,0), (0,-1), 6),
('LEFTPADDING', (1,0), (1,-1), 10),
]))
return t
# ─── PAGE TEMPLATE ───────────────────────────────────────────────────────────
def add_page_decorations(canvas, doc):
canvas.saveState()
# Top thin stripe
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, PAGE_H - 6*mm, PAGE_W, 6*mm, fill=1, stroke=0)
# Bottom footer bar
canvas.setFillColor(GREY_LIGHT)
canvas.rect(0, 0, PAGE_W, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(GREY_MID)
canvas.setFont('Helvetica', 7.5)
canvas.drawCentredString(PAGE_W/2, 0.45*cm,
f'Dry Eye Disease - A Clinical Reference | TFOS DEWS II Framework | Page {doc.page}')
canvas.restoreState()
def build_cover(canvas, doc):
canvas.saveState()
# Full-page gradient-like background using two rects
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
canvas.setFillColor(MID_BLUE)
canvas.rect(0, 0, PAGE_W, PAGE_H * 0.45, fill=1, stroke=0)
# Decorative circles
canvas.setFillColor(HexColor("#1976D2"))
canvas.circle(PAGE_W - 3*cm, PAGE_H - 3*cm, 5*cm, fill=1, stroke=0)
canvas.setFillColor(HexColor("#1565C0"))
canvas.circle(3*cm, 4*cm, 3.5*cm, fill=1, stroke=0)
canvas.restoreState()
# ─── BUILD THE PDF ────────────────────────────────────────────────────────────
OUTPUT = '/tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf'
doc = BaseDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=1.5*cm, bottomMargin=1.8*cm,
)
cover_frame = Frame(0, 0, PAGE_W, PAGE_H, id='cover')
content_frame = Frame(2*cm, 1.8*cm, PAGE_W - 4*cm, PAGE_H - 3.3*cm, id='content')
cover_template = PageTemplate(id='Cover', frames=[cover_frame], onPage=build_cover)
main_template = PageTemplate(id='Main', frames=[content_frame], onPage=add_page_decorations)
doc.addPageTemplates([cover_template, main_template])
story = []
# ══════════════════════════════════════════════════════════════════════════════
# COVER PAGE
# ══════════════════════════════════════════════════════════════════════════════
story.append(Spacer(1, 5*cm))
story.append(Paragraph("DRY EYE DISEASE", title_style))
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph("A Comprehensive Clinical Reference", subtitle_style))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Definition · Classification · Evaluation · Stepwise Management",
subtitle_style))
story.append(Spacer(1, 0.6*cm))
story.append(HRFlowable(width="70%", thickness=1.5, color=HexColor("#64B5F6"),
hAlign='CENTER'))
story.append(Spacer(1, 0.6*cm))
story.append(Paragraph(
"Based on the TFOS DEWS II (Tear Film & Ocular Surface Society<br/>"
"Dry Eye Workshop II, 2017) International Guidelines",
subtitle_style))
story.append(Spacer(1, 1.2*cm))
story.append(Paragraph(
"References: Kanski's Clinical Ophthalmology (10th Ed.) · "
"Wills Eye Manual (8th Ed.) · TFOS DEWS II Report",
date_style))
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph("July 2026", date_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1: DEFINITION & OVERVIEW
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("1. DEFINITION AND OVERVIEW", DARK_BLUE))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("TFOS DEWS II Definition (2017)", h2_style))
story.append(Paragraph(
"Dry eye is a <b>multifactorial disease of the ocular surface</b> "
"characterised by a loss of homeostasis of the tear film, and accompanied by "
"ocular symptoms, in which <b>tear film instability and hyperosmolarity, "
"ocular surface inflammation and damage, and neurosensory abnormalities</b> "
"play etiological roles.",
body_style))
story.append(Spacer(1, 0.2*cm))
# Definition box
story.append(info_box(
"Key Conceptual Pillars of DED (DEWS II)",
[
"Loss of tear film HOMEOSTASIS is the central concept",
"Ocular symptoms must be present (distinguishes from asymptomatic tear dysfunction)",
"Tear film instability → hyperosmolarity → inflammation → ocular surface damage",
"Neurosensory abnormalities recognized as etiological contributors",
"Positive feedback (vicious cycle) perpetuates and amplifies the disease",
],
bg=LIGHT_BLUE, title_color=DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("Tear Film Structure and Function", h2_style))
story.append(Paragraph(
"The tear film is a complex, trilaminar fluid layer covering the ocular surface, "
"composed of three interacting layers:",
body_style))
story.append(Spacer(1, 0.15*cm))
tear_data = [
["Layer", "Source", "Composition", "Function"],
["Lipid (Outer)", "Meibomian glands\n(tarsal glands)", "Phospholipids, triglycerides,\ncholesterol esters, wax esters",
"Retards evaporation;\nstabilizes tear film; smooths optical surface"],
["Aqueous (Middle)", "Main & accessory\nlacrimal glands", "Water, electrolytes, proteins\n(lactoferrin, lysozyme, IgA)",
"Nourishes cornea; dilutes irritants;\nantimicrobial; oxygen supply"],
["Mucin (Inner)", "Goblet cells;\ncorneal epithelium",
"Transmembrane mucins (MUC1,4,16);\nsecreted mucins (MUC5AC)",
"Converts hydrophobic surface to hydrophilic;\nfacilitates wetting and lubrication"],
]
tear_table = Table(tear_data, colWidths=[2.8*cm, 3.2*cm, 4.2*cm, 4.8*cm])
tear_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, GREY_LIGHT]),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(tear_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("Pathophysiology - The Vicious Cycle", h2_style))
story.append(Paragraph(
"The four core, inter-related mechanisms responsible for dry eye are:",
body_style))
story.append(Spacer(1, 0.1*cm))
path_items = [
("Tear Instability", "Reduced tear film break-up time; loss of surface mucins; meibomian gland dysfunction"),
("Tear Hyperosmolarity", "Core mechanism; damages epithelial cells directly and triggers inflammatory cascades; "
"threshold ~308 mOsm/L (normal); >316 mOsm/L = moderate-severe"),
("Ocular Surface Inflammation", "Present in 80% of KCS patients; both cause and consequence of dry eye; "
"involves T-cell activation, pro-inflammatory cytokines (IL-1, IL-6, TNF-α), MMP-9 upregulation"),
("Ocular Surface Damage", "Punctate epithelial erosions, mucous plaques, corneal filaments, goblet cell loss; "
"perpetuates the vicious cycle"),
]
for title, detail in path_items:
story.append(Paragraph(f'<b>• {title}:</b> {detail}', bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(info_box(
"Clinical Tip (Kanski's)",
["Whether a patient has primary aqueous deficiency or evaporative dry eye, "
"the end result is ocular surface inflammation and discomfort.",
"There is a poor correlation between symptoms and clinical tests; "
"reliability improves as severity increases."],
bg=ORANGE_SOFT, title_color=ORANGE))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2: CLASSIFICATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("2. CLASSIFICATION (DEWS / DEWS II)", MID_BLUE))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"The DEWS (2007) and updated DEWS II (2017) classification divides dry eye into two "
"primary etiological subtypes, recognizing that most patients present with "
"significant overlap between mechanisms.",
body_style))
story.append(Spacer(1, 0.25*cm))
story.append(two_col_table(
left_title="I. AQUEOUS-DEFICIENT DRY EYE (ADDE)",
left_items=[
"Sjögren Syndrome (SS) DED",
" Primary SS (isolated)",
" Secondary SS (RA, SLE, PSC)",
"",
"Non-Sjögren Syndrome DED:",
"Lacrimal deficiency - Primary:",
" Age-related dry eye",
" Congenital alacrima",
" Familial dysautonomia (Riley-Day)",
"Lacrimal deficiency - Secondary:",
" Inflammatory/neoplastic infiltration",
" AIDS, GVHD",
" Lacrimal gland/nerve ablation",
"Lacrimal duct obstruction:",
" Trachoma, cicatricial pemphigoid",
" Chemical burns, Stevens-Johnson",
"Reflex hyposecretion - Sensory:",
" Contact lens wear, diabetes",
" Refractive surgery, neurotrophic keratitis",
"Reflex hyposecretion - Motor:",
" CN VII palsy, systemic drugs",
],
right_title="II. EVAPORATIVE DRY EYE (EDE)",
right_items=[
"A. INTRINSIC CAUSES:",
"Meibomian Gland Dysfunction (MGD):",
" Posterior blepharitis, rosacea",
"Lid aperture disorders:",
" Scleral show, lid retraction",
" Proptosis, facial nerve palsy",
"Low blink rate:",
" Parkinson disease",
" VDU/computer/reading use",
"Drug-induced evaporation:",
" Antihistamines, beta-blockers",
" Antispasmodics, diuretics",
"",
"B. EXTRINSIC CAUSES:",
"Vitamin A deficiency",
"Topical drug toxicity / preservatives",
"Contact lens wear",
"Ocular surface disease (allergic conjunctivitis)",
],
left_bg=LIGHT_BLUE, right_bg=TEAL_LIGHT,
left_tc=MID_BLUE, right_tc=TEAL))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("DEWS II Severity Grading", h2_style))
story.append(Paragraph(
"DEWS/DEWS II grade severity from 1 to 4. The Ocular Surface Disease Index (OSDI) "
"questionnaire and clinical findings are used together for grading. "
"Treatment advances stepwise through these levels.",
body_style))
story.append(Spacer(1, 0.15*cm))
sev_data = [
["Grade", "Symptoms", "Conjunctival Staining", "Corneal Staining", "TBUT\n(sec)", "Schirmer\n(mm/5min)"],
["1\nMild", "Mild / episodic\nenvironmental triggers", "None–mild", "None", ">10", ">10"],
["2\nModerate", "Moderate / episodic\nor chronic", "None–mild", "None–mild\n(peripheral)", "<10", ">10 or\nborderline"],
["3\nSevere", "Frequent/chronic;\nactivity limitation", "Moderate–severe", "Moderate\n(central)", "<5", "<5"],
["4\nVery Severe", "Constant; disabling;\nwithout symptom relief", "Severe with\ncorneal features", "Severe\n(filaments)", "Immediate", "<2"],
]
sev_table = Table(sev_data, colWidths=[1.8*cm, 3.5*cm, 3.5*cm, 3*cm, 1.7*cm, 1.5*cm])
sev_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8.5),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8),
('BACKGROUND', (0,1), (-1,1), HexColor("#E8F5E9")),
('BACKGROUND', (0,2), (-1,2), HexColor("#FFF9C4")),
('BACKGROUND', (0,3), (-1,3), HexColor("#FFE0B2")),
('BACKGROUND', (0,4), (-1,4), HexColor("#FFCDD2")),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
]))
story.append(sev_table)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3: EVALUATION / WORKUP
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("3. EVALUATION AND DIAGNOSIS", TEAL))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"A systematic approach to evaluation integrates patient symptoms, "
"clinical signs, and objective tests. DEWS II recommends a "
"triaging approach: screening questionnaires → diagnostic tests "
"performed in a sequence that minimises confounding.",
body_style))
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.1 Symptoms and History", h2_style))
story.append(Paragraph(
"Characteristic symptoms include burning, dryness, grittiness, foreign body sensation, "
"mildly decreased vision, and paradoxical excess tearing (reflex). Symptoms "
"worsen with environmental triggers (wind, air-conditioning, central heating, smoke, "
"low humidity) and prolonged visual tasks that reduce blink rate "
"(reading, computer use, watching TV).",
body_style))
story.append(Spacer(1, 0.1*cm))
story.append(info_box(
"Validated Questionnaires",
[
"OSDI (Ocular Surface Disease Index) - 12 items; score 0-100; "
">12 = mild DED, >23 = moderate, >32 = severe",
"SPEED (Standard Patient Evaluation of Eye Dryness)",
"DEQ-5 (Dry Eye Questionnaire 5)",
"Symptom Assessment in Dry Eye (SANDE)",
],
bg=LIGHT_BLUE, title_color=MID_BLUE))
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.2 Clinical Examination - Recommended Test Sequence", h2_style))
story.append(Paragraph(
"Tests should be performed in the following order to minimise confounding "
"(Schirmer strips can damage the ocular surface and affect staining results):",
body_style))
story.append(Spacer(1, 0.15*cm))
# Workup sequence table
workup_data = [
["Step", "Test", "Method / Interpretation", "Normal / Cutoff"],
["1", "Tear Meniscus Height\n(TMH)", "Slit lamp: height at inferior lid margin;\nassess before any drops",
"≥ 0.2 mm (DEWS II)\n≥ 0.5 mm (Wills Eye)"],
["2", "Tear Film Break-up\nTime (TBUT / NIKBUT)",
"Fluorescein 2% / impregnated strip + non-preserved saline;\nbroad cobalt blue beam; time from last blink to first dry spot\n"
"(Non-invasive TBUT preferred in DEWS II)",
"< 10 sec = suspicious\n< 5 sec = severe\n(NIKBUT < 10 sec = abnormal)"],
["3", "Ocular Surface\nStaining",
"Fluorescein: corneal/conjunctival epithelial damage\n"
"Lissamine green: devitalized cells (preferred; less irritation)\n"
"Rose Bengal: dead/devitalized cells (intense staining; stings)",
"Oxford / van Bijsterveld grading;\n"
"Interpalpebral pattern = ADDE;\nInferior = blepharitis/exposure"],
["4", "Tear Osmolarity",
"TearLab™ or similar; collected from tear meniscus;\ninter-eye difference > 8 mOsm/L also significant",
"Normal ≤ 308 mOsm/L\n308-316 = mild-moderate\n> 316 = moderate-severe"],
["5", "MMP-9\n(InflammaDry)",
"Point-of-care test; elevated MMP-9 indicates\nocular surface inflammation",
"≥ 40 ng/mL = positive;\nsensitivity 85%, specificity 94%"],
["6", "Schirmer Test",
"No. 41 Whatman paper (5 mm × 35 mm); lower lid junction of\nmiddle & lateral 1/3; 5 min; eyes open with blinking\n"
"Schirmer I (without anaesthetic): basal + reflex\n"
"Schirmer II (with proparacaine): basal secretion only",
"Schirmer I: ≥ 15 mm/5 min\nSchirmer II: ≥ 6 mm/5 min\n(< 5 mm = severely abnormal)"],
["7", "Meibomian Gland\nEvaluation",
"Meibography (IR transillumination): gland dropout grading\nMeibum expression: quality (clear → turbid → toothpaste)\nLid margin: vascular engorgement, notching, plugging",
"Meiboscore 0-3 per lid\n(3 = >67% dropout)"],
["8", "Lid Wiper\nEpitheliopathy",
"Lissamine green staining of upper tarsal conjunctival\nwiper zone; friction marker",
"Any staining ≥ 2 mm wide\nor ≥ 25% circumference = positive"],
]
workup_table = Table(workup_data, colWidths=[0.8*cm, 3.0*cm, 7.0*cm, 4.2*cm])
workup_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('ALIGN', (1,0), (-1,0), 'CENTER'),
('ALIGN', (1,1), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, TEAL_LIGHT]),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
('FONTNAME', (0,1), (0,-1), 'Helvetica-Bold'),
('TEXTCOLOR', (0,1), (0,-1), TEAL),
]))
story.append(workup_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.3 Additional / Ancillary Investigations", h2_style))
story.append(Paragraph(
"The following are used in selected cases or research settings:",
body_style))
additional_tests = [
"Phenol red thread test: pH-sensitive thread; 15 sec; < 6 mm = abnormal (faster than Schirmer)",
"Tear lactoferrin: Low levels suggest ADDE (ELISA or Lactoferrin InflammaDry)",
"Fluorescein clearance test / Tear function index: Delayed clearance in all dry eye states",
"Impression cytology: Goblet cell density; squamous metaplasia grading",
"In-vivo confocal microscopy (IVCM): Corneal nerve density; sub-basal nerve plexus; Langerhans cells",
"Anterior segment OCT: Quantitative tear meniscus height/volume",
"Systemic workup for Sjögren syndrome: Anti-SSA/SSB antibodies, ANA, RF, minor salivary gland biopsy",
]
for t in additional_tests:
story.append(Paragraph(f"• {t}", bullet_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4: STEPWISE MANAGEMENT
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("4. STEPWISE MANAGEMENT (DEWS II FRAMEWORK)", GREEN))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"The DEWS and DEWS II guidelines provide a severity-based, stepwise treatment algorithm. "
"Management proceeds to the next level if preceding measures are inadequate. "
"The underlying processes are generally not reversible; management aims to "
"control symptoms and prevent surface damage.",
body_style))
story.append(Spacer(1, 0.15*cm))
level_colors = {
1: (GREEN_LIGHT, GREEN, GREEN),
2: (LIGHT_BLUE, MID_BLUE, MID_BLUE),
3: (ORANGE_SOFT, ORANGE, ORANGE),
4: (RED_LIGHT, RED, RED),
}
# Level 1
story.append(level_box(1, "EDUCATION, ENVIRONMENT & FIRST-LINE THERAPIES\n(All Patients - Grade 1)", [
"Patient education: realistic expectations, importance of compliance",
"Blink exercises: conscious blinking during reading, VDT use, TV viewing",
"Screen positioning: below eye level to reduce palpebral aperture size",
"Lifestyle modification: humidifiers, avoid fans/AC drafts, protect from wind",
"Systemic medication review: identify and eliminate contributory drugs",
"Discontinue toxic/preserved topical medications where possible",
"Artificial tear substitutes (preserved drops, gels, ointments at HS)",
"Basic eyelid therapy: warm compresses + lid hygiene for blepharitis/MGD",
"Reparative lid surgery if needed: entropion, ectropion, lagophthalmos",
"Caution: advise against laser refractive surgery in DED patients",
"Contact lens: replace or modify wearing schedule; switch to daily disposables",
"Dietary: Omega-3 supplementation (fish oil 1-3g/day)",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 2
story.append(level_box(2, "ANTI-INFLAMMATORY AND SUPPLEMENTAL THERAPIES\n(Grades 2-3)", [
"Non-preserved artificial tears (up to q1-2h PRN)",
"Lubricating gel or ointment q.h.s.",
"CYCLOSPORINE A 0.05% or 0.09% (Restasis / Cequa) b.i.d.:",
" Indicated for chronic DED with decreased tears secondary to ocular inflammation",
" Burns on instillation for first weeks; onset 1-3 months",
" Consider concurrent mild topical steroid (loteprednol 0.5% or FML 0.1%) for 1 month",
"LIFITEGRAST 5% (Xiidra) b.i.d.: LFA-1 integrin antagonist; anti-inflammatory",
" Symptomatic improvement within 2 weeks; full effect up to 3 months",
" May cause instillation burn, blurred vision, metallic taste",
"Topical steroids (short-course): FML 0.1%, loteprednol 0.5%, prednisolone 1%",
" For acute exacerbations only; monitor IOP; avoid prolonged use",
"Tetracyclines (doxycycline 50-100 mg/day): for MGD, rosacea-associated DED",
" Anti-MMP and anti-inflammatory properties; minimum 6-8 weeks",
"Secretagogues: pilocarpine 5 mg QID, cevimeline 30 mg TID (for Sjögren)",
"Rebamipide 2% QID: mucin secretagogue; improves goblet cell density",
"Punctal plugs (collagen temporary or silicone/acrylic reversible):",
" Occludes lower punctum first; reduces tear drainage",
" Treat active inflammation/blepharitis BEFORE plugging",
"Moisture chamber spectacles / side shields",
"Oral omega-3 fatty acids (if not already started at Level 1)",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 3
story.append(level_box(3, "ADVANCED THERAPIES FOR REFRACTORY DISEASE\n(Grade 3 - Severe)", [
"AUTOLOGOUS SERUM EYE DROPS (20-100% concentration):",
" Rich in growth factors (EGF, TGF-β), vitamins, fibronectin",
" Promotes epithelial healing; use q.i.d. to q2h",
" Requires refrigeration; short shelf life (3-6 months frozen)",
"UMBILICAL CORD SERUM: similar to autologous; higher growth factor content",
"SCLERAL CONTACT LENSES: fluid reservoir over cornea; excellent for severe/irregular surface",
"Bandage soft contact lens: for erosions, filamentary keratitis",
"Permanent punctal occlusion:",
" Thermal/argon laser cautery if plugs repeatedly fall out",
" Occlude both upper and lower puncta if needed",
"MANAGEMENT OF COMPLICATIONS:",
"Filamentary keratitis: remove filaments with forceps + acetylcysteine 10% q.i.d. (mucolytic)",
"Corneal erosions: bandage lens, preservative-free lubricants, vitamin A drops",
"Topical vitamin A (retinol palmitate): promotes epithelial and goblet cell recovery",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 4
story.append(level_box(4, "SYSTEMIC AND SURGICAL INTERVENTIONS\n(Grade 4 - Very Severe / Refractory)", [
"Systemic anti-inflammatory agents (for Sjögren, cicatricial diseases):",
" Hydroxychloroquine, azathioprine, mycophenolate mofetil, rituximab",
"TARSORRHAPHY:",
" Temporary adhesive tape tarsorrhaphy (lateral 1/3 of lids, pending surgery)",
" Permanent lateral tarsorrhaphy for refractory exposure and evaporative DED",
"SALIVARY GLAND TRANSPLANTATION: labial salivary gland grafts to conjunctiva",
"AMNIOTIC MEMBRANE TRANSPLANTATION: for persistent epithelial defects",
"CONJUNCTIVAL/LIMBAL TRANSPLANTATION: for severe cicatricial disease",
"MUCOUS MEMBRANE GRAFTING: in severe cicatricial pemphigoid, Stevens-Johnson",
"Botulinum toxin injection for punctal closure (experimental)",
"Meibomian gland probing/intraductal probing for refractory MGD",
"Intense pulsed light (IPL): for posterior blepharitis/MGD-associated EDE",
"LipiFlow thermal pulsation: meibomian gland expression/therapy",
], level_colors))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("Summary: Wills Eye Manual Severity-Based Quick Guide", h2_style))
quick_data = [
["Severity", "Key Treatments"],
["MILD\n(Grade 1)", "Artificial tears q.i.d. (preserved OK)\nEnvironmental/lifestyle modification\nLid hygiene if blepharitis present"],
["MODERATE\n(Grade 2)", "Preservative-free artificial tears up to q1-2h\nLubricating gel/ointment q.h.s.\nCyclosporine 0.05% or 0.09% b.i.d.\nLifitegrast 5% b.i.d.\nLifestyle modification (humidifiers, smoking cessation)\nPunctal occlusion if above measures inadequate"],
["SEVERE\n(Grade 3-4)", "Cyclosporine or lifitegrast (continued)\nPunctal occlusion (lower + upper puncta; thermal cautery if plugs fail)\nLubricating gel b.i.d.-q.i.d. p.r.n.\nMoisture chamber goggles\nRemove filaments; acetylcysteine 10% q.i.d. if filamentary keratitis\nOral omega-3, autologous serum, topical vitamin A\nBandage soft lens or scleral lens\nLateral tarsorrhaphy (temporary or permanent) if all else fails"],
]
quick_table = Table(quick_data, colWidths=[3.2*cm, 12.8*cm])
quick_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREEN),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9.5),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('ALIGN', (1,1), (1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('BACKGROUND', (0,1), (-1,1), GREEN_LIGHT),
('BACKGROUND', (0,2), (-1,2), HexColor("#FFF9C4")),
('BACKGROUND', (0,3), (-1,3), HexColor("#FFCDD2")),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [6, 5, 6, 5]),
('FONTNAME', (0,1), (0,-1), 'Helvetica-Bold'),
]))
story.append(quick_table)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5: SPECIAL SITUATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("5. SPECIAL SITUATIONS AND MANAGEMENT PEARLS", HexColor("#6A1B9A")))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("5.1 Sjögren Syndrome DED", h2_style))
story.append(Paragraph(
"SS DED is typically severe ADDE. ACR criteria require: "
"positive anti-SSA/SSB (or RF + ANA), ocular surface staining above threshold grade, "
"and focal lymphocytic sialadenitis on minor salivary gland biopsy.",
body_style))
ss_mgmt = [
"All above topical therapies at appropriate severity level",
"Cyclosporine 0.05% preferred given strong inflammatory component",
"Punctal occlusion often required early",
"Secretagogues: pilocarpine 5 mg QID or cevimeline 30 mg TID for systemic dryness",
"Hydroxychloroquine for systemic manifestations",
"Rheumatology referral essential; treat extraglandular manifestations",
"Regular monitoring for lymphoma (increased risk in primary SS)",
]
for i in ss_mgmt:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("5.2 MGD-Predominant / Evaporative DED", h2_style))
mgd_mgmt = [
"Warm compresses twice daily (45°C for 5-10 min) to liquefy meibum",
"Lid massage and hygiene (lid scrubs, diluted baby shampoo, or commercial wipes)",
"Omega-3 fatty acids (fish oil 1-3 g/day or flaxseed oil)",
"Oral doxycycline 50-100 mg/day (anti-MMP properties; minimum 6-8 weeks)",
"Azithromycin topical 1% (alternative to systemic tetracyclines)",
"Intense Pulsed Light (IPL) therapy for rosacea-associated MGD",
"LipiFlow vectored thermal pulsation device for meibomian gland obstruction",
"Meibomian gland intraductal probing for scarring/obstruction",
]
for i in mgd_mgmt:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("5.3 Post-Refractive Surgery / Post-LASIK DED", h2_style))
story.append(Paragraph(
"Caused by severing of corneal nerves during flap creation, reducing afferent reflex "
"arc. SMILE has lower incidence than LASIK. Usually resolves within 6-12 months "
"but may persist.",
body_style))
post_surg = [
"Frequent preservative-free artificial tears from day of surgery",
"Cyclosporine 0.05% b.i.d. starting 1 month pre-operatively in at-risk patients",
"Avoid LASIK in patients with pre-existing dry eye (relative contraindication)",
"Punctal occlusion if symptoms persist beyond 3-6 months",
"Autologous serum for recalcitrant post-LASIK neurotrophic epitheliopathy",
]
for i in post_surg:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("5.4 Filamentary Keratitis", h2_style))
story.append(Paragraph(
"Mucous strands anchored to corneal epithelium; common in severe aqueous deficiency.",
body_style))
fk = [
"Remove filaments with jeweller's forceps at slit lamp",
"Acetylcysteine 10% (mucolytic) q.i.d. eye drops",
"Bandage soft contact lens",
"Aggressive lubrication with preservative-free drops and ointment",
"Address underlying cause (DED, prolonged eye closure, post-surgical)",
]
for i in fk:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.3*cm))
story.append(info_box(
"Drug Classes That Can Cause or Worsen Dry Eye",
[
"Anticholinergics, antihistamines (H1 and H2 blockers)",
"Beta-blockers (systemic and topical)",
"Diuretics (reduces aqueous secretion)",
"Antidepressants: TCAs, SSRIs",
"Antipsychotics, antispasmodics",
"Oral contraceptives, retinoids",
"Isotretinoin (Accutane): reduces meibomian gland size and function",
"Chemotherapy agents",
"Topical preserved eye drops (BAC/BAK preservative)",
],
bg=RED_LIGHT, title_color=RED))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6: FOLLOW-UP AND MONITORING
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("6. FOLLOW-UP, MONITORING AND PATIENT EDUCATION", GREY_DARK))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("Follow-up Schedule", h2_style))
fu_data = [
["Severity", "Initial Review", "Subsequent Follow-up"],
["Mild (Grade 1)", "4-6 weeks", "Every 3-6 months; earlier if worsening"],
["Moderate (Grade 2)", "2-4 weeks after starting Csa/Lifitegrast", "Every 2-3 months; titrate treatment"],
["Severe (Grade 3-4)", "1-2 weeks initially; after procedures", "Monthly until stable; 3-monthly long-term"],
["Post-Refractive Surgery", "1 week, 1 month, 3 months, 6 months", "As clinically indicated"],
]
fu_table = Table(fu_data, colWidths=[4*cm, 4*cm, 7*cm])
fu_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREY_DARK),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('ALIGN', (0,0), (-1,0), 'CENTER'),
('ALIGN', (0,1), (0,-1), 'CENTER'),
('ALIGN', (1,1), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, GREY_LIGHT]),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [6, 4, 6, 4]),
]))
story.append(fu_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("Treatment Response Assessment", h2_style))
tr_items = [
"OSDI or SPEED score reduction (aim for >25% improvement)",
"TBUT improvement (target > 10 sec non-invasive)",
"Reduction in corneal/conjunctival staining grade",
"Tear osmolarity normalization (< 308 mOsm/L or inter-eye difference < 8 mOsm/L)",
"MMP-9 negativity on InflammaDry",
"Schirmer improvement (in ADDE)",
"Patient-reported improvement in daily activities and QoL",
]
for i in tr_items:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Key Patient Education Points", h2_style))
edu_items = [
"DED is a chronic condition requiring long-term management; not a 'quick fix'",
"Compliance with regular instillation is essential even when asymptomatic",
"Artificial tears wash away natural tears; use preservative-free if > 4 times/day",
"Cyclosporine and lifitegrast require weeks-to-months to show full benefit",
"Blink consciously during prolonged screen/reading time; take 20-20-20 breaks",
"Avoid rubbing eyes (exacerbates inflammation and surface damage)",
"Refrigerating preservative-free unit doses does not extend shelf life beyond 24h once opened",
"Lubricating ointment use at bedtime for 3-6 months after epithelial healing reduces recurrence",
]
for i in edu_items:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 7: REFERENCES
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("7. REFERENCES", GREY_DARK))
story.append(Spacer(1, 0.35*cm))
refs = [
("1", "TFOS DEWS II Executive Summary.",
"Craig JP, Nelson JD, Azar DT, et al.",
"Ocul Surf. 2017;15(4):802-812. DOI: 10.1016/j.jtos.2017.08.003"),
("2", "TFOS DEWS II Definition and Classification Report.",
"Stapleton F, Alves M, Bunya VY, et al.",
"Ocul Surf. 2017;15(3):276-283."),
("3", "TFOS DEWS II Pathophysiology Report.",
"Bron AJ, de Paiva CS, Chauhan SK, et al.",
"Ocul Surf. 2017;15(3):438-510."),
("4", "TFOS DEWS II Diagnostic Methodology Report.",
"Wolffsohn JS, Arita R, Chalmers R, et al.",
"Ocul Surf. 2017;15(3):539-574."),
("5", "TFOS DEWS II Management and Therapy Report.",
"Jones L, Downie LE, Korb D, et al.",
"Ocul Surf. 2017;15(3):575-628."),
("6", "Kanski's Clinical Ophthalmology: A Systematic Approach, 10th Edition.",
"Bowling B.",
"Elsevier, 2024. Chapter 5: Corneal and External Eye Disease."),
("7", "The Wills Eye Manual: Office and Emergency Room Diagnosis and Treatment, 8th Edition.",
"Wills Eye Hospital.",
"Wolters Kluwer, 2022. Section 4.3: Dry Eye Syndrome, pp. 171-176."),
("8", "Goodman & Gilman's The Pharmacological Basis of Therapeutics, 14th Edition.",
"Brunton LL, et al. (Eds.).",
"McGraw-Hill, 2023. Chapter on Ophthalmic Pharmacology."),
("9", "Association between potential factors and dry eye disease: A systematic review and meta-analysis.",
"Yang K, Wu S, Ke L.",
"Medicine (Baltimore). 2024 Dec 27. PMID: 39969375."),
]
ref_data = [["No.", "Title", "Authors", "Source"]]
for no, title, authors, source in refs:
ref_data.append([no, title, authors, source])
ref_table = Table(ref_data, colWidths=[0.6*cm, 5.5*cm, 4.5*cm, 4.4*cm])
ref_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREY_DARK),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8.5),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 7.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, GREY_LIGHT]),
('GRID', (0,0), (-1,-1), 0.3, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [4, 3, 4, 3]),
]))
story.append(ref_table)
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=1, color=GREY_MID))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"This document is a clinical reference for educational purposes. "
"All management decisions should be individualized based on clinical assessment. "
"For the full TFOS DEWS II reports, visit: www.tearfilm.org",
ref_style))
# ─── BUILD ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF generated: {OUTPUT}")
cd /tmp/workspace/dry-eye-pdf && python generate_dry_eye_pdf.py 2>&1
ls -lh /tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf
python3 -c "from pypdf import PdfReader; r = PdfReader('/tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf'); print(f'Pages: {len(r.pages)}')"
Dry Eye Disease Clinical Reference
PDF Document · PDF
Make the pdf on white background
~/dry-eye-pdf/generate_dry_eye_white.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, white, black
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
from reportlab.lib import colors
# ─── COLOR PALETTE (white background, accent colors only for borders/text) ───
DARK_BLUE = HexColor("#0D3B66")
MID_BLUE = HexColor("#1565C0")
TEAL = HexColor("#00838F")
ORANGE = HexColor("#E65100")
GREY_DARK = HexColor("#263238")
GREY_MID = HexColor("#607D8B")
GREY_LIGHT = HexColor("#F5F5F5")
GREEN = HexColor("#2E7D32")
RED = HexColor("#C62828")
PURPLE = HexColor("#6A1B9A")
# Very light tints (nearly white) - used sparingly for table row alternation only
ROW_ALT = HexColor("#FAFAFA")
PAGE_W, PAGE_H = A4
# ─── STYLES ──────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent='Normal', **kwargs):
return ParagraphStyle(name, parent=styles[parent], **kwargs)
# Cover styles (dark text on white cover)
cover_title_style = make_style('CoverTitle',
fontSize=30, leading=36, textColor=DARK_BLUE,
fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=6)
cover_sub_style = make_style('CoverSub',
fontSize=14, leading=20, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_CENTER, spaceAfter=4)
cover_tagline_style = make_style('CoverTagline',
fontSize=11, leading=16, textColor=GREY_MID,
fontName='Helvetica-Oblique', alignment=TA_CENTER, spaceAfter=2)
cover_date_style = make_style('CoverDate',
fontSize=10, leading=14, textColor=GREY_MID,
fontName='Helvetica', alignment=TA_CENTER)
# Section heading style (white text on colored banner — small left-bar variant)
h1_inner_style = make_style('H1Inner',
fontSize=14, leading=19, textColor=white,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=0, spaceAfter=0)
h2_style = make_style('H2',
fontSize=12, leading=17, textColor=DARK_BLUE,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=10, spaceAfter=3)
h3_style = make_style('H3',
fontSize=10.5, leading=15, textColor=TEAL,
fontName='Helvetica-Bold', alignment=TA_LEFT,
spaceBefore=6, spaceAfter=2)
body_style = make_style('BodyText2',
fontSize=9.5, leading=14, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_JUSTIFY,
spaceBefore=2, spaceAfter=2)
bullet_style = make_style('BulletText',
fontSize=9.5, leading=14, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_LEFT,
leftIndent=14, firstLineIndent=-10,
spaceBefore=1, spaceAfter=1)
sub_bullet_style = make_style('SubBullet',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_LEFT,
leftIndent=28, firstLineIndent=-10,
spaceBefore=1, spaceAfter=1)
tip_style = make_style('TipText',
fontSize=9.5, leading=14, textColor=GREY_DARK,
fontName='Helvetica-Oblique', alignment=TA_LEFT,
leftIndent=12, rightIndent=8,
spaceBefore=4, spaceAfter=4)
ref_style = make_style('RefText',
fontSize=8, leading=11, textColor=GREY_MID,
fontName='Helvetica-Oblique', alignment=TA_LEFT,
spaceBefore=1, spaceAfter=1)
# ─── HELPERS ─────────────────────────────────────────────────────────────────
def section_header(text, color=DARK_BLUE):
"""Colored banner (same as before but text on solid color strip)."""
data = [[Paragraph(text, h1_inner_style)]]
t = Table(data, colWidths=[PAGE_W - 4*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('TOPPADDING', (0,0), (-1,-1), 7),
('BOTTOMPADDING', (0,0), (-1,-1), 7),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
]))
return t
def info_box(title, items, border_color=MID_BLUE, bullet='•'):
"""White box with colored left border and colored title — NO fill."""
title_p = Paragraph(f'<b>{title}</b>', make_style(f'IB_{title[:8]}',
fontSize=10, leading=14, textColor=border_color,
fontName='Helvetica-Bold', spaceBefore=0, spaceAfter=4))
content = [title_p]
for item in items:
content.append(Paragraph(f'{bullet} {item}', make_style(f'IBB_{title[:6]}',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, firstLineIndent=0,
spaceBefore=1, spaceAfter=1)))
data = [[content]]
col_w = PAGE_W - 4*cm
t = Table(data, colWidths=[col_w])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), white),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 12),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('BOX', (0,0), (-1,-1), 0.75, GREY_MID),
('LINEBEFORE', (0,0), (0,-1), 4, border_color),
]))
return t
def two_col_table(left_items, right_items, left_title, right_title,
left_tc=MID_BLUE, right_tc=TEAL):
def make_cell(title, items, tc):
cell = [Paragraph(f'<b>{title}</b>', make_style(f'CT_{title[:6]}',
fontSize=10, leading=14, textColor=tc,
fontName='Helvetica-Bold', spaceBefore=0, spaceAfter=5))]
for i in items:
if i == "":
cell.append(Spacer(1, 4))
else:
prefix = ' ' if i.startswith(' ') else '• '
cell.append(Paragraph(f'{prefix}{i.strip()}', make_style(f'CB_{title[:4]}',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, spaceBefore=1, spaceAfter=1)))
return cell
data = [[make_cell(left_title, left_items, left_tc),
make_cell(right_title, right_items, right_tc)]]
col_w = (PAGE_W - 4*cm - 0.5*cm) / 2
t = Table(data, colWidths=[col_w, col_w], hAlign='LEFT')
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), white),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('BOX', (0,0), (0,-1), 1, left_tc),
('BOX', (1,0), (1,-1), 1, right_tc),
('LINEBEFORE', (0,0), (0,-1), 4, left_tc),
('LINEBEFORE', (1,0), (1,-1), 4, right_tc),
]))
return t
def level_box(level_num, title, items, color_map):
color = color_map.get(level_num, GREY_MID)
label = Paragraph(f'<b>LEVEL {level_num}</b>', make_style(f'LLbl{level_num}',
fontSize=18, leading=22, textColor=color,
fontName='Helvetica-Bold', alignment=TA_CENTER))
sub = Paragraph(title, make_style(f'LSub{level_num}',
fontSize=8.5, leading=12, textColor=GREY_DARK,
fontName='Helvetica-Bold', alignment=TA_CENTER))
bullets = []
for i in items:
if i.startswith(' '):
bullets.append(Paragraph(f' {i.strip()}', make_style(f'LSB{level_num}',
fontSize=8.5, leading=12, textColor=GREY_MID,
fontName='Helvetica', leftIndent=16, spaceBefore=0, spaceAfter=1)))
else:
bullets.append(Paragraph(f'• {i}', make_style(f'LB{level_num}',
fontSize=9, leading=13, textColor=GREY_DARK,
fontName='Helvetica', leftIndent=8, spaceBefore=1, spaceAfter=1)))
left_cell = [label, Spacer(1, 6), sub]
right_cell = bullets
col_w_l = 3.2*cm
col_w_r = (PAGE_W - 4*cm) - col_w_l - 0.3*cm
data = [[left_cell, right_cell]]
t = Table(data, colWidths=[col_w_l, col_w_r])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), white),
('VALIGN', (0,0), (0,-1), 'MIDDLE'),
('VALIGN', (1,0), (1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (0,-1), 6),
('LEFTPADDING', (1,0), (1,-1), 12),
('BOX', (0,0), (-1,-1), 1.5, color),
('LINEAFTER', (0,0), (0,-1), 1, color),
('LINEBEFORE', (0,0), (0,-1), 4, color),
]))
return t
# ─── PAGE DECORATIONS ─────────────────────────────────────────────────────────
def add_page_decorations(canvas, doc):
canvas.saveState()
# Thin top rule
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, PAGE_H - 5*mm, PAGE_W, 5*mm, fill=1, stroke=0)
# Bottom thin rule
canvas.setFillColor(GREY_LIGHT)
canvas.rect(0, 0, PAGE_W, 1.2*cm, fill=1, stroke=0)
canvas.setStrokeColor(GREY_MID)
canvas.setLineWidth(0.5)
canvas.line(0, 1.2*cm, PAGE_W, 1.2*cm)
canvas.setFillColor(GREY_MID)
canvas.setFont('Helvetica', 7.5)
canvas.drawCentredString(PAGE_W/2, 0.45*cm,
f'Dry Eye Disease — Clinical Reference | TFOS DEWS II Framework | Page {doc.page}')
canvas.restoreState()
def build_cover(canvas, doc):
"""White cover with a dark blue top banner and blue bottom stripe."""
canvas.saveState()
# Full white background
canvas.setFillColor(white)
canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# Top banner (about 1/3 of page)
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, PAGE_H * 0.68, PAGE_W, PAGE_H * 0.32, fill=1, stroke=0)
# Thin accent line below banner
canvas.setFillColor(TEAL)
canvas.rect(0, PAGE_H * 0.67, PAGE_W, PAGE_H * 0.012, fill=1, stroke=0)
# Bottom thin stripe
canvas.setFillColor(GREY_LIGHT)
canvas.rect(0, 0, PAGE_W, 1.5*cm, fill=1, stroke=0)
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, 1.4*cm, PAGE_W, 2*mm, fill=1, stroke=0)
canvas.restoreState()
# ─── COVER TEXT STYLES (used directly on canvas for cover) ──────────────────
cover_banner_title = make_style('CBT',
fontSize=30, leading=36, textColor=white,
fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=6)
cover_banner_sub = make_style('CBS',
fontSize=13, leading=18, textColor=HexColor("#B3E5FC"),
fontName='Helvetica', alignment=TA_CENTER, spaceAfter=4)
cover_body_title = make_style('CBTB',
fontSize=18, leading=24, textColor=DARK_BLUE,
fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=6)
cover_body_sub = make_style('CBSB',
fontSize=11, leading=16, textColor=GREY_DARK,
fontName='Helvetica', alignment=TA_CENTER, spaceAfter=4)
cover_ref_style = make_style('CBREF',
fontSize=9, leading=13, textColor=GREY_MID,
fontName='Helvetica-Oblique', alignment=TA_CENTER, spaceAfter=2)
# ─── BUILD THE PDF ────────────────────────────────────────────────────────────
OUTPUT = '/tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf'
doc = BaseDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=1.5*cm, bottomMargin=1.8*cm,
)
cover_frame = Frame(2*cm, 1.8*cm, PAGE_W - 4*cm, PAGE_H - 3.3*cm, id='cover')
content_frame = Frame(2*cm, 1.8*cm, PAGE_W - 4*cm, PAGE_H - 3.3*cm, id='content')
cover_template = PageTemplate(id='Cover', frames=[cover_frame], onPage=build_cover)
main_template = PageTemplate(id='Main', frames=[content_frame], onPage=add_page_decorations)
doc.addPageTemplates([cover_template, main_template])
story = []
# ══════════════════════════════════════════════════════════════════════════════
# COVER PAGE
# ══════════════════════════════════════════════════════════════════════════════
# Content that sits inside the blue top banner
story.append(Spacer(1, 3.2*cm)) # push into banner zone
story.append(Paragraph("DRY EYE DISEASE", cover_banner_title))
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph("A Comprehensive Clinical Reference", cover_banner_sub))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"Definition · Classification · Evaluation · Stepwise Management",
cover_banner_sub))
# Content in white zone
story.append(Spacer(1, 2.2*cm))
story.append(HRFlowable(width="60%", thickness=2, color=TEAL, hAlign='CENTER'))
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph(
"Based on the TFOS DEWS II International Dry Eye Workshop (2017) Guidelines",
cover_body_title))
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph(
"Tear Film & Ocular Surface Society Dry Eye Workshop II",
cover_body_sub))
story.append(Spacer(1, 0.3*cm))
story.append(HRFlowable(width="40%", thickness=0.75, color=GREY_MID, hAlign='CENTER'))
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph(
"References: Kanski's Clinical Ophthalmology (10th Ed.) · "
"Wills Eye Manual (8th Ed.) · TFOS DEWS II Reports (Ocul Surf. 2017)",
cover_ref_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("July 2026", cover_ref_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1: DEFINITION & OVERVIEW
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("1. DEFINITION AND OVERVIEW", DARK_BLUE))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("TFOS DEWS II Definition (2017)", h2_style))
story.append(Paragraph(
"Dry eye is a <b>multifactorial disease of the ocular surface</b> characterised by a loss of "
"homeostasis of the tear film, and accompanied by ocular symptoms, in which "
"<b>tear film instability and hyperosmolarity, ocular surface inflammation and damage, "
"and neurosensory abnormalities</b> play etiological roles.",
body_style))
story.append(Spacer(1, 0.2*cm))
story.append(info_box(
"Key Conceptual Pillars of DED (DEWS II)",
[
"Loss of tear film HOMEOSTASIS is the central unifying concept",
"Ocular symptoms must be present (distinguishes from asymptomatic tear dysfunction)",
"Tear film instability → hyperosmolarity → inflammation → ocular surface damage",
"Neurosensory abnormalities recognized as etiological contributors",
"Positive feedback ('vicious cycle') perpetuates and amplifies the disease",
],
border_color=DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("Tear Film Structure and Function", h2_style))
story.append(Paragraph(
"The tear film is a trilaminar fluid layer covering the ocular surface:",
body_style))
story.append(Spacer(1, 0.15*cm))
tear_data = [
["Layer", "Source", "Composition", "Function"],
["Lipid (Outer)", "Meibomian glands", "Phospholipids, triglycerides,\ncholesterol esters, wax esters",
"Retards evaporation; stabilizes\ntear film; smooths optical surface"],
["Aqueous (Middle)", "Main & accessory\nlacrimal glands", "Water, electrolytes, proteins\n(lactoferrin, lysozyme, IgA)",
"Nourishes cornea; dilutes irritants;\nantimicrobial; oxygen supply"],
["Mucin (Inner)", "Goblet cells;\ncorneal epithelium",
"Transmembrane mucins\n(MUC1, 4, 16); secreted MUC5AC",
"Hydrophilic conversion of surface;\nfacilitates wetting & lubrication"],
]
tear_table = Table(tear_data, colWidths=[2.8*cm, 3.2*cm, 4.5*cm, 4.5*cm])
tear_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(tear_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("Pathophysiology — The Vicious Cycle", h2_style))
story.append(Paragraph("The four core inter-related mechanisms:", body_style))
story.append(Spacer(1, 0.1*cm))
path_items = [
("Tear Instability", "Reduced TBUT; loss of surface mucins; meibomian gland dysfunction"),
("Tear Hyperosmolarity", "Core mechanism; damages epithelial cells directly; triggers inflammatory "
"cascades; threshold ~308 mOsm/L; >316 mOsm/L = moderate-severe"),
("Ocular Surface Inflammation", "Present in 80% of KCS; both cause and consequence; "
"T-cell activation, IL-1, IL-6, TNF-α, MMP-9 upregulation"),
("Ocular Surface Damage", "Punctate epithelial erosions, mucous plaques, corneal filaments, "
"goblet cell loss; perpetuates the vicious cycle"),
]
for title, detail in path_items:
story.append(Paragraph(f'<b>• {title}:</b> {detail}', bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(info_box(
"Clinical Tips (Kanski's)",
["Whether a patient has primary aqueous deficiency or evaporative dry eye, "
"the end result is ocular surface inflammation and discomfort.",
"There is a poor correlation between symptoms and clinical tests; "
"reliability improves as severity increases."],
border_color=ORANGE))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2: CLASSIFICATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("2. CLASSIFICATION (DEWS / DEWS II)", MID_BLUE))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"The DEWS (2007) and DEWS II (2017) classification divides dry eye into two primary "
"etiological subtypes. Most patients present with significant overlap between mechanisms.",
body_style))
story.append(Spacer(1, 0.25*cm))
story.append(two_col_table(
left_title="I. AQUEOUS-DEFICIENT DRY EYE (ADDE)",
left_items=[
"Sjögren Syndrome (SS) DED",
" Primary SS (isolated)",
" Secondary SS (RA, SLE, PSC)",
"",
"Non-Sjögren Syndrome DED:",
"Lacrimal deficiency — Primary:",
" Age-related dry eye",
" Congenital alacrima",
" Familial dysautonomia (Riley-Day)",
"Lacrimal deficiency — Secondary:",
" Inflammatory/neoplastic infiltration",
" AIDS, Graft-versus-host disease",
" Lacrimal gland/nerve ablation",
"Lacrimal duct obstruction:",
" Trachoma, cicatricial pemphigoid",
" Chemical burns, Stevens-Johnson",
"Reflex hyposecretion — Sensory:",
" Contact lens wear, diabetes",
" Refractive surgery, neurotrophic keratitis",
"Reflex hyposecretion — Motor:",
" CN VII palsy, systemic drugs",
],
right_title="II. EVAPORATIVE DRY EYE (EDE)",
right_items=[
"A. INTRINSIC CAUSES:",
"Meibomian Gland Dysfunction (MGD):",
" Posterior blepharitis, rosacea",
"Lid aperture disorders:",
" Scleral show, lid retraction",
" Proptosis, facial nerve palsy",
"Low blink rate:",
" Parkinson disease",
" VDU/computer/reading use",
"Drug-induced evaporation:",
" Antihistamines, beta-blockers",
" Antispasmodics, diuretics",
"",
"B. EXTRINSIC CAUSES:",
"Vitamin A deficiency",
"Topical drug toxicity / preservatives",
"Contact lens wear",
"Ocular surface disease (allergic conjunctivitis)",
],
left_tc=MID_BLUE, right_tc=TEAL))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("DEWS II Severity Grading", h2_style))
story.append(Paragraph(
"Severity is graded 1–4 using the OSDI questionnaire combined with clinical findings. "
"Treatment advances stepwise through these levels.",
body_style))
story.append(Spacer(1, 0.15*cm))
sev_data = [
["Grade", "Symptoms", "Conjunctival Staining", "Corneal Staining", "TBUT (sec)", "Schirmer (mm/5min)"],
["1 — Mild", "Mild/episodic\nenvironmental triggers", "None–mild", "None", ">10", ">10"],
["2 — Moderate","Moderate; episodic\nor chronic", "None–mild", "None–mild peripheral","<10", ">10 or borderline"],
["3 — Severe", "Frequent/chronic;\nactivity limitation", "Moderate–severe","Moderate central", "<5", "<5"],
["4 — V. Severe","Constant; disabling;\nno relief", "Severe + corneal","Severe (filaments)", "Immed.","<2"],
]
sev_table = Table(sev_data, colWidths=[2.2*cm, 3.8*cm, 3.2*cm, 3.0*cm, 1.6*cm, 1.2*cm])
sev_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8.5),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
# Colored left cells
('TEXTCOLOR', (0,1), (0,1), GREEN),
('TEXTCOLOR', (0,2), (0,2), MID_BLUE),
('TEXTCOLOR', (0,3), (0,3), ORANGE),
('TEXTCOLOR', (0,4), (0,4), RED),
('FONTNAME', (0,1), (0,-1), 'Helvetica-Bold'),
]))
story.append(sev_table)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3: EVALUATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("3. EVALUATION AND DIAGNOSIS", TEAL))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"A systematic approach integrates patient symptoms, clinical signs, and objective tests. "
"DEWS II recommends a triaging approach: validated questionnaires → diagnostic tests "
"in sequence to minimise confounding.",
body_style))
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.1 Symptoms and History", h2_style))
story.append(Paragraph(
"Characteristic symptoms: burning, dryness, grittiness, foreign body sensation, "
"mildly decreased vision, and paradoxical excess tearing. Worsen with environmental "
"triggers (wind, AC, smoke, low humidity) and prolonged visual tasks that reduce blink rate.",
body_style))
story.append(Spacer(1, 0.1*cm))
story.append(info_box(
"Validated Questionnaires",
[
"OSDI (Ocular Surface Disease Index) — 12 items; score 0–100; "
">12 = mild, >23 = moderate, >32 = severe DED",
"SPEED (Standard Patient Evaluation of Eye Dryness)",
"DEQ-5 (Dry Eye Questionnaire 5)",
"SANDE (Symptom Assessment in Dry Eye)",
],
border_color=MID_BLUE))
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.2 Recommended Test Sequence (Wills Eye / DEWS II)", h2_style))
story.append(Paragraph(
"Perform in this order — Schirmer strips can damage the ocular surface and affect staining.",
body_style))
story.append(Spacer(1, 0.15*cm))
workup_data = [
["Step", "Test", "Method / Interpretation", "Normal / Cutoff"],
["1", "Tear Meniscus\nHeight (TMH)",
"Slit lamp before any drops; assess inferior lid margin height",
"≥ 0.2 mm (DEWS II)\n≥ 0.5 mm (Wills Eye)"],
["2", "Tear Film Break-up\nTime (TBUT/NIKBUT)",
"Fluorescein 2% + cobalt blue; time from last blink to first dry spot.\n"
"Non-invasive TBUT (NIKBUT) preferred by DEWS II.",
"< 10 sec = suspicious\n< 5 sec = severe\nNIKBUT < 10 sec = abnormal"],
["3", "Ocular Surface\nStaining",
"Fluorescein: corneal/conjunctival epithelial damage.\n"
"Lissamine green: devitalized cells (preferred; less stinging).\n"
"Rose Bengal: dead/devitalized cells (more intense; stings).",
"Oxford / van Bijsterveld grading.\nInterpalpebral = ADDE;\nInferior = blepharitis"],
["4", "Tear Osmolarity",
"TearLab™ or similar; inter-eye difference >8 mOsm/L also significant.",
"Normal ≤ 308 mOsm/L\n308–316 = mild–moderate\n>316 = moderate–severe"],
["5", "MMP-9\n(InflammaDry)",
"Point-of-care test; detects ocular surface inflammation marker.",
"≥ 40 ng/mL = positive\nSn 85%; Sp 94%"],
["6", "Schirmer Test",
"No. 41 Whatman paper (5×35 mm); lower lid junction middle/lateral 1/3; 5 min.\n"
"Schirmer I (without anaesthetic): basal + reflex secretion.\n"
"Schirmer II (with proparacaine): basal secretion only.",
"Schirmer I: ≥ 15 mm/5 min\nSchirmer II: ≥ 6 mm/5 min\n< 5 mm = severely abnormal"],
["7", "Meibomian Gland\nEvaluation",
"Meibography (IR transillumination): gland dropout grading.\n"
"Meibum expression: quality (clear → turbid → toothpaste).\n"
"Lid margin: vascular engorgement, notching, orifice plugging.",
"Meiboscore 0–3 per lid\n(3 = >67% gland dropout)"],
["8", "Lid Wiper\nEpitheliopathy",
"Lissamine green staining of upper tarsal conjunctival wiper zone; friction marker.",
"Staining ≥ 2 mm wide\nor ≥ 25% circumference\n= positive"],
]
workup_table = Table(workup_data, colWidths=[0.8*cm, 3.0*cm, 7.0*cm, 4.2*cm])
workup_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('ALIGN', (1,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [5, 4, 5, 4]),
('FONTNAME', (0,1), (0,-1), 'Helvetica-Bold'),
('TEXTCOLOR', (0,1), (0,-1), TEAL),
]))
story.append(workup_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("3.3 Additional / Ancillary Investigations", h2_style))
additional_tests = [
"Phenol red thread test: pH-sensitive thread; 15 sec; < 6 mm = abnormal",
"Tear lactoferrin: Low levels suggest ADDE (Lactoferrin InflammaDry or ELISA)",
"Fluorescein clearance test / Tear function index: Delayed clearance in all dry eye states",
"Impression cytology: Goblet cell density; squamous metaplasia grading",
"In-vivo confocal microscopy (IVCM): Corneal nerve density; sub-basal nerve plexus",
"Anterior segment OCT: Quantitative tear meniscus height/volume",
"Sjögren workup: Anti-SSA/SSB, ANA, RF, minor salivary gland biopsy",
]
for t in additional_tests:
story.append(Paragraph(f"• {t}", bullet_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4: STEPWISE MANAGEMENT
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("4. STEPWISE MANAGEMENT (DEWS II FRAMEWORK)", GREEN))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph(
"The DEWS II guidelines provide a severity-based, stepwise treatment algorithm. "
"Management proceeds to the next level if preceding measures are inadequate. "
"Underlying processes are generally not reversible; treatment aims to "
"control symptoms and prevent surface damage.",
body_style))
story.append(Spacer(1, 0.15*cm))
level_colors = {
1: GREEN,
2: MID_BLUE,
3: ORANGE,
4: RED,
}
# Level 1
story.append(level_box(1, "EDUCATION, ENVIRONMENT & FIRST-LINE THERAPIES (All patients — Grade 1)", [
"Patient education: realistic expectations, importance of compliance",
"Blink exercises: conscious blinking during reading, VDT use, TV viewing",
"Screen positioning: below eye level to reduce palpebral aperture size",
"Lifestyle modification: humidifiers, avoid fans/AC drafts, protect from wind",
"Systemic medication review: identify and eliminate contributory drugs",
"Discontinue toxic/preserved topical medications where possible",
"Artificial tear substitutes (preserved drops q.i.d.; gels; ointments at HS)",
"Basic eyelid therapy: warm compresses + lid hygiene for blepharitis/MGD",
"Reparative lid surgery if needed: entropion, ectropion, lagophthalmos",
"Caution: advise against laser refractive surgery in active DED",
"Contact lenses: modify schedule; consider daily disposables or silicone hydrogel",
"Dietary: Omega-3 supplementation (fish oil 1–3 g/day or flaxseed oil)",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 2
story.append(level_box(2, "ANTI-INFLAMMATORY AND SUPPLEMENTAL THERAPIES (Grades 2–3)", [
"Non-preserved artificial tears (up to q1–2h PRN)",
"Lubricating gel or ointment q.h.s.",
"CYCLOSPORINE A 0.05% or 0.09% (Restasis / Cequa) b.i.d.:",
" Indicated for chronic DED with decreased tears due to ocular inflammation",
" Burns on instillation for first weeks; onset 1–3 months",
" Consider concurrent mild topical steroid (loteprednol 0.5% or FML 0.1%) for 1 month",
"LIFITEGRAST 5% (Xiidra) b.i.d.: LFA-1 integrin antagonist; anti-inflammatory",
" Symptomatic improvement within 2 weeks; full effect up to 3 months",
" May cause instillation burn, transient blur, metallic taste",
"Topical steroids (short-course only): FML 0.1%, loteprednol 0.5%, prednisolone 1%",
" For acute exacerbations; monitor IOP; avoid prolonged use",
"Tetracyclines: doxycycline 50–100 mg/day for MGD, rosacea-associated DED",
" Anti-MMP and anti-inflammatory properties; minimum 6–8 weeks",
"Secretagogues: pilocarpine 5 mg QID, cevimeline 30 mg TID (for Sjögren syndrome)",
"Rebamipide 2% QID: mucin secretagogue; improves goblet cell density",
"Punctal plugs (collagen temporary or silicone/acrylic reversible):",
" Occlude lower punctum first; reduces tear drainage",
" Treat active inflammation/blepharitis BEFORE plugging",
"Moisture chamber spectacles / wrap-around side shields",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 3
story.append(level_box(3, "ADVANCED THERAPIES FOR REFRACTORY DISEASE (Grade 3 — Severe)", [
"AUTOLOGOUS SERUM EYE DROPS (20–100% concentration):",
" Rich in growth factors (EGF, TGF-β), vitamins, fibronectin",
" Promotes epithelial healing; use q.i.d. to q2h; refrigerate; 3–6 month shelf life",
"UMBILICAL CORD SERUM: similar to autologous; higher growth factor content",
"SCLERAL CONTACT LENSES: fluid reservoir over cornea; excellent for severe/irregular surface",
"Bandage soft contact lens: for erosions, filamentary keratitis",
"Permanent punctal occlusion (thermal/argon laser cautery) if plugs repeatedly fall out",
" Occlude upper and lower puncta if needed",
"FILAMENTARY KERATITIS management:",
" Remove filaments with jeweller's forceps at slit lamp",
" Acetylcysteine 10% q.i.d. (mucolytic)",
"Topical vitamin A (retinol palmitate): promotes epithelial and goblet cell recovery",
"Intense Pulsed Light (IPL): for rosacea-associated MGD/EDE",
"LipiFlow thermal pulsation: vectored heat + pressure for meibomian gland obstruction",
], level_colors))
story.append(Spacer(1, 0.25*cm))
# Level 4
story.append(level_box(4, "SYSTEMIC AND SURGICAL INTERVENTIONS (Grade 4 — Very Severe / Refractory)", [
"Systemic anti-inflammatory agents (Sjögren, cicatricial diseases):",
" Hydroxychloroquine, azathioprine, mycophenolate mofetil, rituximab",
"TARSORRHAPHY:",
" Temporary adhesive tape tarsorrhaphy (lateral 1/3 of lids, pending surgery)",
" Permanent lateral tarsorrhaphy for refractory exposure/evaporative DED",
"AMNIOTIC MEMBRANE TRANSPLANTATION: for persistent epithelial defects",
"SALIVARY GLAND TRANSPLANTATION: labial salivary gland grafts to conjunctiva",
"CONJUNCTIVAL / LIMBAL TRANSPLANTATION: severe cicatricial disease",
"MUCOUS MEMBRANE GRAFTING: cicatricial pemphigoid, Stevens-Johnson syndrome",
"Meibomian gland intraductal probing: for refractory scarring/obstruction",
"Botulinum toxin for punctal closure (experimental)",
], level_colors))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("Summary: Wills Eye Manual Severity-Based Quick Guide", h2_style))
quick_data = [
["Severity", "Key Treatments"],
["MILD\n(Grade 1)",
"Artificial tears q.i.d. (preserved acceptable) · Environmental/lifestyle modification · Lid hygiene if blepharitis"],
["MODERATE\n(Grade 2)",
"Preservative-free tears up to q1–2h · Lubricating gel/ointment q.h.s. · "
"Cyclosporine 0.05% or 0.09% b.i.d. · Lifitegrast 5% b.i.d. · "
"Lifestyle modification · Punctal occlusion if above inadequate"],
["SEVERE\n(Grade 3–4)",
"Continue cyclosporine/lifitegrast · Punctal occlusion (lower + upper; thermal cautery if plugs fail) · "
"Lubricating gel b.i.d.–q.i.d. · Moisture chamber goggles · "
"Acetylcysteine 10% for filamentary keratitis · Autologous serum · Topical vitamin A · "
"Scleral/bandage lens · Lateral tarsorrhaphy as last resort"],
]
quick_table = Table(quick_data, colWidths=[2.8*cm, 12.2*cm])
quick_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREEN),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9.5),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('ALIGN', (1,1), (1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [6, 5, 6, 5]),
('FONTNAME', (0,1), (0,-1), 'Helvetica-Bold'),
('TEXTCOLOR', (0,1), (0,1), GREEN),
('TEXTCOLOR', (0,2), (0,2), MID_BLUE),
('TEXTCOLOR', (0,3), (0,3), RED),
]))
story.append(quick_table)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5: SPECIAL SITUATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("5. SPECIAL SITUATIONS AND MANAGEMENT PEARLS", PURPLE))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("5.1 Sjögren Syndrome DED", h2_style))
story.append(Paragraph(
"SS DED is typically severe ADDE. ACR criteria: positive anti-SSA/SSB (or RF + ANA), "
"ocular surface staining above threshold grade, and focal lymphocytic sialadenitis "
"on minor salivary gland biopsy.",
body_style))
for i in [
"All topical therapies at appropriate severity level",
"Cyclosporine 0.05% preferred given the strong inflammatory component",
"Punctal occlusion often required early in disease course",
"Secretagogues: pilocarpine 5 mg QID or cevimeline 30 mg TID for systemic dryness",
"Hydroxychloroquine for systemic manifestations",
"Rheumatology referral essential; monitor for lymphoma (increased risk in primary SS)",
]:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("5.2 MGD-Predominant / Evaporative DED", h2_style))
for i in [
"Warm compresses twice daily (45°C for 5–10 min) to liquefy meibum",
"Lid massage and hygiene (lid scrubs or commercial wipes)",
"Omega-3 fatty acids 1–3 g/day (fish oil or flaxseed oil)",
"Oral doxycycline 50–100 mg/day for minimum 6–8 weeks (anti-MMP properties)",
"Azithromycin topical 1% as alternative to systemic tetracyclines",
"IPL (Intense Pulsed Light) for rosacea-associated MGD",
"LipiFlow vectored thermal pulsation for meibomian gland obstruction",
"Meibomian gland intraductal probing for scarring/obstruction",
]:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("5.3 Post-Refractive Surgery / Post-LASIK DED", h2_style))
story.append(Paragraph(
"Caused by severing corneal nerves during flap creation, reducing afferent reflex arc. "
"SMILE has lower incidence than LASIK. Usually resolves within 6–12 months.",
body_style))
for i in [
"Frequent preservative-free artificial tears from day of surgery",
"Cyclosporine 0.05% b.i.d. starting 1 month pre-operatively in at-risk patients",
"Avoid LASIK in patients with pre-existing dry eye (relative contraindication)",
"Punctal occlusion if symptoms persist beyond 3–6 months",
"Autologous serum for recalcitrant neurotrophic epitheliopathy",
]:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(info_box(
"Drug Classes That Can Cause or Worsen Dry Eye",
[
"Anticholinergics, antihistamines (H1 and H2 blockers)",
"Beta-blockers (systemic and topical ophthalmic)",
"Diuretics (reduces aqueous secretion)",
"Antidepressants: TCAs, SSRIs",
"Antipsychotics, antispasmodics",
"Oral contraceptives, retinoids, isotretinoin (reduces meibomian gland size/function)",
"Chemotherapy agents",
"Topical preserved eye drops (benzalkonium chloride/BAK preservative)",
],
border_color=RED))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6: FOLLOW-UP AND MONITORING
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("6. FOLLOW-UP, MONITORING AND PATIENT EDUCATION", GREY_DARK))
story.append(Spacer(1, 0.35*cm))
story.append(Paragraph("Follow-up Schedule", h2_style))
fu_data = [
["Severity", "Initial Review", "Subsequent Follow-up"],
["Mild (Grade 1)", "4–6 weeks", "Every 3–6 months; earlier if worsening"],
["Moderate (Grade 2)", "2–4 weeks after starting Csa/Lifitegrast", "Every 2–3 months; titrate treatment"],
["Severe (Grade 3–4)", "1–2 weeks initially; after procedures", "Monthly until stable; 3-monthly long-term"],
["Post-Refractive Surgery","1 week, 1 month, 3 months, 6 months", "As clinically indicated"],
]
fu_table = Table(fu_data, colWidths=[4*cm, 4.5*cm, 6.5*cm])
fu_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREY_DARK),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 9),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('GRID', (0,0), (-1,-1), 0.5, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [6, 4, 6, 4]),
]))
story.append(fu_table)
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph("Treatment Response Assessment", h2_style))
for i in [
"OSDI or SPEED score reduction (aim for >25% improvement from baseline)",
"TBUT improvement (target > 10 sec non-invasive)",
"Reduction in corneal/conjunctival staining grade",
"Tear osmolarity normalization (< 308 mOsm/L or inter-eye difference < 8 mOsm/L)",
"MMP-9 negativity on InflammaDry",
"Schirmer improvement (in aqueous-deficient DED)",
"Patient-reported improvement in daily activities and quality of life",
]:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("Key Patient Education Points", h2_style))
for i in [
"DED is a chronic condition requiring long-term management — not a quick fix",
"Compliance with regular instillation is essential even when asymptomatic",
"Use preservative-free drops if instilling more than 4 times per day",
"Cyclosporine and lifitegrast require weeks-to-months for full benefit",
"Take 20-20-20 breaks during screen use: every 20 min, look 20 ft away for 20 sec",
"Blink consciously during prolonged screen/reading/TV time",
"Avoid rubbing eyes — exacerbates inflammation and surface damage",
"Lubricating ointment at bedtime for 3–6 months after epithelial healing reduces recurrence",
]:
story.append(Paragraph(f"• {i}", bullet_style))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 7: REFERENCES
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("7. REFERENCES", GREY_DARK))
story.append(Spacer(1, 0.35*cm))
refs = [
("1", "TFOS DEWS II Executive Summary.",
"Craig JP, Nelson JD, Azar DT, et al.",
"Ocul Surf. 2017;15(4):802–812. doi:10.1016/j.jtos.2017.08.003"),
("2", "TFOS DEWS II Definition and Classification Report.",
"Stapleton F, Alves M, Bunya VY, et al.",
"Ocul Surf. 2017;15(3):276–283."),
("3", "TFOS DEWS II Pathophysiology Report.",
"Bron AJ, de Paiva CS, Chauhan SK, et al.",
"Ocul Surf. 2017;15(3):438–510."),
("4", "TFOS DEWS II Diagnostic Methodology Report.",
"Wolffsohn JS, Arita R, Chalmers R, et al.",
"Ocul Surf. 2017;15(3):539–574."),
("5", "TFOS DEWS II Management and Therapy Report.",
"Jones L, Downie LE, Korb D, et al.",
"Ocul Surf. 2017;15(3):575–628."),
("6", "Kanski's Clinical Ophthalmology: A Systematic Approach, 10th Ed.",
"Bowling B.",
"Elsevier, 2024. Chapter 5."),
("7", "The Wills Eye Manual, 8th Edition.",
"Wills Eye Hospital.",
"Wolters Kluwer, 2022. Section 4.3: Dry Eye Syndrome, pp. 171–176."),
("8", "Goodman & Gilman's The Pharmacological Basis of Therapeutics, 14th Ed.",
"Brunton LL, et al. (Eds.).",
"McGraw-Hill, 2023."),
("9", "Association between potential factors and dry eye disease: A systematic review.",
"Yang K, Wu S, Ke L.",
"Medicine (Baltimore). 2024 Dec 27. PMID: 39969375."),
]
ref_data = [["No.", "Title", "Authors", "Source"]]
for no, title, authors, source in refs:
ref_data.append([no, title, authors, source])
ref_table = Table(ref_data, colWidths=[0.6*cm, 5.5*cm, 4.5*cm, 4.4*cm])
ref_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREY_DARK),
('TEXTCOLOR', (0,0), (-1,0), white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8.5),
('BACKGROUND', (0,1), (-1,-1), white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [white, ROW_ALT]),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 7.5),
('GRID', (0,0), (-1,-1), 0.3, GREY_MID),
('ROWPADDING', (0,0), (-1,-1), [4, 3, 4, 3]),
]))
story.append(ref_table)
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=0.75, color=GREY_MID))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"This document is a clinical reference for educational purposes. "
"All management decisions should be individualized based on clinical assessment. "
"For the full TFOS DEWS II reports visit: www.tearfilm.org",
ref_style))
# ─── BUILD ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF generated: {OUTPUT}")
cd /tmp/workspace/dry-eye-pdf && python generate_dry_eye_white.py 2>&1
python3 -c "from pypdf import PdfReader; r = PdfReader('/tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf'); print(f'Pages: {len(r.pages)}')" && ls -lh /tmp/workspace/dry-eye-pdf/Dry_Eye_Disease_Clinical_Reference.pdf
Dry Eye Disease Clinical Reference
PDF Document · PDF
#FAFAFA alternating rows — no colored row fills