~/suture-refcard/build_card.py
#!/usr/bin/env python3
"""
Suture Quick-Reference Card — PDF generator
Two A4 pages, landscape, colour-coded, bedside-ready
"""
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph,
Spacer, HRFlowable, KeepTogether
)
from reportlab.platypus import PageBreak
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = colors.HexColor("#0D3B66")
TEAL = colors.HexColor("#0A7E8C")
AMBER = colors.HexColor("#E07B00")
RED = colors.HexColor("#C0392B")
GREEN = colors.HexColor("#1A7A4A")
LGRAY = colors.HexColor("#F4F6F8")
MGRAY = colors.HexColor("#BDC3C7")
WHITE = colors.white
BLACK = colors.black
LBLUE = colors.HexColor("#D6EAF8")
LGREEN = colors.HexColor("#D5F5E3")
LORANGE = colors.HexColor("#FEF9E7")
LRED = colors.HexColor("#FADBD8")
W, H = landscape(A4) # 297 x 210 mm → 841 x 595 pt
MARGIN = 10 * mm
# ── Style helpers ────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def sty(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=base[parent], **kw)
HDR1 = sty("hdr1", fontSize=14, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=16)
HDR2 = sty("hdr2", fontSize=9, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11)
CELL = sty("cell", fontSize=7.5, fontName="Helvetica", leading=9,
alignment=TA_LEFT, textColor=BLACK)
CELLB = sty("cellb", fontSize=7.5, fontName="Helvetica-Bold", leading=9,
alignment=TA_LEFT, textColor=NAVY)
CELLIC= sty("cellic", fontSize=7, fontName="Helvetica-Oblique", leading=9,
alignment=TA_LEFT, textColor=colors.HexColor("#555555"))
WARN = sty("warn", fontSize=7.5, fontName="Helvetica-Bold", leading=9,
alignment=TA_LEFT, textColor=RED)
FOOT = sty("foot", fontSize=6, fontName="Helvetica-Oblique",
alignment=TA_CENTER, textColor=colors.HexColor("#777777"), leading=8)
SECTITLE = sty("sectitle", fontSize=8.5, fontName="Helvetica-Bold",
textColor=NAVY, leading=10)
def P(txt, style=CELL): return Paragraph(txt, style)
def PB(txt): return P(txt, CELLB)
def PI(txt): return P(txt, CELLIC)
def PW(txt): return P(txt, WARN)
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 1 – SELECTION, MATERIAL, NEEDLE, KNOTS
# ══════════════════════════════════════════════════════════════════════════════
# ── Section 1: Technique by clinical scenario ────────────────────────────────
SCEN_DATA = [
[PB("Clinical Scenario"), PB("Technique"), PB("Material (Deep)"), PB("Material (Skin)"), PB("Size (Skin)")],
[P("Simple face laceration"), P("Subcuticular or\nSimple interrupted"), P("Buried dermal\n(Monocryl/Vicryl)"), P("Nylon / Prolene"), P("5-0 or 6-0")],
[P("Scalp laceration"), P("Simple interrupted\n(layered)"), P("Vicryl"), P("Nylon"), P("3-0 / 4-0")],
[P("Trunk / back"), P("Layered closure"), P("Vicryl 3-0"), P("Nylon / Prolene"), P("3-0 / 4-0")],
[P("Extremity laceration"), P("Simple interrupted"), P("Vicryl 3-0"), P("Nylon"), P("3-0 / 4-0")],
[P("Volar hand / fingers\n(swelling expected)"), P("Horizontal mattress"), P("—"), P("Nylon"), P("4-0")],
[P("High-tension wound"), P("Buried dermal +\nSimple interrupted"), P("Vicryl / Monocryl 3-0"), P("Nylon / Prolene"), P("3-0 / 4-0")],
[P("Flap / triangular corner"), P("Half-buried (corner)\nsuture"), P("—"), P("Nylon"), P("5-0")],
[P("Maximum eversion needed"), P("Vertical mattress"), P("—"), P("Nylon"), P("4-0")],
[P("Elective excision (cosmetic)"), P("Layered: buried +\nSubcuticular"), P("Vicryl 4-0"), P("Monocryl (subcuticular)"), P("4-0")],
[P("Long abdominal fascia"), P("Continuous small-bite\n(SL:WL = 4:1)"), P("PDS / Maxon 0 or 1-0"), P("Nylon (skin)"), P("3-0")],
]
scen_col = [55*mm, 45*mm, 48*mm, 42*mm, 22*mm]
SCEN_TABLE = Table(SCEN_DATA, colWidths=scen_col, repeatRows=1)
SCEN_TABLE.setStyle(TableStyle([
# Header row
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
# Zebra
*[("BACKGROUND", (0,i), (-1,i), LGRAY if i % 2 == 0 else WHITE)
for i in range(1, len(SCEN_DATA))],
("BACKGROUND", (0,1), (-1,1), LGRAY),
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
]))
# ── Section 2: Suture material properties ───────────────────────────────────
MAT_DATA = [
[PB("Suture"), PB("Type"), PB("Absorbable"), PB("Strength\nHalf-life"), PB("Knot\nSecurity"), PB("Tissue\nReaction"), PB("Best Use")],
[P("Vicryl\n(polyglactin)"), P("Braided"), P("Yes"), P("~3-4 wk"), P("Good"), P("Low"), P("Dermis, subcut")],
[P("Monocryl\n(poliglecaprone 25)"),P("Mono"), P("Yes"), P("~1-2 wk"), P("Fair"), P("Very low"), P("Subcuticular")],
[P("PDS\n(polydioxanone)"), P("Mono"), P("Yes"), P("~6 wk"), P("Fair"), P("Low"), P("Fascia, tendon")],
[P("Maxon\n(polyglyconate)"), P("Mono"), P("Yes"), P("~6 wk"), P("Fair"), P("Low"), P("Fascia")],
[P("Plain catgut"), P("Twisted"), P("Yes"), P("~3-5 days"), P("Poor"), P("High"), P("Mucosa only")],
[P("Chromic catgut"), P("Twisted"), P("Yes"), P("~10-14 days"),P("Poor"), P("High"), P("Avoid if possible")],
[P("Nylon\n(ethilon)"), P("Mono"), P("No"), P("Permanent*"), P("Poor\n(4 throws)"), P("Low"), P("Skin (all sites)")],
[P("Prolene\n(polypropylene)"), P("Mono"), P("No"), P("Permanent"), P("Very poor\n(4+ throws)"), P("Lowest"), P("Skin, vessels")],
[P("Ethibond\n(polyester)"), P("Braided"), P("No"), P("Permanent"), P("Very good"), P("Low"), P("Tendons, fascia")],
[P("Silk"), P("Braided"), P("No"), P("Degrades\n~1 yr"), P("Excellent"), P("High"), P("Ties, mucosa\n(avoid buried)")],
]
mat_col = [34*mm, 20*mm, 22*mm, 22*mm, 24*mm, 22*mm, 34*mm]
MAT_TABLE = Table(MAT_DATA, colWidths=mat_col, repeatRows=1)
MAT_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), 7),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LGRAY if i % 2 == 0 else WHITE)
for i in range(1, len(MAT_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ── Section 3: Size guide ────────────────────────────────────────────────────
SIZE_DATA = [
[PB("Location"), PB("Deep"), PB("Skin")],
[P("Eyelid"), P("6-0 Vicryl"), P("6-0 nylon")],
[P("Face"), P("4-0 Vicryl"), P("5-0 / 6-0 nylon")],
[P("Ear / Nose"), P("4-0 Vicryl"), P("5-0 nylon")],
[P("Neck"), P("4-0 Vicryl"), P("4-0 / 5-0 nylon")],
[P("Scalp"), P("3-0 Vicryl"), P("3-0 / 4-0 nylon")],
[P("Trunk"), P("3-0 Vicryl"), P("3-0 / 4-0 nylon")],
[P("Extremity"), P("3-0 Vicryl"), P("3-0 / 4-0 nylon")],
[P("Hand (palm)"), P("4-0 Vicryl"), P("4-0 nylon")],
[P("Foot / sole"), P("3-0 Vicryl"), P("3-0 nylon")],
[P("Fascia"), P("0 / 1-0 PDS"), P("3-0 nylon")],
]
size_col = [32*mm, 32*mm, 34*mm]
SIZE_TABLE = Table(SIZE_DATA, colWidths=size_col, repeatRows=1)
SIZE_TABLE.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), AMBER),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LGRAY if i % 2 == 0 else WHITE)
for i in range(1, len(SIZE_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ── Section 4: Knot throws ───────────────────────────────────────────────────
KNOT_DATA = [
[PB("Material"), PB("Min. Throws"), PB("Tail length")],
[P("Monofilament"), P("4 square"), P("3-4 mm")],
[P("Braided / Vicryl"), P("3 square"), P("3-4 mm")],
[P("Nylon"), P("3 square"), P("3-4 mm")],
[P("Prolene"), P("5-6 square"), P("4-5 mm")],
[P("Cut on knot\n(no tails)"), P("+1 extra throw"), P("—")],
]
knot_col = [34*mm, 28*mm, 28*mm]
KNOT_TABLE = Table(KNOT_DATA, colWidths=knot_col, repeatRows=1)
KNOT_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), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LGREEN if i % 2 == 0 else WHITE)
for i in range(1, len(KNOT_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 2 – TIMING, INDICATIONS, CONTRAINDICATIONS, COMPLICATIONS
# ══════════════════════════════════════════════════════════════════════════════
# ── Section 5: Suture removal timing ────────────────────────────────────────
TIMING_DATA = [
[PB("Anatomic Site"), PB("Removal Timing"), PB("Key Reasoning")],
[P("Eyelids"), P("5-7 days"), P("Excellent vascularity; early removal avoids track marks")],
[P("Face"), P("5-7 days"), P("Rich blood supply; cosmetic priority")],
[P("Ear"), P("5-7 days"), P("Limited tissue; early eversion critical")],
[P("Neck"), P("7 days"), P("Low tension; good healing")],
[P("Scalp"), P("7-14 days"), P("Moderate tension; higher dehiscence risk")],
[P("Trunk / back"), P("10-14 days"), P("Higher static tension")],
[P("Upper extremity"), P("10-14 days"), P("Dynamic tension with movement")],
[P("Lower extremity"), P("10-14 days"), P("Poor vascularity compared to face")],
[P("Lower leg"), P("Up to 21 days"), P("Worst vascularity; highest dehiscence risk")],
[P("Over joints"), P("14+ days"), P("Dynamic tension; premature removal risks dehiscence")],
[P("Palms / soles"), P("14-21 days"), P("High mechanical stress; thicker skin")],
]
timing_col = [40*mm, 30*mm, 95*mm]
TIMING_TABLE = Table(TIMING_DATA, colWidths=timing_col, repeatRows=1)
TIMING_TABLE.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LBLUE if i % 2 == 0 else WHITE)
for i in range(1, len(TIMING_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ── Section 6: Indications ───────────────────────────────────────────────────
IND_DATA = [
[PB("Indication"), PB("Reason")],
[P("Clean wound < 6 hrs old"), P("Bacterial load still manageable; safe primary closure")],
[P("Wound under tension"), P("Prevents dehiscence and wide scar formation")],
[P("Cosmetically sensitive area"), P("Precise edge approximation reduces scarring")],
[P("Deep wound with dead space"), P("Dead space harbors seroma/hematoma and infection")],
[P("Active bleeding from laceration"), P("Sutures provide mechanical hemostasis")],
[P("Wound over a joint"), P("Dynamic stress requires secure closure")],
[P("Wound with visible deep structures"), P("Must cover tendons/vessels to prevent desiccation")],
]
ind_col = [75*mm, 100*mm]
IND_TABLE = Table(IND_DATA, colWidths=ind_col, repeatRows=1)
IND_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), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LGREEN if i % 2 == 0 else WHITE)
for i in range(1, len(IND_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ── Section 7: Contraindications ────────────────────────────────────────────
CON_DATA = [
[PB("Contraindication"), PB("Severity"), PB("Reason")],
[P("Frank wound infection / abscess"), PW("ABSOLUTE"), P("Traps bacteria; accelerates spread")],
[P("Heavily contaminated wound\n(soil, feces, devitalized tissue)"), PW("ABSOLUTE"), P("Anaerobic environment created; debride first")],
[P("Human / animal bite (most sites)"), PW("ABSOLUTE"), P("High infection risk (Pasteurella, Eikenella)\nException: face bites may close primarily")],
[P("Puncture wounds"), PW("ABSOLUTE"), P("Cannot irrigate adequately; anaerobes trapped")],
[P("Wound > 12-24 hrs old (most sites)"), PW("ABSOLUTE"), P("Bacterial colonization irreversible")],
[P("Immunocompromised patient"), P("Relative"), P("Consider delayed primary closure")],
[P("Poorly controlled diabetes"), P("Relative"), P("Impaired healing and neutrophil function")],
[P("Keloid-prone patient"), P("Relative"), P("Minimize sutures; prefer tapes/adhesives")],
[P("Anticoagulated patient"), P("Relative"), P("Higher hematoma risk; ensure hemostasis first")],
]
con_col = [72*mm, 25*mm, 78*mm]
CON_TABLE = Table(CON_DATA, colWidths=con_col, repeatRows=1)
CON_TABLE.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LRED if i % 2 == 0 else WHITE)
for i in range(1, len(CON_DATA))],
("TEXTCOLOR", (1,1), (1,5), RED),
("FONTNAME", (1,1), (1,5), "Helvetica-Bold"),
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ── Section 8: Complications ─────────────────────────────────────────────────
COMP_DATA = [
[PB("Complication"), PB("Timing"), PB("Prevention")],
[P("Wound infection"), P("2-5 days"), P("Irrigate, debride, aseptic technique")],
[P("Hematoma"), P("0-24 hrs"), P("Hemostasis before closure, pressure dressing")],
[P("Wound dehiscence"), P("Days-weeks"), P("Layered closure; correct suture selection")],
[P("Suture track marks"), P("> 7-10 days skin"), P("Remove on time; correct tension at tying")],
[P("Keloid / hypertrophic"), P("Weeks-months"), P("Minimize tension; silicone sheets post-removal")],
[P("Suture sinus"), P("Weeks-months"), P("Use absorbable sutures deep; bury knots properly")],
[P("Contact dermatitis"), P("3-5 days"), P("Use petrolatum, not antibiotic ointment")],
]
comp_col = [47*mm, 33*mm, 95*mm]
COMP_TABLE = Table(COMP_DATA, colWidths=comp_col, repeatRows=1)
COMP_TABLE.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#5D4037")),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
*[("BACKGROUND", (0,i), (-1,i), LORANGE if i % 2 == 0 else WHITE)
for i in range(1, len(COMP_DATA))],
("GRID", (0,0), (-1,-1), 0.3, MGRAY),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING",(0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
# ══════════════════════════════════════════════════════════════════════════════
# LAYOUT HELPERS
# ══════════════════════════════════════════════════════════════════════════════
def section_header(text, color):
"""Coloured bar acting as a section header."""
t = Table([[Paragraph(text, HDR2)]], colWidths=[W - 2*MARGIN])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
return t
def page_title(text):
t = Table([[Paragraph(text, HDR1)]], colWidths=[W - 2*MARGIN])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
]))
return t
def two_col(left, right, lw=None, rw=None):
"""Side-by-side layout for two sub-tables."""
lw = lw or (W - 2*MARGIN) * 0.56
rw = rw or (W - 2*MARGIN) * 0.42
wrapper = Table([[left, right]], colWidths=[lw, rw])
wrapper.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
return wrapper
def three_col(a, b, c, aw, bw, cw):
wrapper = Table([[a, b, c]], colWidths=[aw, bw, cw])
wrapper.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 2),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
return wrapper
GAP = Spacer(1, 2*mm)
# ══════════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ══════════════════════════════════════════════════════════════════════════════
OUT = "/home/daytona/workspace/suture-refcard/Suture_Reference_Card.pdf"
doc = SimpleDocTemplate(
OUT,
pagesize=landscape(A4),
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=8*mm, bottomMargin=8*mm,
)
story = []
# ── PAGE 1 ────────────────────────────────────────────────────────────────────
story.append(page_title("SUTURE QUICK-REFERENCE CARD | Page 1 of 2 — Selection, Materials & Knots"))
story.append(GAP)
# Row 1: Scenario table (full width)
story.append(section_header("TECHNIQUE SELECTION BY CLINICAL SCENARIO", NAVY))
story.append(SCEN_TABLE)
story.append(GAP)
# Row 2: Material table (full width)
story.append(section_header("SUTURE MATERIAL PROPERTIES", TEAL))
story.append(MAT_TABLE)
story.append(GAP)
# Row 3: Size guide + Knot security side by side
size_block = [section_header("SUTURE SIZE BY LOCATION", AMBER), SIZE_TABLE]
knot_block = [section_header("KNOT SECURITY — MIN. THROWS", GREEN), KNOT_TABLE]
# Wrap each sub-list into a single-cell table
def vstack(items):
rows = [[x] for x in items]
t = Table(rows)
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 1),
]))
return t
story.append(two_col(vstack(size_block), vstack(knot_block),
lw=99*mm, rw=97*mm))
# Footnote
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"* Nylon loses ~20% tensile strength per year in vivo — NOT truly permanent. "
"SL:WL = Suture Length : Wound Length ratio. "
"Deep = buried absorbable layer. Skin = external non-absorbable layer. "
"References: Fitzpatrick's Dermatology 9e · Roberts & Hedges 7e · Tintinalli 9e · Fischer's Mastery of Surgery 8e · Pfenninger & Fowler 3e",
FOOT))
story.append(PageBreak())
# ── PAGE 2 ────────────────────────────────────────────────────────────────────
story.append(page_title("SUTURE QUICK-REFERENCE CARD | Page 2 of 2 — Timing, Indications, Contraindications & Complications"))
story.append(GAP)
# Timing (full width)
story.append(section_header("SUTURE REMOVAL TIMING BY ANATOMIC SITE", NAVY))
story.append(TIMING_TABLE)
story.append(GAP)
# Indications + Contraindications side by side
ind_block = [section_header("INDICATIONS FOR PRIMARY SUTURE CLOSURE", GREEN), IND_TABLE]
con_block = [section_header("CONTRAINDICATIONS TO PRIMARY CLOSURE", RED), CON_TABLE]
story.append(two_col(vstack(ind_block), vstack(con_block),
lw=180*mm, rw=97*mm))
story.append(GAP)
# Complications (full width)
story.append(section_header("COMMON COMPLICATIONS & PREVENTION", colors.HexColor("#5D4037")))
story.append(COMP_TABLE)
# Footnote
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"The 6-hour rule (Pye's Surgical Handicraft 22e): clean wounds may be safely closed up to 6 hrs after injury; this is a guide, not an absolute. "
"Face bites: exception to bite closure rule due to superior facial vascularity. "
"Delayed primary closure: close contaminated wounds at 3-5 days after contamination control. "
"Pull suture toward wound on removal (not away) to avoid disrupting healing scar (Roberts & Hedges 7e).",
FOOT))
doc.build(story)
print("PDF generated:", OUT)