~/antihypertensive-table/generate_pdf.py
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import KeepTogether
OUTPUT = "/home/daytona/workspace/antihypertensive-table/Antihypertensive_Drugs_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=landscape(A4),
leftMargin=1*cm, rightMargin=1*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
"Title", parent=styles["Title"],
fontSize=16, textColor=colors.HexColor("#1a3c5e"),
spaceAfter=4, alignment=TA_CENTER, fontName="Helvetica-Bold"
)
subtitle_style = ParagraphStyle(
"Sub", parent=styles["Normal"],
fontSize=9, textColor=colors.HexColor("#5a6a7a"),
spaceAfter=8, alignment=TA_CENTER
)
cell_style = ParagraphStyle(
"Cell", parent=styles["Normal"],
fontSize=7.5, leading=10, fontName="Helvetica"
)
cell_bold = ParagraphStyle(
"CellBold", parent=styles["Normal"],
fontSize=8, leading=10, fontName="Helvetica-Bold"
)
cell_small = ParagraphStyle(
"CellSmall", parent=styles["Normal"],
fontSize=7, leading=9.5, fontName="Helvetica"
)
header_style = ParagraphStyle(
"Header", parent=styles["Normal"],
fontSize=8.5, fontName="Helvetica-Bold",
textColor=colors.white, alignment=TA_CENTER, leading=11
)
legend_style = ParagraphStyle(
"Legend", parent=styles["Normal"],
fontSize=7, fontName="Helvetica", textColor=colors.HexColor("#444444")
)
# Colour palette
COL_HEADER = colors.HexColor("#1a3c5e")
COL_CLASS_1 = colors.HexColor("#d6e8f7") # Diuretics
COL_CLASS_2 = colors.HexColor("#d4edda") # ACEi
COL_CLASS_3 = colors.HexColor("#cce5ff") # ARB
COL_CLASS_4 = colors.HexColor("#fff3cd") # CCB-DHP
COL_CLASS_5 = colors.HexColor("#fde8d8") # CCB non-DHP
COL_CLASS_6 = colors.HexColor("#e8d5f5") # Beta-blockers
COL_CLASS_7 = colors.HexColor("#fce4ec") # Loop diuretics
COL_CLASS_8 = colors.HexColor("#e0f2f1") # MRA
COL_CLASS_9 = colors.HexColor("#f3e5f5") # Alpha blockers
COL_CLASS_10 = colors.HexColor("#fff9c4") # Central agents
COL_CLASS_11 = colors.HexColor("#ffccbc") # Direct vasodilators
COL_ALT = colors.HexColor("#f8f8f8")
WHITE = colors.white
def p(text, style=None):
if style is None:
style = cell_style
return Paragraph(text, style)
def ph(text):
return Paragraph(text, header_style)
# ── TABLE DATA ──────────────────────────────────────────────────────────────
col_headers = [
ph("Drug Class"),
ph("Examples"),
ph("Dose Range\n(mg/day)"),
ph("Frequency"),
ph("Mechanism\nof Action"),
ph("SBP ↓ vs\nPlacebo"),
ph("Side Effects"),
ph("Compelling\nIndications"),
ph("Contraindications /\nCautions"),
]
data = [col_headers]
rows = [
# (row_colour, drug_class, examples, dose, freq, mech, sbp, side_fx, indications, contra)
(COL_CLASS_1,
"Thiazide /\nThiazide-like\nDiuretics\n(First-line)",
"Chlorthalidone\nHydrochlorothiazide\nIndapamide",
"12.5–25\n12.5–50\n1.25–2.5",
"OD\nOD\nOD",
"Inhibit Na reabsorption\nin distal convoluted\ntubule → ↓plasma vol",
"~10 mmHg",
"Hypokalemia\nHyponatremia\nHyperuricemia\nHyperglycemia\nHyperlipidemia",
"Elderly / ISH\nBlack patients\nHF prevention\nOsteoporosis",
"Gout (relative)\nHypokalaemia\nPregnancy"),
(COL_CLASS_2,
"ACE Inhibitors\n(ACEi)\n(First-line)",
"Lisinopril\nRamipril\nEnalapril\nPerindopril",
"10–40\n2.5–10\n5–40\n4–8",
"OD\nOD/BD\nOD/BD\nOD",
"Inhibit ACE → ↓Ang II\n→ vasodilation\n↓aldosterone",
"~8 mmHg",
"Dry cough (10–15%)\nHyperkalaemia\nAngioedema (rare)\nAcute kidney injury\n(bilateral RAS)",
"Diabetes + CKD\nProteinuria\nHFrEF\nPost-MI\nLV dysfunction",
"Pregnancy (CI)\nBilateral RAS\nHistory of angioedema\nHyperkalaemia"),
(COL_CLASS_3,
"Angiotensin\nReceptor\nBlockers (ARB)\n(First-line)",
"Losartan\nValsartan\nCandesartan\nTelmisartan\nIrbesartan",
"50–100\n80–320\n8–32\n20–80\n150–300",
"OD\nOD\nOD\nOD\nOD",
"Block AT1 receptor\nfor Ang II\n→ vasodilation",
"~8 mmHg",
"Hyperkalaemia\nAcute kidney injury\n(No cough — advantage\nover ACEi)",
"Same as ACEi\nACEi-intolerant\n(cough)",
"Pregnancy (CI)\nBilateral RAS\nDo NOT combine\nwith ACEi"),
(COL_CLASS_4,
"CCB –\nDihydropyridine\n(DHP)\n(First-line)",
"Amlodipine\nNifedipine LA\nFelodipine\nLevamlodipine",
"2.5–10\n30–90\n2.5–10\n2.5–5",
"OD\nOD\nOD\nOD",
"Block L-type Ca²⁺\nchannels → arterial\nvasodilation",
"~10 mmHg",
"Ankle oedema\nFlushing / headache\nReflex tachycardia\nGingival hyperplasia",
"Elderly / ISH\nAngina\nStroke prevention\nRaynaud's",
"Severe aortic\nstenosis\n(relative)\nHF with oedema\n(relative)"),
(COL_CLASS_5,
"CCB –\nNon-DHP\n(Rate-limiting)",
"Diltiazem ER\nVerapamil ER",
"120–540\n120–480",
"OD\nOD/BD",
"Block L-type Ca²⁺\nin heart + vessels\n→ ↓HR + vasodilation",
"~8 mmHg",
"Bradycardia\nAV block\nConstipation\n(verapamil)",
"AF rate control\nAngina\nSVT",
"HFrEF (CI)\nBradycardia\nAV block 2°/3°\nCombine with\nbeta-blocker (caution)"),
(COL_CLASS_6,
"Beta-Blockers\n(Second-line\nfor HTN)",
"Metoprolol succinate\nBisoprolol\nAtenolol\nCarvedilol\nNebivolol",
"25–200\n2.5–10\n25–100\n6.25–50\n5–40",
"OD\nOD\nOD\nBD\nOD",
"Block β1-adrenergic\nreceptors → ↓HR\n+ ↓cardiac output",
"~9 mmHg",
"Fatigue\nCold extremities\nErecile dysfunction\nBronchoconstriction\nMask hypoglycaemia",
"Post-MI\nHFrEF\nAngina\nAF rate control\nHyperthyroidism",
"Asthma / COPD (CI)\nAV block 2°/3° (CI)\nDecompensated HF\nPeripheral vascular\ndisease"),
(COL_CLASS_7,
"Loop Diuretics\n(Second-line)",
"Furosemide\nTorsemide\nBumetanide",
"20–80\n5–10\n0.5–2",
"BD\nOD\nOD/BD",
"Inhibit Na reabsorption\nin loop of Henle\n→ potent diuresis",
"N/A\n(variable)",
"Hypokalaemia\nVolume depletion\nHyperuricaemia\nOtotoxicity (high dose)",
"CKD GFR <30\nSymptomatic HF\nWith minoxidil\nPulmonary oedema",
"Hypokalaemia\nHypovolaemia"),
(COL_CLASS_8,
"Mineralo-\ncorticoid\nReceptor\nAntagonists\n(MRA)",
"Spironolactone\nEplerenone\nFinerenone",
"25–100\n50–100\n10–20",
"OD\nOD\nOD",
"Block aldosterone\nreceptors in collecting\nduct → K-sparing\ndiuresis",
"~8 mmHg\n(resistant HTN)",
"Hyperkalaemia\nGynecomastia\n(spironolactone)\nRenal impairment",
"Resistant HTN\n(4th drug)\nPrimary\naldosteronism\nHFrEF",
"Hyperkalaemia (CI)\nSevere CKD (CI)\nWith ACEi+ARB\n(triple blockade)"),
(COL_CLASS_9,
"Alpha-1\nBlockers",
"Doxazosin\nPrazosin\nTerazosin",
"1–16\n1–20\n1–20",
"OD\nBD/TDS\nOD",
"Block α1-adrenergic\nreceptors → peripheral\nvasodilation",
"~5 mmHg",
"Orthostatic\nhypotension\n(especially elderly)\nFirst-dose syncope",
"HTN + BPH (men)\nResistant HTN\nPhaeochromocytoma",
"Heart failure\n(less cardio-\nprotective)\nElderly (fall risk)"),
(COL_CLASS_10,
"Central\nAlpha-2\nAgonists",
"Clonidine\nClonidine patch\nMethyldopa\nMoxonidine",
"0.1–0.8\n0.1–0.3\n250–1000\n0.2–0.6",
"BD\nWeekly\nBD/TDS\nOD/BD",
"Stimulate central\nα2-adrenergic\nreceptors → ↓\nsympathetic outflow",
"N/A\n(variable)",
"Sedation\nDry mouth\nBradycardia\nRebound HTN\n(clonidine withdrawal)",
"Methyldopa:\nPregnancy\nClonidine:\nResistant HTN",
"Avoid abrupt\nwithdrawal of\nclonidine\n(rebound HTN)"),
(COL_CLASS_11,
"Direct\nVasodilators\n(Reserved for\nresistant HTN)",
"Hydralazine\nMinoxidil",
"50–200\n5–100",
"BD/TDS\nOD–TDS",
"Directly dilate\nperipheral arterioles\n→ ↓SVR",
"N/A\n(potent)",
"Reflex tachycardia\nFluid retention\nLupus-like syndrome\n(hydralazine)\nHypertrichosis\n(minoxidil)",
"4th/5th step:\nResistant HTN\nHydralazine:\nPregnancy HTN",
"Must combine with\ndiuretic + beta-blocker\nto counter reflex\ntachycardia + oedema"),
]
for row_colour, drug_class, examples, dose, freq, mech, sbp, side_fx, indications, contra in rows:
data.append([
p(drug_class, cell_bold),
p(examples, cell_style),
p(dose, cell_small),
p(freq, cell_small),
p(mech, cell_small),
p(sbp, cell_bold),
p(side_fx, cell_small),
p(indications, cell_small),
p(contra, cell_small),
])
# Column widths (landscape A4 = ~277mm usable)
col_widths = [
2.6*cm, # Class
3.2*cm, # Examples
2.1*cm, # Dose
1.6*cm, # Freq
4.0*cm, # Mechanism
1.8*cm, # SBP
3.8*cm, # Side effects
3.4*cm, # Indications
3.4*cm, # Contra
]
table = Table(data, colWidths=col_widths, repeatRows=1)
# Build per-row colour style commands
row_styles = []
# Header row
row_styles += [
("BACKGROUND", (0, 0), (-1, 0), COL_HEADER),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 8.5),
("ROWBACKGROUNDS", (0, 0), (-1, 0), [COL_HEADER]),
]
colours = [
COL_CLASS_1, COL_CLASS_2, COL_CLASS_3, COL_CLASS_4,
COL_CLASS_5, COL_CLASS_6, COL_CLASS_7, COL_CLASS_8,
COL_CLASS_9, COL_CLASS_10, COL_CLASS_11,
]
for i, clr in enumerate(colours):
row_styles.append(("BACKGROUND", (0, i+1), (-1, i+1), clr))
# General table style
table_style = TableStyle([
# Grid
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#aaaaaa")),
("LINEBELOW", (0, 0), (-1, 0), 1.2, colors.white),
# Alignment
("VALIGN", (0, 0), (-1, -1), "TOP"),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("ALIGN", (2, 1), (3, -1), "CENTER"),
("ALIGN", (5, 1), (5, -1), "CENTER"),
# Padding
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING",(0,0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("RIGHTPADDING",(0, 0), (-1, -1), 4),
# Header height
("ROWHEIGHT", (0, 0), (-1, 0), 22),
] + row_styles)
table.setStyle(table_style)
# ── COMPELLING INDICATIONS MINI TABLE ───────────────────────────────────────
ci_header_style = ParagraphStyle(
"CIH", parent=styles["Normal"],
fontSize=8, fontName="Helvetica-Bold",
textColor=colors.white, alignment=TA_CENTER
)
ci_cell = ParagraphStyle(
"CIC", parent=styles["Normal"],
fontSize=7.5, fontName="Helvetica", leading=10
)
ci_title = p("QUICK GUIDE: Drug of Choice by Condition", ParagraphStyle(
"CIT", parent=styles["Normal"],
fontSize=9.5, fontName="Helvetica-Bold",
textColor=colors.HexColor("#1a3c5e"), spaceAfter=3
))
ci_data = [
[p("Condition", ci_header_style), p("Preferred Drug(s)", ci_header_style), p("Avoid / Caution", ci_header_style)],
[p("Diabetes + CKD + Proteinuria", ci_cell), p("ACEi or ARB (first choice)", ci_cell), p("ARB + ACEi together", ci_cell)],
[p("Heart failure (HFrEF)", ci_cell), p("ACEi/ARB + BB + Thiazide + MRA", ci_cell), p("Non-DHP CCB, Diltiazem/Verapamil", ci_cell)],
[p("Post-MI", ci_cell), p("Beta-blocker + ACEi", ci_cell), p("—", ci_cell)],
[p("Isolated systolic HTN / Elderly", ci_cell), p("Chlorthalidone or Amlodipine", ci_cell), p("High-dose diuretics (fall risk)", ci_cell)],
[p("Angina pectoris", ci_cell), p("Beta-blocker or CCB", ci_cell), p("—", ci_cell)],
[p("Atrial fibrillation (rate control)", ci_cell), p("Diltiazem / Verapamil / BB", ci_cell), p("Verapamil + BB combination", ci_cell)],
[p("Stroke prevention", ci_cell), p("ACEi (Perindopril) + Thiazide (Indapamide)", ci_cell), p("—", ci_cell)],
[p("Black patients (uncomplicated)", ci_cell), p("Thiazide or CCB (ACEi/ARB less effective alone)", ci_cell), p("ACEi/ARB monotherapy", ci_cell)],
[p("Pregnancy", ci_cell), p("Methyldopa, Labetalol, Nifedipine", ci_cell), p("ACEi, ARB (teratogenic — CI)", ci_cell)],
[p("Primary aldosteronism", ci_cell), p("Spironolactone (MRA)", ci_cell), p("—", ci_cell)],
[p("BPH + HTN (men)", ci_cell), p("Doxazosin (alpha-blocker)", ci_cell), p("—", ci_cell)],
[p("Resistant HTN (add 4th drug)", ci_cell), p("Spironolactone 25–50 mg", ci_cell), p("Hyperkalaemia risk", ci_cell)],
[p("Raynaud's phenomenon", ci_cell), p("Amlodipine (DHP-CCB)", ci_cell), p("Beta-blockers worsen it", ci_cell)],
[p("Phaeochromocytoma", ci_cell), p("Alpha-blocker first, then BB", ci_cell), p("BB alone before alpha (CI — hypertensive crisis)", ci_cell)],
]
ci_col_widths = [6.5*cm, 7.0*cm, 7.0*cm]
ci_table = Table(ci_data, colWidths=ci_col_widths)
ci_row_styles = []
for i in range(1, len(ci_data)):
clr = COL_ALT if i % 2 == 0 else WHITE
ci_row_styles.append(("BACKGROUND", (0, i), (-1, i), clr))
ci_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#2e6da4")),
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#bbbbbb")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING",(0, 0), (-1, -1), 5),
] + ci_row_styles))
# ── ASSEMBLE DOCUMENT ────────────────────────────────────────────────────────
elements = [
Paragraph("Antihypertensive Drugs — Quick Reference Table", title_style),
Paragraph("Based on Harrison's Principles of Internal Medicine 22E (2025) · Goldman-Cecil Medicine · ESH/ACC/AHA Guidelines", subtitle_style),
HRFlowable(width="100%", thickness=1.5, color=colors.HexColor("#1a3c5e"), spaceAfter=6),
table,
Spacer(1, 0.5*cm),
ci_title,
ci_table,
Spacer(1, 0.3*cm),
Paragraph(
"<b>Abbreviations:</b> OD = once daily · BD = twice daily · TDS = three times daily · "
"ACEi = ACE inhibitor · ARB = angiotensin receptor blocker · CCB = calcium channel blocker · "
"DHP = dihydropyridine · MRA = mineralocorticoid receptor antagonist · HFrEF = heart failure with reduced ejection fraction · "
"CKD = chronic kidney disease · ISH = isolated systolic hypertension · SVR = systemic vascular resistance · "
"RAS = renal artery stenosis · CI = contraindicated · BPH = benign prostatic hyperplasia",
legend_style
),
]
doc.build(elements)
print(f"PDF created: {OUTPUT}")