Definition etiology causes clinical features examination diagnosis and management of Epiphora for Ent pg exam

Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Finding Sources
Reading File
Finding Sources
Reading File
Reading File
Reading File
Reading File
Loading Image
Loading Image
Here is a comprehensive PG exam-oriented write-up on Epiphora:

EPIPHORA — Complete ENT/Ophthalmology PG Exam Notes


1. DEFINITION

Epiphora is the overflow of tears onto the cheek/face due to either hypersecretion of tears beyond normal drainage capacity, or defective drainage of tears through the lacrimal drainage system.
"Epiphora is the overflow of tears at the eyelid margin." — Kanski's Clinical Ophthalmology, 10th ed.

2. RELEVANT ANATOMY

Anatomy of the lacrimal drainage system
Fig. 3.1 — Anatomy of the lacrimal drainage system (Kanski's Clinical Ophthalmology)
The lacrimal drainage system:
StructureDimensions
Ampulla (vertical canaliculus)2 mm
Horizontal canaliculus~8 mm
Common canaliculusjoins lateral wall of lacrimal sac
Lacrimal sac10–12 mm; lies in lacrimal fossa
Nasolacrimal duct12–18 mm; opens into inferior meatus
Valve of Hasnermucosal fold at NLD opening
Valve of Rosenmüllerat junction of common canaliculus & sac — prevents reflux
Physiology of tear drainage: Tears flow along the marginal strips → lacus lacrimalis → enter canaliculi by capillarity + suction. On blinking, orbicularis (Horner muscle) compresses the ampullae and forces tears down the nasolacrimal duct. On eye opening, negative pressure draws tears from canaliculi into the sac.

3. ETIOLOGY / CAUSES

Two fundamental mechanisms:

A. Hypersecretion (Lacrimation)

Reflex overproduction exceeds drainage capacity:
  • Dry eye ("paradoxical watering" — commonest cause overall)
  • Conjunctivitis, keratitis, uveitis
  • Entropion / trichiasis / aberrant eyelashes
  • Corneal foreign body or ulcer
  • Congenital hereditary endothelial dystrophy
  • Sclerocornea (in neonates)
  • Congenital glaucoma (rare)

B. Defective Drainage

I. Punctal/Lid Malposition

  • Ectropion (commonest lid cause) — punctum everts away from globe
  • Centurion syndrome — medial lid displaced anteriorly by prominent nasal bridge
  • Lower lid laxity

II. Obstruction at any level

LevelCause
PunctalStenosis (chronic blepharitis, herpes, radiation, cicatrising conjunctivitis, 5-FU, topical glaucoma drops); atresia (congenital)
CanalicularCanaliculitis (Actinomyces israelii), trauma, herpes, cicatrising conditions
Lacrimal sacDacryocystitis, dacryolithiasis, tumour
NLDPANDO (Primary Acquired NLD Obstruction) — commonest acquired cause in adults; congenital NLD obstruction (valve of Hasner failure to canalise); secondary — sinusitis, trauma, tumour

III. Lacrimal Pump Failure

  • Facial nerve palsy (orbicularis weakness)
  • Lower lid laxity / involutional change
  • Conjunctivochalasis (redundant conjunctival fold blocking punctum)

4. CLINICAL FEATURES

Symptoms

  • Tears overflowing onto the cheek (cardinal symptom)
  • Worse in cold, windy weather; better in warm, dry room (suggests drainage failure)
  • Mucopurulent discharge (especially in NLD obstruction)
  • Crusting of lashes on waking
  • Skin maceration/excoriation at medial canthus
  • Swelling at medial canthus (dacryocystitis, mucocele, dacryocoele)

In Neonates / Congenital NLD Obstruction

  • Epiphora present in ~20% of normal babies
  • Spontaneous resolution in ~90% by age 1 year
  • Mucopurulent discharge (sticky eye)
  • Congenital dacryocoele: bluish cystic swelling at/below medial canthus at birth
Epiphora clinical photos
Fig. 3.18 — (A) Child with watering eye; (B) probing of nasolacrimal duct; (C) congenital dacryocele (Kanski's)

5. EXAMINATION

Step 1 — Slit-lamp Examination (Before any manipulation)

  • Marginal tear strip (meniscus): Normal = 0.2–0.4 mm; Elevated ≥0.6 mm = abnormal
  • Examine puncta (do this BEFORE cannulation to avoid masking stenosis):
    • Punctal stenosis (commonest cause of drainage failure)
    • Ectropion / eversion
    • Pouting punctum → canaliculitis
    • Conjunctivochalasis occluding punctum
    • Eyelash in ampulla

Step 2 — Lacrimal Sac Palpation

  • Mucocele: Regurgitation of mucopurulent material on pressure → NLD obstruction with patent canaliculi
  • Acute dacryocystitis: Tender swelling — do NOT press (or irrigate in acute phase)
  • Hard mass → stone or tumour

Step 3 — Fluorescein Disappearance Test (FDT)

  • Instil fluorescein 1–2% drops into both conjunctival fornices
  • Normal: little/no dye remains at 5–10 minutes under blue light
  • Prolonged retention = inadequate lacrimal drainage

Step 4 — Jones Tests (Dye Tests)

  • Jones I (Primary Dye Test): Fluorescein instilled; nasal pledget checked at 5 minutes.
    • Positive (normal): fluorescein recovered from nose → drainage intact
    • Negative: proceed to Jones II
  • Jones II (Secondary Dye Test): Irrigate canaliculus with saline after prior fluorescein instillation:
    • Fluorescein-stained saline from nose → partial NLD obstruction (fluorescein entered sac)
    • Unstained saline from nose → upper lacrimal (punctal/canalicular) dysfunction or pump failure

Step 5 — Lacrimal Syringing (Irrigation)

  • Dilate punctum → insert blunt 26/27G lacrimal cannula into lower punctum
  • Hard stop (cannula hits medial lacrimal bone) = canalicular system patent to sac level
  • Soft stop (cannula meets soft resistance before hard stop) = canalicular obstruction
  • Gently irrigate:
    • Free flow to nasopharynx = patent NLD
    • Regurgitation from same punctum = complete canalicular block
    • Regurgitation from opposite punctum = NLD obstruction (common canaliculus patent)
    • Partial flow with resistance = partial NLD obstruction (functional block)

6. INVESTIGATIONS / DIAGNOSIS

InvestigationPurpose
Fluorescein disappearance testScreening; highly specific for congenital NLD obstruction
Jones I & II testsLocalise level of obstruction
Lacrimal syringingConfirm and localise obstruction; gold standard for functional test
ProbingDiagnose + treat congenital NLD obstruction
Dacryocystography (DCG)Radio-opaque contrast injected into canaliculi; shows anatomy, site of block, diverticula, filling defects (stones, tumours); digital subtraction DCG is most detailed
Nuclear lacrimal scintigraphyPhysiological assessment with radiolabelled tears; identifies partial or functional blocks; less anatomical detail than DCG
CT/MRIParanasal sinus disease, lacrimal sac tumour
Nasal endoscopyPolyps, deviated septum, intranasal pathology
Microbiological swabDischarge in canaliculitis/dacryocystitis

7. MANAGEMENT

Management is directed at the underlying cause and level of obstruction.

A. Treat Hypersecretion (Medical)

  • Artificial tears/lubricants for dry eye paradoxical watering
  • Anti-inflammatory drops (ciclosporin, steroids) for blepharitis/dry eye
  • Treat underlying conjunctivitis, trichiasis, entropion

B. Congenital NLD Obstruction

  1. Conservative (first-line, <12 months): Crigler massage (Lacrimal sac massage) — index finger over common canaliculus, roll downwards over sac to create hydrostatic pressure rupturing the Hasner membrane
  2. Probing (definitive treatment): Fine wire via canalicular system → NLD → disrupts obstructing membrane at valve of Hasner; can be repeated if first attempt fails
    • Delayed until 12–18 months (may delay to 24 months if mild)
    • Under GA; topical anaesthesia for very young infants in outpatient setting
    • Follow with irrigation to confirm patency
  3. If probing fails: Silicone tube intubation ± balloon dilatation of NLD; endoscopic procedures; dacryocystorhinostomy (DCR)
  4. Congenital dacryocoele: Conservative first; probing if fails

C. Acquired Punctal/Canalicular Obstruction

  • Punctal stenosis:
    • Punctal dilatation ± mini-Monoka stent (removed at 1 month)
    • Punctoplasty (one-, two- or three-snip procedure) if dilation fails
  • Ectropion: Lid tightening procedures (lateral tarsal strip, medial canthoplasty)
  • Conjunctivochalasis: Topical lubricants/anti-inflammatories; surgical excision/conjunctival suturing
  • Canaliculitis (Actinomyces): Canaliculotomy + curettage + topical penicillin/cefuroxime

D. Lacrimal Sac / NLD Obstruction (Acquired / PANDO)

Dacryocystorhinostomy (DCR) — definitive operation:
  • Creates anastomosis between lacrimal sac and nasal mucosa, bypassing NLD obstruction
  • External DCR (standard):
    • Vertical skin incision 10 mm medial to inner canthus
    • Periosteum elevated; anterior lacrimal crest and lacrimal fossa bone removed
    • Lacrimal sac opened with H-shaped incision → anterior and posterior flaps
    • Nasal mucosa opened → flaps sutured (posterior then anterior)
    • Success rate ~90%
  • Endoscopic (endonasal) DCR: No skin incision; good for functional failures; comparable success rates
  • Silicone tube intubation often placed at time of DCR; removed at 3–6 months

E. Dacryocystitis

  • Acute: Systemic antibiotics (IV in severe cases: amoxicillin-clavulanate or cephalosporins); warm compresses; DCR after infection resolves
  • Chronic: DCR is the definitive treatment
  • Mucocele: Pressure test confirms diagnosis; DCR required

F. Lacrimal Pump Failure (Facial Nerve Palsy)

  • Treat the underlying cause
  • Lubricating eye drops/ointment to protect cornea
  • Lateral tarsorrhaphy or lower lid tightening if laxity contributes

8. SUMMARY MNEMONIC — Causes of Epiphora

"PECK DFL"
  • P — Punctal stenosis / malposition (ectropion)
  • E — Evaporation/dry eye (paradoxical hypersecretion)
  • C — Canalicular obstruction (trauma, canaliculitis)
  • K — Keratoconjunctivitis / surface disease
  • D — Dacryocystitis / NLD obstruction (PANDO)
  • F — Facial nerve palsy (pump failure)
  • L — Lid laxity / conjunctivochalasis

9. KEY EXAM POINTS

FactDetail
Commonest cause of watering eyeParadoxical dry eye (hypersecretion)
Commonest acquired NLD obstruction in adultsPANDO (Primary Acquired NLD Obstruction)
Congenital NLD obstruction spontaneous resolution~90% by age 1 year
Site of congenital obstructionValve of Hasner (lower end NLD)
First-line treatment congenital NLD obstructionCrigler massage
Definitive Rx congenital NLD obstructionProbing
Definitive Rx acquired NLD obstruction / PANDODCR (Dacryocystorhinostomy)
Jones II positive (fluorescein-stained saline)Partial NLD obstruction — fluorescein entered sac
Jones II negative (unstained saline from nose)Upper lacrimal (punctal/canalicular) block or pump failure
Hard stop on syringingCanalicular system patent to sac
Soft stop on syringingCanalicular obstruction
Causative organism in canaliculitisActinomyces israelii
NLD drains intoInferior nasal meatus (below inferior turbinate)
DCR: anastomosis betweenLacrimal sac and nasal mucosa
Congenital dacryocoele is NOT confused withEncephalocoele (which is pulsatile, above medial canthal ligament)

Sources: Kanski's Clinical Ophthalmology: A Systematic Approach, 10th Ed. (ISBN 9780443110993); K.J. Lee's Essential Otolaryngology; Goldman-Cecil Medicine

Make the whole thing into PDF

Reading File
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace

Running Command

mkdir -p /home/daytona/workspace/epiphora-pdf

Writing File

~/epiphora-pdf/generate_pdf.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, Image as RLImage
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
from reportlab.lib.colors import HexColor
import urllib.request
import os
import io

# ── colour palette ──────────────────────────────────────────────────────────
DARK_BLUE   = HexColor('#1a3a5c')
MID_BLUE    = HexColor('#2563a8')
LIGHT_BLUE  = HexColor('#dbeafe')
ACCENT      = HexColor('#0ea5e9')
TABLE_HEAD  = HexColor('#1e3a5f')
TABLE_ALT   = HexColor('#f0f7ff')
WHITE       = colors.white
BLACK       = colors.black
GREY_TEXT   = HexColor('#374151')
BORDER      = HexColor('#93c5fd')

OUTPUT = '/home/daytona/workspace/epiphora-pdf/Epiphora_PG_Exam_Notes.pdf'

# ── document ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=A4,
    leftMargin=2*cm, rightMargin=2*cm,
    topMargin=2.5*cm, bottomMargin=2.5*cm,
    title='Epiphora — PG Exam Notes',
    author='Orris Medical'
)

W = A4[0] - 4*cm   # usable width

styles = getSampleStyleSheet()

# custom styles
def S(name, **kw):
    base = kw.pop('parent', 'Normal')
    s = ParagraphStyle(name, parent=styles[base], **kw)
    return s

title_style   = S('MyTitle',  fontSize=22, textColor=WHITE,
                  alignment=TA_CENTER, fontName='Helvetica-Bold',
                  spaceAfter=4, leading=28)
subtitle_style= S('MySub',    fontSize=11, textColor=LIGHT_BLUE,
                  alignment=TA_CENTER, fontName='Helvetica', spaceAfter=2)
date_style    = S('MyDate',   fontSize=9,  textColor=LIGHT_BLUE,
                  alignment=TA_CENTER, fontName='Helvetica-Oblique')

h1_style      = S('H1',  fontSize=14, textColor=WHITE,
                  fontName='Helvetica-Bold', spaceAfter=4, spaceBefore=14,
                  leading=18)
h2_style      = S('H2',  fontSize=11, textColor=DARK_BLUE,
                  fontName='Helvetica-Bold', spaceAfter=3, spaceBefore=10,
                  leading=15)
h3_style      = S('H3',  fontSize=10, textColor=MID_BLUE,
                  fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=6,
                  leading=14)

body_style    = S('Body', fontSize=9.5, textColor=GREY_TEXT,
                  fontName='Helvetica', spaceAfter=4, leading=14,
                  alignment=TA_JUSTIFY)
bullet_style  = S('Bullet', fontSize=9.5, textColor=GREY_TEXT,
                  fontName='Helvetica', spaceAfter=3, leading=13,
                  leftIndent=14, bulletIndent=4)
bullet2_style = S('Bullet2', fontSize=9, textColor=GREY_TEXT,
                  fontName='Helvetica', spaceAfter=2, leading=12,
                  leftIndent=28, bulletIndent=18)
tip_style     = S('Tip',  fontSize=9, textColor=HexColor('#1e40af'),
                  fontName='Helvetica-Oblique', spaceAfter=4, leading=13,
                  leftIndent=10)
source_style  = S('Source', fontSize=8, textColor=HexColor('#6b7280'),
                  fontName='Helvetica-Oblique', alignment=TA_CENTER,
                  spaceAfter=6, spaceBefore=2)
key_style     = S('Key', fontSize=9, textColor=HexColor('#166534'),
                  fontName='Helvetica-Bold', spaceAfter=2, leading=13,
                  leftIndent=14)

# ── helpers ──────────────────────────────────────────────────────────────────

def section_header(text, number):
    """Blue banner heading."""
    tbl = Table([[Paragraph(f'{number}. {text}', h1_style)]], colWidths=[W])
    tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), DARK_BLUE),
        ('TOPPADDING',    (0,0), (-1,-1), 7),
        ('BOTTOMPADDING', (0,0), (-1,-1), 7),
        ('LEFTPADDING',   (0,0), (-1,-1), 10),
        ('RIGHTPADDING',  (0,0), (-1,-1), 10),
        ('ROWBACKGROUNDS',(0,0), (-1,-1), [DARK_BLUE]),
    ]))
    return tbl

def sub_header(text):
    return Paragraph(text, h2_style)

def sub_sub_header(text):
    return Paragraph(text, h3_style)

def body(text):
    return Paragraph(text, body_style)

def bullet(text, level=1):
    prefix = '• ' if level == 1 else '◦ '
    st = bullet_style if level == 1 else bullet2_style
    return Paragraph(prefix + text, st)

def tip(text):
    tbl = Table([[Paragraph('💡 TIP: ' + text, tip_style)]], colWidths=[W])
    tbl.setStyle(TableStyle([
        ('BACKGROUND',    (0,0), (-1,-1), HexColor('#eff6ff')),
        ('LEFTPADDING',   (0,0), (-1,-1), 10),
        ('RIGHTPADDING',  (0,0), (-1,-1), 10),
        ('TOPPADDING',    (0,0), (-1,-1), 6),
        ('BOTTOMPADDING', (0,0), (-1,-1), 6),
        ('BOX',           (0,0), (-1,-1), 0.5, HexColor('#bfdbfe')),
        ('ROUNDEDCORNERS',(0,0), (-1,-1), 3),
    ]))
    return tbl

def sp(h=4):
    return Spacer(1, h)

def hr():
    return HRFlowable(width='100%', thickness=0.5, color=BORDER, spaceAfter=4, spaceBefore=4)

def make_table(headers, rows, col_widths=None):
    if col_widths is None:
        col_widths = [W/len(headers)]*len(headers)
    hdr_style = ParagraphStyle('th', fontSize=9, textColor=WHITE,
                               fontName='Helvetica-Bold', leading=12)
    cell_style= ParagraphStyle('td', fontSize=8.5, textColor=GREY_TEXT,
                               fontName='Helvetica', leading=12)
    data = [[Paragraph(h, hdr_style) for h in headers]]
    for i, row in enumerate(rows):
        data.append([Paragraph(str(c), cell_style) for c in row])
    tbl = Table(data, colWidths=col_widths, repeatRows=1)
    cmd = [
        ('BACKGROUND',    (0,0), (-1,0), TABLE_HEAD),
        ('TEXTCOLOR',     (0,0), (-1,0), WHITE),
        ('TOPPADDING',    (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING',   (0,0), (-1,-1), 7),
        ('RIGHTPADDING',  (0,0), (-1,-1), 7),
        ('GRID',          (0,0), (-1,-1), 0.4, BORDER),
        ('VALIGN',        (0,0), (-1,-1), 'TOP'),
    ]
    for i in range(1, len(data)):
        bg = TABLE_ALT if i % 2 == 0 else WHITE
        cmd.append(('BACKGROUND', (0,i), (-1,i), bg))
    tbl.setStyle(TableStyle(cmd))
    return tbl

def download_image(url, filename):
    path = f'/home/daytona/workspace/epiphora-pdf/{filename}'
    if not os.path.exists(path):
        try:
            urllib.request.urlretrieve(url, path)
        except Exception as e:
            print(f'Image download failed: {e}')
            return None
    return path

# ── download images ───────────────────────────────────────────────────────────
img1_path = download_image(
    'https://cdn.orris.care/cdss_images/3a8a23ad5c9d6f2155842d46f4f6c5e369ee22d9348a7b6f6fcee983280f86ac.png',
    'anatomy.png')
img2_path = download_image(
    'https://cdn.orris.care/cdss_images/bb7f941f6f8bbcd929daf997a6d4ced1623339a125df7b0bef1d163fd4c3d321.png',
    'epiphora_clinical.png')

# ── title page flowable ───────────────────────────────────────────────────────
def title_block():
    tbl = Table([
        [Paragraph('EPIPHORA', title_style)],
        [Paragraph('Complete PG Exam Notes — ENT / Ophthalmology', subtitle_style)],
        [Paragraph('Source: Kanski\'s Clinical Ophthalmology 10th Ed. | KJ Lee\'s Otolaryngology | Goldman-Cecil Medicine', date_style)],
    ], colWidths=[W])
    tbl.setStyle(TableStyle([
        ('BACKGROUND',    (0,0), (-1,-1), DARK_BLUE),
        ('TOPPADDING',    (0,0), (0,0),   20),
        ('BOTTOMPADDING', (0,-1),(-1,-1), 20),
        ('TOPPADDING',    (0,1), (-1,-2), 4),
        ('BOTTOMPADDING', (0,0), (-1,-2), 4),
        ('LEFTPADDING',   (0,0), (-1,-1), 16),
        ('RIGHTPADDING',  (0,0), (-1,-1), 16),
    ]))
    return tbl

# ── BUILD STORY ───────────────────────────────────────────────────────────────
story = []

# Title
story.append(title_block())
story.append(sp(12))

# ═══════════════════════════════════════════════════════════════════════════════
# 1. DEFINITION
# ═══════════════════════════════════════════════════════════════════════════════
story.append(section_header('DEFINITION', 1))
story.append(sp(6))
story.append(body(
    '<b>Epiphora</b> is the overflow of tears onto the cheek/face due to either '
    '<b>hypersecretion</b> of tears beyond normal drainage capacity, or '
    '<b>defective drainage</b> of tears through the lacrimal drainage system.'
))
story.append(sp(4))
story.append(tip(
    'It is common for a watering eye to be caused by reflex hypersecretion of tears '
    'secondary to a dry ocular surface ("paradoxical watering").'
))

# ═══════════════════════════════════════════════════════════════════════════════
# 2. ANATOMY
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('RELEVANT ANATOMY', 2))
story.append(sp(6))

if img1_path:
    img = RLImage(img1_path, width=10*cm, height=8*cm, kind='proportional')
    story.append(img)
    story.append(Paragraph(
        'Fig. 3.1 — Anatomy of the lacrimal drainage system (Kanski\'s Clinical Ophthalmology)',
        source_style))
    story.append(sp(6))

anat_rows = [
    ['Ampulla (vertical canaliculus)', '2 mm; part of tarsal plate'],
    ['Horizontal canaliculus',         '~8 mm; surrounded by Horner muscle (palpebral orbicularis)'],
    ['Common canaliculus',             'Superior + inferior canaliculi unite (>90%); opens into lateral wall of lacrimal sac'],
    ['Valve of Rosenmüller',           'Mucosal flap at junction of common canaliculus and sac; prevents reflux'],
    ['Lacrimal sac',                   '10–12 mm; lies in lacrimal fossa between anterior & posterior lacrimal crests'],
    ['Nasolacrimal duct (NLD)',        '12–18 mm; opens into inferior nasal meatus below inferior turbinate'],
    ['Valve of Hasner',                'Mucosal fold at lower end of NLD; last part to canalise at birth'],
]
story.append(make_table(['Structure', 'Key Details'], anat_rows, [6.5*cm, 11.5*cm]))
story.append(sp(8))

story.append(sub_header('Physiology of Tear Drainage'))
for t in [
    'Tears flow along marginal strips → lacus lacrimalis → enter canaliculi by capillarity + suction.',
    'On <b>blinking</b>: orbicularis compresses ampullae, shortens canaliculi, closes puncta medially → forces tears down NLD.',
    'On <b>eye opening</b>: canaliculi and sac expand, creating negative pressure that draws tears from canaliculi into sac.',
]:
    story.append(bullet(t))

# ═══════════════════════════════════════════════════════════════════════════════
# 3. ETIOLOGY
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('ETIOLOGY / CAUSES', 3))
story.append(sp(6))

story.append(sub_header('A. Hypersecretion (Lacrimation) — Reflex Overproduction'))
for t in [
    '<b>Dry eye</b> — "paradoxical watering" (commonest cause overall)',
    'Conjunctivitis, keratitis, uveitis, scleritis',
    'Entropion / trichiasis / aberrant eyelashes / corneal foreign body',
    'Congenital hereditary endothelial dystrophy',
    'Sclerocornea (neonates)',
    'Congenital glaucoma (rare)',
]:
    story.append(bullet(t))

story.append(sp(6))
story.append(sub_header('B. Defective Drainage'))

story.append(sub_sub_header('I. Punctal / Lid Malposition'))
for t in [
    '<b>Ectropion</b> (commonest lid cause) — punctum everts away from globe',
    '<b>Centurion syndrome</b> — medial lid displaced anteriorly by prominent nasal bridge',
    'Lower lid laxity / involutional changes',
]:
    story.append(bullet(t))

story.append(sp(4))
story.append(sub_sub_header('II. Obstruction at Any Level'))
obs_rows = [
    ['Punctal',       'Stenosis (chronic blepharitis, herpes, radiation, cicatrising conjunctivitis, 5-FU, topical glaucoma drops); congenital atresia'],
    ['Canalicular',   'Canaliculitis (Actinomyces israelii), trauma, herpes simplex, cicatrising conditions'],
    ['Lacrimal sac',  'Dacryocystitis, dacryolithiasis, tumour, mucocele'],
    ['NLD',           'PANDO (Primary Acquired NLD Obstruction) — commonest acquired adult cause; congenital NLD obstruction (Hasner valve); secondary — sinusitis, trauma, tumour, post-surgical'],
]
story.append(make_table(['Level', 'Causes'], obs_rows, [4*cm, 14*cm]))
story.append(sp(4))

story.append(sub_sub_header('III. Lacrimal Pump Failure'))
for t in [
    '<b>Facial nerve palsy</b> (orbicularis weakness)',
    'Lower lid laxity / involutional change',
    '<b>Conjunctivochalasis</b> — redundant conjunctival fold blocking punctum',
]:
    story.append(bullet(t))

# ═══════════════════════════════════════════════════════════════════════════════
# 4. CLINICAL FEATURES
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('CLINICAL FEATURES', 4))
story.append(sp(6))

story.append(sub_header('Symptoms'))
for t in [
    '<b>Tears overflowing onto the cheek</b> — cardinal symptom',
    'Worse in cold, windy weather; better in warm, dry room (→ drainage failure)',
    'Mucopurulent discharge (especially in NLD obstruction)',
    'Crusting of lashes on waking',
    'Skin maceration / excoriation at medial canthus',
    'Swelling at medial canthus (dacryocystitis, mucocele, dacryocoele)',
]:
    story.append(bullet(t))

story.append(sp(6))
story.append(sub_header('In Neonates / Congenital NLD Obstruction'))
for t in [
    'Epiphora in ~20% of normal babies at birth',
    'Spontaneous resolution in ~<b>90% by age 1 year</b>',
    'Mucopurulent sticky discharge',
    '<b>Congenital dacryocoele (amniotocoele)</b>: bluish cystic swelling at/below medial canthus at birth; must not be confused with encephalocoele (pulsatile, above medial canthal ligament)',
]:
    story.append(bullet(t))

story.append(sp(6))
if img2_path:
    img = RLImage(img2_path, width=12*cm, height=9*cm, kind='proportional')
    story.append(img)
    story.append(Paragraph(
        'Fig. 3.18 — (A) Child with watering eye; (B) probing of NLD; (C) congenital dacryocele '
        '(Kanski\'s Clinical Ophthalmology)',
        source_style))

# ═══════════════════════════════════════════════════════════════════════════════
# 5. EXAMINATION
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('EXAMINATION', 5))
story.append(sp(6))

exam_steps = [
    ('Step 1 — Slit-lamp Examination (Before any manipulation)',
     [
         '<b>Marginal tear strip (meniscus)</b>: Normal = 0.2–0.4 mm; Elevated ≥0.6 mm = abnormal',
         'Examine <b>puncta BEFORE cannulation</b> (irrigation dilates punctum, masking stenosis)',
         'Punctal stenosis — commonest cause of drainage failure',
         'Ectropion / eversion of punctum',
         'Pouting punctum → canaliculitis',
         'Conjunctivochalasis occluding punctum',
         'Eyelash in ampulla; large caruncle displacing punctum',
     ]),
    ('Step 2 — Lacrimal Sac Palpation',
     [
         '<b>Mucocele</b>: Regurgitation of mucopurulent material on pressure → NLD obstruction with patent canaliculi',
         '<b>Acute dacryocystitis</b>: Tender swelling — do NOT press (do NOT irrigate in acute phase)',
         'Hard mass on palpation → stone or tumour',
     ]),
    ('Step 3 — Fluorescein Disappearance Test (FDT)',
     [
         'Instil fluorescein 1–2% drops into both conjunctival fornices',
         'Normal: little/no dye remains at 5–10 min under blue light in darkened room',
         '<b>Prolonged retention</b> = inadequate lacrimal drainage',
     ]),
    ('Step 4 — Jones Dye Tests',
     [
         '<b>Jones I (Primary Dye Test)</b>: Fluorescein instilled; nasal pledget checked at 5 min.',
         'Positive (normal): fluorescein recovered from nose → drainage intact',
         'Negative: proceed to Jones II',
         '<b>Jones II (Secondary Dye Test)</b>: Irrigate canaliculus with saline after prior fluorescein:',
         'Fluorescein-stained saline from nose → partial NLD obstruction (fluorescein entered sac)',
         'Unstained saline from nose → upper lacrimal (punctal/canalicular) dysfunction or pump failure',
     ]),
    ('Step 5 — Lacrimal Syringing (Irrigation)',
     [
         'Dilate punctum → insert blunt 26/27G lacrimal cannula into lower punctum',
         '<b>Hard stop</b> (cannula hits medial lacrimal bone) = canalicular system patent to sac level',
         '<b>Soft stop</b> (soft resistance before hard stop) = canalicular obstruction',
         'Free flow to nasopharynx → NLD patent',
         'Regurgitation from <i>same</i> punctum → complete canalicular block',
         'Regurgitation from <i>opposite</i> punctum → NLD obstruction (common canaliculus patent)',
         'Partial flow with resistance → partial NLD obstruction / functional block',
     ]),
]

for (header, bullets) in exam_steps:
    story.append(KeepTogether([
        sub_header(header),
        *[bullet(t) for t in bullets],
        sp(4),
    ]))

# ═══════════════════════════════════════════════════════════════════════════════
# 6. INVESTIGATIONS / DIAGNOSIS
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('INVESTIGATIONS / DIAGNOSIS', 6))
story.append(sp(6))

inv_rows = [
    ['Fluorescein Disappearance Test',  'Screening; highly specific for congenital NLD obstruction'],
    ['Jones I & II Tests',              'Localise level of obstruction (upper vs lower drainage system)'],
    ['Lacrimal Syringing',              'Confirm and localise obstruction; gold standard functional test'],
    ['Probing',                         'Diagnose AND treat congenital NLD obstruction'],
    ['Dacryocystography (DCG)',         'Radio-opaque contrast injected into canaliculi; shows anatomy, site of block, diverticula, filling defects (stones, tumours); digital subtraction DCG most detailed'],
    ['Nuclear Lacrimal Scintigraphy',   'Physiological assessment with radiolabelled tears; identifies partial or functional blocks; less anatomical detail than DCG'],
    ['CT / MRI',                        'Paranasal sinus disease, lacrimal sac tumour, complex anatomy'],
    ['Nasal Endoscopy',                 'Polyps, deviated septum, intranasal pathology'],
    ['Microbiological swab',            'Discharge in canaliculitis (Actinomyces) or dacryocystitis'],
]
story.append(make_table(['Investigation', 'Purpose'], inv_rows, [6*cm, 12*cm]))

# ═══════════════════════════════════════════════════════════════════════════════
# 7. MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('MANAGEMENT', 7))
story.append(sp(6))

story.append(sub_header('A. Treat Hypersecretion (Medical)'))
for t in [
    'Artificial tears / lubricants for dry eye paradoxical watering',
    'Anti-inflammatory drops (ciclosporin, topical steroids) for blepharitis / dry eye',
    'Treat underlying conjunctivitis, trichiasis, entropion',
]:
    story.append(bullet(t))

story.append(sp(6))
story.append(sub_header('B. Congenital NLD Obstruction'))
steps = [
    ('<b>Conservative (first-line, &lt;12 months)</b>',
     'Crigler massage — index finger over common canaliculus, rolled downwards over sac to create hydrostatic pressure rupturing Hasner membrane'),
    ('<b>Probing (definitive treatment)</b>',
     'Fine wire via canalicular system → NLD → disrupts obstructing membrane at valve of Hasner; '
     'delayed until 12–18 months (or 24 months if mild symptoms); under GA; can be repeated; '
     'follow with irrigation to confirm patency'),
    ('<b>If probing fails</b>',
     'Silicone tube intubation ± balloon dilatation of NLD; endoscopic procedures; dacryocystorhinostomy (DCR)'),
    ('<b>Congenital dacryocoele</b>',
     'Conservative first; probing if conservative management fails'),
]
for (bold, text) in steps:
    story.append(bullet(f'{bold}: {text}'))

story.append(sp(6))
story.append(sub_header('C. Acquired Punctal / Canalicular Obstruction'))
for t in [
    '<b>Punctal stenosis</b>: Punctal dilatation ± mini-Monoka stent (removed at 1 month); <b>Punctoplasty</b> (one-, two- or three-snip procedure) if dilation fails',
    '<b>Ectropion</b>: Lid tightening procedures (lateral tarsal strip, medial canthoplasty)',
    '<b>Conjunctivochalasis</b>: Topical lubricants / anti-inflammatories; surgical excision or conjunctival suturing',
    '<b>Canaliculitis (Actinomyces)</b>: Canaliculotomy + curettage + topical penicillin / cefuroxime',
]:
    story.append(bullet(t))

story.append(sp(6))
story.append(sub_header('D. Acquired NLD Obstruction / PANDO — Dacryocystorhinostomy (DCR)'))
story.append(body(
    'DCR creates an anastomosis between the lacrimal sac and nasal mucosa, bypassing NLD obstruction. '
    'Success rate ~90%.'
))

dcr_rows = [
    ['External DCR (standard)',
     'Vertical skin incision 10 mm medial to inner canthus; '
     'anterior lacrimal crest and lacrimal fossa bone removed; '
     'sac opened with H-shaped incision (anterior + posterior flaps); '
     'nasal mucosa opened; posterior then anterior flaps sutured; '
     'silicone tube often inserted (removed at 3–6 months)'],
    ['Endoscopic (Endonasal) DCR',
     'No skin incision; comparable success rates; preferred for functional failures or redo cases'],
]
story.append(make_table(['Procedure', 'Key Steps / Notes'], dcr_rows, [5.5*cm, 12.5*cm]))

story.append(sp(6))
story.append(sub_header('E. Dacryocystitis'))
for t in [
    '<b>Acute</b>: Systemic antibiotics (IV amoxicillin-clavulanate or cephalosporins in severe cases); warm compresses; DCR after infection resolves',
    '<b>Chronic</b>: DCR is the definitive treatment',
    '<b>Mucocele</b>: Pressure test confirms; DCR required',
]:
    story.append(bullet(t))

story.append(sp(6))
story.append(sub_header('F. Lacrimal Pump Failure (Facial Nerve Palsy)'))
for t in [
    'Treat underlying cause',
    'Lubricating eye drops / ointment to protect cornea',
    'Lateral tarsorrhaphy or lower lid tightening if lid laxity contributes',
]:
    story.append(bullet(t))

# ═══════════════════════════════════════════════════════════════════════════════
# 8. MNEMONIC
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('MNEMONIC — Causes of Epiphora', 8))
story.append(sp(6))

mnem_rows = [
    ['P', 'Punctal stenosis / malposition (Ectropion)'],
    ['E', 'Evaporation / Dry Eye (paradoxical hypersecretion) — COMMONEST'],
    ['C', 'Canalicular obstruction (trauma, Canaliculitis — Actinomyces)'],
    ['K', 'Keratoconjunctivitis / ocular surface disease'],
    ['D', 'Dacryocystitis / NLD obstruction (PANDO)'],
    ['F', 'Facial nerve palsy (pump failure)'],
    ['L', 'Lid laxity / conjunctivochalasis'],
]
style_mnem = TableStyle([
    ('BACKGROUND',    (0,0), (0,-1), MID_BLUE),
    ('TEXTCOLOR',     (0,0), (0,-1), WHITE),
    ('FONTNAME',      (0,0), (0,-1), 'Helvetica-Bold'),
    ('FONTSIZE',      (0,0), (-1,-1), 11),
    ('FONTNAME',      (1,0), (1,-1), 'Helvetica'),
    ('TEXTCOLOR',     (1,0), (1,-1), GREY_TEXT),
    ('FONTSIZE',      (1,0), (1,-1), 9.5),
    ('TOPPADDING',    (0,0), (-1,-1), 7),
    ('BOTTOMPADDING', (0,0), (-1,-1), 7),
    ('LEFTPADDING',   (0,0), (-1,-1), 12),
    ('RIGHTPADDING',  (0,0), (-1,-1), 12),
    ('GRID',          (0,0), (-1,-1), 0.4, BORDER),
])
for i in range(len(mnem_rows)):
    bg = TABLE_ALT if i % 2 == 0 else WHITE
    style_mnem.add('BACKGROUND', (1,i), (1,i), bg)

mnem_cell = ParagraphStyle('mc', fontSize=9.5, textColor=GREY_TEXT,
                            fontName='Helvetica', leading=13)
mnem_letter = ParagraphStyle('ml', fontSize=14, textColor=WHITE,
                              fontName='Helvetica-Bold', leading=18,
                              alignment=TA_CENTER)
mnem_data = [[Paragraph(r[0], mnem_letter),
              Paragraph(r[1], mnem_cell)] for r in mnem_rows]
mnem_tbl = Table(mnem_data, colWidths=[2*cm, 16*cm])
mnem_tbl.setStyle(style_mnem)
story.append(mnem_tbl)

# ═══════════════════════════════════════════════════════════════════════════════
# 9. KEY EXAM POINTS
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('KEY EXAM POINTS', 9))
story.append(sp(6))

key_rows = [
    ['Commonest cause of watering eye',              'Paradoxical dry eye (hypersecretion)'],
    ['Commonest acquired NLD obstruction (adults)',  'PANDO (Primary Acquired NLD Obstruction)'],
    ['Congenital NLD obstruction — spontaneous resolution', '~90% by age 1 year'],
    ['Site of congenital obstruction',               'Valve of Hasner (lower end of NLD)'],
    ['First-line Rx — congenital NLD obstruction',  'Crigler massage'],
    ['Definitive Rx — congenital NLD obstruction',  'Probing (under GA at 12–18 months)'],
    ['Definitive Rx — acquired NLD obstruction / PANDO', 'Dacryocystorhinostomy (DCR)'],
    ['Jones II positive (fluorescein-stained saline from nose)', 'Partial NLD obstruction — fluorescein entered sac'],
    ['Jones II negative (unstained saline from nose)', 'Upper lacrimal block (punctal/canalicular) or pump failure'],
    ['Hard stop on syringing',                       'Canalicular system patent to sac level'],
    ['Soft stop on syringing',                       'Canalicular obstruction'],
    ['Causative organism — canaliculitis',           'Actinomyces israelii (Gram-positive anaerobe)'],
    ['NLD drains into',                              'Inferior nasal meatus (below inferior turbinate)'],
    ['DCR — anastomosis between',                   'Lacrimal sac and nasal mucosa'],
    ['Congenital dacryocoele ≠',                    'Encephalocoele (pulsatile, ABOVE medial canthal ligament)'],
    ['Normal marginal tear strip',                   '0.2–0.4 mm; ≥0.6 mm = abnormal (epiphora)'],
    ['DCR success rate',                             '~90%'],
]
story.append(make_table(['Point', 'Answer'], key_rows, [8*cm, 10*cm]))

# ═══════════════════════════════════════════════════════════════════════════════
# Footer / Sources
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(14))
story.append(hr())
story.append(Paragraph(
    '<b>Sources:</b> Kanski\'s Clinical Ophthalmology: A Systematic Approach, 10th Ed. (ISBN 9780443110993) '
    '| K.J. Lee\'s Essential Otolaryngology | Goldman-Cecil Medicine',
    source_style))

# ── BUILD PDF ─────────────────────────────────────────────────────────────────
doc.build(story)
print(f'PDF created: {OUTPUT}')
Running Command

cd /home/daytona/workspace/epiphora-pdf && python generate_pdf.py

Running Command

ls -lh /home/daytona/workspace/epiphora-pdf/Epiphora_PG_Exam_Notes.pdf

Your PDF is ready — 11 MB, fully formatted with images, tables, and colour-coded sections.

Epiphora PG Exam Notes

PDF Document · PDF

What's included in the PDF:
  • Dark blue section banners for each heading (Definition → Key Exam Points)
  • Lacrimal anatomy diagram + clinical photo of epiphora/dacryocoele
  • Colour-coded tables for anatomy, causes by level, investigations, DCR types, and key exam points
  • PECK DFL mnemonic in a styled table
  • Jones test interpretation, syringing findings, and full management algorithm
  • Source citations at the footer
This is a shared conversation. Sign in to Orris to start your own chat.