~/neck-triangles/build_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 cm, mm
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, PageBreak, KeepTogether
)
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
# ── Output path ──────────────────────────────────────────────────────────────
OUTPUT = "/tmp/workspace/neck-triangles/Triangles_of_the_Neck_Grays.pdf"
IMG_DIR = "/tmp/workspace/neck-triangles"
# ── Colour palette ───────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MED_BLUE = colors.HexColor("#2e6da4")
LIGHT_BLUE = colors.HexColor("#dce8f5")
ACCENT = colors.HexColor("#e8f0f8")
TABLE_HEAD = colors.HexColor("#2e6da4")
TABLE_ALT = colors.HexColor("#f0f5fb")
WHITE = colors.white
BLACK = colors.black
GREY = colors.HexColor("#555555")
RED_ACCENT = colors.HexColor("#c0392b")
GREEN_ACC = colors.HexColor("#1e7145")
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
TITLE = S("MyTitle", fontSize=22, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, spaceAfter=4, leading=28)
SUBTITLE = S("MySub", fontSize=13, textColor=LIGHT_BLUE, fontName="Helvetica",
alignment=TA_CENTER, spaceAfter=2, leading=18)
REF_STYLE = S("MyRef", fontSize=9, textColor=LIGHT_BLUE, fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceAfter=2)
H1 = S("MyH1", fontSize=15, textColor=WHITE, fontName="Helvetica-Bold",
spaceBefore=14, spaceAfter=4, leading=20)
H2 = S("MyH2", fontSize=12, textColor=DARK_BLUE, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=3, leading=16)
H3 = S("MyH3", fontSize=10, textColor=MED_BLUE, fontName="Helvetica-Bold",
spaceBefore=7, spaceAfter=2, leading=14)
BODY = S("MyBody", fontSize=9.5,textColor=BLACK, fontName="Helvetica",
spaceBefore=2, spaceAfter=3, leading=14, alignment=TA_JUSTIFY)
BULLET = S("MyBullet", fontSize=9.5,textColor=BLACK, fontName="Helvetica",
spaceBefore=1, spaceAfter=1, leading=13, leftIndent=14,
bulletIndent=4, alignment=TA_LEFT)
CAPTION = S("MyCaption", fontSize=8.5,textColor=GREY, fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceBefore=2, spaceAfter=8)
CLINICAL = S("MyClinical",fontSize=9, textColor=colors.HexColor("#1e4d2b"), fontName="Helvetica",
spaceBefore=1, spaceAfter=1, leading=13, leftIndent=10)
FOOTER_S = S("MyFooter", fontSize=7.5,textColor=GREY, fontName="Helvetica-Oblique",
alignment=TA_CENTER)
NOTE_S = S("MyNote", fontSize=8.5,textColor=GREY, fontName="Helvetica-Oblique",
spaceBefore=4, spaceAfter=6, alignment=TA_CENTER)
# ── Helper: section header band ──────────────────────────────────────────────
def section_header(title):
tbl = Table([[Paragraph(title, H1)]], colWidths=[17*cm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("ROUNDEDCORNERS", [4]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 12),
]))
return tbl
def subsection_header(title):
tbl = Table([[Paragraph(title, H2)]], colWidths=[17*cm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
("LINEBELOW", (0,0), (-1,-1), 1.5, MED_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
return tbl
def sub3_header(title):
return Paragraph(f"<font color='#2e6da4'><b>{title}</b></font>", H3)
def bullet(text):
return Paragraph(f"• {text}", BULLET)
def body(text):
return Paragraph(text, BODY)
def sp(h=4):
return Spacer(1, h*mm)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=MED_BLUE, spaceAfter=3, spaceBefore=3)
# ── Generic table builder ─────────────────────────────────────────────────────
def make_table(headers, rows, col_widths=None):
data = [[Paragraph(f"<b>{h}</b>", S("TH", fontSize=9, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=12)) for h in headers]]
for i, row in enumerate(rows):
bg = TABLE_ALT if i % 2 == 0 else WHITE
data.append([Paragraph(str(c), S(f"TD{i}", fontSize=9, fontName="Helvetica",
leading=12, alignment=TA_LEFT)) for c in row])
if col_widths is None:
col_widths = [17*cm / len(headers)] * len(headers)
t = Table(data, colWidths=col_widths, repeatRows=1)
style = [
("BACKGROUND", (0,0), (-1,0), TABLE_HEAD),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#b0c8e0")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
for i in range(1, len(rows)+1):
if i % 2 == 1:
style.append(("BACKGROUND", (0,i), (-1,i), TABLE_ALT))
t.setStyle(TableStyle(style))
return t
# ── Clinical box ──────────────────────────────────────────────────────────────
def clinical_box(rows):
"""rows = list of (landmark, significance) tuples"""
items = []
for lm, sig in rows:
items.append([
Paragraph(f"<b>{lm}</b>", S("CLM", fontSize=9, fontName="Helvetica-Bold",
textColor=GREEN_ACC, leading=12)),
Paragraph(sig, S("CLS", fontSize=9, fontName="Helvetica", leading=12))
])
t = Table(items, colWidths=[4.5*cm, 12.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#f0faf4")),
("LINEABOVE", (0,0), (-1,0), 1.5, GREEN_ACC),
("LINEBELOW", (0,-1),(-1,-1), 1.5, GREEN_ACC),
("LINEBEFORE", (0,0), (0,-1), 3, GREEN_ACC),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#a8d5b5")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
return t
# ── Page template with header/footer ─────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
W, H = A4
# Header bar
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, H-22*mm, W, 22*mm, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 10)
canvas.setFillColor(WHITE)
canvas.drawString(1.5*cm, H-14*mm, "TRIANGLES OF THE NECK")
canvas.setFont("Helvetica", 8)
canvas.setFillColor(LIGHT_BLUE)
canvas.drawRightString(W-1.5*cm, H-14*mm, "Reference: Gray's Anatomy for Students, 4e")
# Footer
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, 0, W, 12*mm, fill=1, stroke=0)
canvas.setFont("Helvetica", 8)
canvas.setFillColor(LIGHT_BLUE)
canvas.drawCentredString(W/2, 4*mm, f"Page {doc.page}")
canvas.setFont("Helvetica-Oblique", 7.5)
canvas.drawString(1.5*cm, 4*mm, "Drake, Vogl & Mitchell — Gray's Anatomy for Students, 4th Ed.")
canvas.drawRightString(W-1.5*cm, 4*mm, "Chapter 8: Head and Neck")
canvas.restoreState()
def on_first_page(canvas, doc):
canvas.saveState()
W, H = A4
# Full dark blue top banner
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, H-80*mm, W, 80*mm, fill=1, stroke=0)
# Accent line
canvas.setFillColor(MED_BLUE)
canvas.rect(0, H-82*mm, W, 2.5*mm, fill=1, stroke=0)
# Footer
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, 0, W, 12*mm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7.5)
canvas.setFillColor(LIGHT_BLUE)
canvas.drawCentredString(W/2, 4*mm, "Drake, Vogl & Mitchell — Gray's Anatomy for Students, 4th Ed. | Chapter 8: Head and Neck")
canvas.restoreState()
# ── Build document ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.8*cm, bottomMargin=2*cm,
title="Triangles of the Neck",
author="Gray's Anatomy for Students",
subject="Human Anatomy – Head and Neck"
)
story = []
# ============================================================
# COVER PAGE
# ============================================================
W, H = A4
story.append(Spacer(1, 50*mm)) # Space for banner
cover_title = Table(
[[Paragraph("TRIANGLES OF THE NECK", TITLE)],
[Paragraph("Anterior Triangle & Posterior Triangle", SUBTITLE)],
[Paragraph("Boundaries • Contents • Clinical Landmarks", SUBTITLE)],
[Spacer(1, 4*mm)],
[Paragraph("Reference: Gray's Anatomy for Students, 4th Edition", REF_STYLE)],
[Paragraph("Drake, Vogl & Mitchell | Chapter 8: Head and Neck", REF_STYLE)]],
colWidths=[17*cm]
)
cover_title.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 14),
("RIGHTPADDING", (0,0), (-1,-1), 14),
]))
story.append(cover_title)
story.append(sp(8))
# Cover diagram
img_path = os.path.join(IMG_DIR, "ant_triangle.png")
img = Image(img_path, width=15*cm, height=8.5*cm, kind="proportional")
story.append(img)
story.append(Paragraph("Fig. 8.169 — Borders and Subdivisions of the Anterior and Posterior Triangles of the Neck<br/>"
"<i>Gray's Anatomy for Students, 4e, Drake, Vogl & Mitchell</i>", CAPTION))
story.append(PageBreak())
# ============================================================
# SECTION 1: ANTERIOR TRIANGLE
# ============================================================
story.append(section_header("I. ANTERIOR TRIANGLE OF THE NECK"))
story.append(sp(4))
story.append(body(
"The anterior triangle of the neck is a bilateral space situated on the anterior aspect of the neck. "
"It is bounded laterally by the anterior border of the sternocleidomastoid (SCM) muscle, superiorly by "
"the inferior border of the mandible, and medially by the midline of the neck. "
"<i>(Gray's Anatomy for Students, p. 1145)</i>"
))
story.append(sp(3))
# --- Boundaries table ---
story.append(subsection_header("A. Boundaries"))
story.append(sp(2))
bnd_rows = [
("Lateral (posterior)", "Anterior border of the sternocleidomastoid muscle"),
("Superior (base)", "Inferior border of the body of the mandible"),
("Medial (anterior)", "Midline of the neck (chin to suprasternal notch)"),
("Apex", "Directed downward at the suprasternal notch"),
("Roof", "Skin, superficial fascia (containing platysma), investing layer of deep cervical fascia"),
("Floor", "Pharynx, larynx, thyroid gland"),
]
story.append(make_table(["Boundary", "Structure"], bnd_rows, [5.5*cm, 11.5*cm]))
story.append(sp(4))
# --- Subdivisions ---
story.append(subsection_header("B. Subdivisions"))
story.append(sp(2))
story.append(body(
"The anterior triangle is further subdivided into four smaller triangles by the digastric muscle "
"(anterior and posterior bellies) and the superior belly of the omohyoid muscle. "
"<i>(Gray's Anatomy for Students, p. 1145)</i>"
))
story.append(sp(3))
# Sub-1: Submental
story.append(sub3_header("1. Submental Triangle (Unpaired)"))
sub1 = [
("Sides (laterally)", "Anterior belly of digastric muscle on each side"),
("Base (inferiorly)", "Body of the hyoid bone"),
("Apex (superiorly)", "Symphysis menti (mandibular symphysis)"),
("Floor", "Mylohyoid muscle"),
]
story.append(make_table(["Boundary", "Structure"], sub1, [5.5*cm, 11.5*cm]))
story.append(sp(2))
story.append(body("<b>Contents:</b>"))
for c in ["Submental lymph nodes (drain tip of tongue, lower lip midline, floor of mouth)",
"Small veins uniting to form the anterior jugular vein"]:
story.append(bullet(c))
story.append(sp(4))
# Sub-2: Submandibular
story.append(sub3_header("2. Submandibular Triangle (Digastric Triangle) — Paired"))
sub2 = [
("Base (superiorly)", "Lower border of the body of the mandible"),
("Anterior wall", "Anterior belly of digastric muscle"),
("Posterior wall", "Posterior belly of digastric + stylohyoid muscle"),
("Floor", "Mylohyoid, hyoglossus, middle constrictor of pharynx"),
("Roof", "Skin, superficial fascia with platysma, deep cervical fascia"),
]
story.append(make_table(["Boundary", "Structure"], sub2, [5.5*cm, 11.5*cm]))
story.append(sp(2))
story.append(body("<b>Contents:</b> <i>(Gray's Anatomy for Students, Table 8.14, p. 1162)</i>"))
sub2_contents = [
["Submandibular salivary gland", "Superficial part lies in triangle; deep part extends around mylohyoid"],
["Submandibular lymph nodes", "3–6 nodes on and around the gland"],
["Facial artery & vein", "Artery grooves the gland; enters face at anteroinferior angle of masseter"],
["Hypoglossal nerve [CN XII]", "Runs forward on hyoglossus, deep to mylohyoid"],
["Nerve to mylohyoid", "Branch of inferior alveolar nerve; runs in mylohyoid groove"],
]
story.append(make_table(["Structure", "Details"], sub2_contents, [5.5*cm, 11.5*cm]))
story.append(sp(4))
# Sub-3: Carotid
story.append(sub3_header("3. Carotid Triangle — Paired"))
sub3bnd = [
("Anteroinferiorly", "Superior belly of the omohyoid muscle"),
("Posteriorly", "Anterior border of sternocleidomastoid"),
("Superiorly", "Stylohyoid muscle + posterior belly of digastric"),
("Floor", "Thyrohyoid, hyoglossus, inferior and middle pharyngeal constrictors"),
]
story.append(make_table(["Boundary", "Structure"], sub3bnd, [5.5*cm, 11.5*cm]))
story.append(sp(2))
story.append(body("<b>Contents:</b> <i>(Gray's Anatomy for Students, Table 8.14, p. 1162)</i>"))
sub3c = [
["Common carotid artery", "Bifurcates at level of C3–C4 (upper border of thyroid cartilage)"],
["Internal carotid artery", "No branches in the neck"],
["External carotid artery", "Branches: superior thyroid, ascending pharyngeal, lingual, facial, occipital"],
["Internal jugular vein", "Lateral to carotid arteries within carotid sheath"],
["Vagus nerve [CN X]", "Within the carotid sheath"],
["Hypoglossal nerve [CN XII]", "Crosses superficially to carotid arteries"],
["Accessory nerve [CN XI]", "Crosses the upper part of the triangle"],
["Ansa cervicalis", "Superior and inferior roots (C1–C3); innervates infrahyoid muscles"],
["Carotid sinus & body", "Baroreceptor and chemoreceptor at carotid bifurcation"],
]
story.append(make_table(["Structure", "Details"], sub3c, [5.5*cm, 11.5*cm]))
story.append(sp(4))
# Sub-4: Muscular
story.append(sub3_header("4. Muscular Triangle (Omotracheal Triangle) — Paired"))
sub4bnd = [
("Medially", "Midline of the neck"),
("Superolaterally", "Superior belly of omohyoid muscle"),
("Posterolaterally", "Anterior border of sternocleidomastoid"),
("Floor", "Trachea, esophagus, thyroid gland"),
]
story.append(make_table(["Boundary", "Structure"], sub4bnd, [5.5*cm, 11.5*cm]))
story.append(sp(2))
story.append(body("<b>Contents:</b>"))
for c in ["Infrahyoid (strap) muscles: sternohyoid, sternothyroid, thyrohyoid, omohyoid (superior belly)",
"Thyroid gland and parathyroid glands",
"Larynx, trachea, pharynx and esophagus",
"Inferior thyroid artery, recurrent laryngeal nerve"]:
story.append(bullet(c))
story.append(sp(5))
# Anterior triangle diagram (again, labeled)
img2 = Image(os.path.join(IMG_DIR, "ant_triangle.png"), width=14*cm, height=8*cm, kind="proportional")
story.append(KeepTogether([img2,
Paragraph("Fig. 8.169 — Borders and Subdivisions of the Anterior Triangle of the Neck<br/>"
"<i>Gray's Anatomy for Students, 4e, p. 1145</i>", CAPTION)]))
story.append(sp(3))
# --- Clinical Landmarks: Anterior ---
story.append(subsection_header("C. Clinical Landmarks — Anterior Triangle"))
story.append(sp(2))
ant_clinical = [
("Carotid Pulse", "Palpated just medial to the anterior SCM border in the carotid triangle; used in CPR and as landmark for carotid endarterectomy"),
("Carotid Bifurcation (C3–C4)", "Contains carotid sinus (baroreceptor) — massage terminates SVT; carotid body (chemoreceptor). Pathological sensitivity causes carotid sinus syncope"),
("Carotid Sinus Massage", "Used clinically to terminate paroxysmal supraventricular tachycardia (SVT)"),
("Submandibular Gland Surgery", "CN XII runs deep to gland — must be protected to prevent tongue paralysis (hypoglossal nerve palsy)"),
("Tracheostomy", "Midline procedure in the muscular triangle between the strap muscles; standard elective airway procedure"),
("Cricothyrotomy", "Emergency airway through the cricothyroid membrane; landmark is the depression between thyroid and cricoid cartilages"),
("Thyroidectomy", "Thyroid in muscular triangle; recurrent laryngeal nerve (tracheoesophageal groove) and parathyroids are at risk"),
("Submental Lymph Nodes", "Enlarged in carcinoma of tip of tongue, floor of mouth, and lower lip"),
("Submandibular Lymph Nodes", "Enlarged in dental infections, oral cavity cancers, submandibular sialadenitis/calculi"),
("Ansa Cervicalis", "Used for laryngeal reinnervation procedures (anastomosis to recurrent laryngeal nerve)"),
]
story.append(clinical_box(ant_clinical))
story.append(sp(4))
story.append(PageBreak())
# ============================================================
# SECTION 2: POSTERIOR TRIANGLE
# ============================================================
story.append(section_header("II. POSTERIOR TRIANGLE OF THE NECK"))
story.append(sp(4))
story.append(body(
"The posterior triangle of the neck is situated on the lateral aspect of the neck, in direct continuity "
"with the upper limb. It lies between the sternocleidomastoid anteriorly and the trapezius posteriorly, "
"with the clavicle forming its base. "
"<i>(Gray's Anatomy for Students, p. 1162)</i>"
))
story.append(sp(3))
# Posterior triangle border diagram
img3 = Image(os.path.join(IMG_DIR, "post_triangle_borders.png"), width=14*cm, height=8*cm, kind="proportional")
story.append(KeepTogether([img3,
Paragraph("Fig. 8.186 — Borders of the Posterior Triangle (Occipital and Omoclavicular Subdivisions)<br/>"
"<i>Gray's Anatomy for Students, 4e, p. 1163</i>", CAPTION)]))
story.append(sp(3))
# --- Boundaries ---
story.append(subsection_header("A. Boundaries"))
story.append(sp(2))
post_bnd = [
("Anterior", "Posterior border of the sternocleidomastoid muscle"),
("Posterior", "Anterior border of the trapezius muscle"),
("Base (inferior)", "Middle one-third of the clavicle"),
("Apex (superior)", "Occipital bone just posterior to the mastoid process"),
("Roof", "Investing layer of cervical fascia stretched between SCM and trapezius; skin and superficial fascia above"),
("Floor", "Prevertebral layer of cervical fascia covering: splenius capitis, levator scapulae, scalenus posterior, scalenus medius, scalenus anterior (superior to inferior)"),
]
story.append(make_table(["Boundary", "Structure"], post_bnd, [5.5*cm, 11.5*cm]))
story.append(sp(4))
story.append(sub3_header("Subdivisions of the Posterior Triangle"))
story.append(body(
"The inferior belly of the omohyoid muscle crosses the posterior triangle and divides it into two sub-triangles: "
"<i>(Gray's Anatomy for Students, p. 1163)</i>"
))
sub_div = [
("Occipital Triangle", "Larger, superior subdivision; contains the accessory nerve, cervical plexus branches, and upper brachial plexus"),
("Omoclavicular (Subclavian) Triangle", "Smaller, inferior subdivision; contains the 3rd part of subclavian artery, subclavian vein, and brachial plexus trunks"),
]
story.append(make_table(["Subdivision", "Key Contents"], sub_div, [6*cm, 11*cm]))
story.append(sp(4))
# --- Contents ---
story.append(subsection_header("B. Contents"))
story.append(sp(2))
story.append(sub3_header("Muscles (forming the floor, superior to inferior)"))
for m in ["Splenius capitis",
"Levator scapulae",
"Scalenus posterior",
"Scalenus medius",
"Scalenus anterior (partially visible)",
"Inferior belly of omohyoid (crosses the triangle)"]:
story.append(bullet(m))
story.append(sp(3))
story.append(sub3_header("Vessels"))
vessels = [
["3rd part of subclavian artery", "Crosses base between anterior and middle scalene muscles; becomes axillary artery at lateral border of rib I"],
["Transverse cervical artery", "Branch of thyrocervical trunk; crosses base, divides into superficial and deep branches at trapezius"],
["Suprascapular artery", "Branch of thyrocervical trunk; crosses inferior part toward scapular notch"],
["Dorsal scapular artery", "May arise from 3rd part of subclavian; runs to medial border of scapula"],
["External jugular vein", "Most superficial structure; crosses SCM, descends in superficial fascia, drains into subclavian vein at base"],
["Subclavian vein", "Crosses base anterior to anterior scalene; receives external jugular vein; joins IJV to form brachiocephalic vein"],
]
story.append(make_table(["Vessel", "Details"], vessels, [5.5*cm, 11.5*cm]))
story.append(sp(3))
# Posterior triangle arteries diagram
img4 = Image(os.path.join(IMG_DIR, "post_triangle_arteries.png"), width=14*cm, height=8.5*cm, kind="proportional")
story.append(KeepTogether([img4,
Paragraph("Fig. 8.189 — Arteries of the Posterior Triangle, Including the Subclavian Artery, Thyrocervical Trunk, Brachial Plexus and Phrenic Nerve<br/>"
"<i>Gray's Anatomy for Students, 4e, p. 1166</i>", CAPTION)]))
story.append(sp(3))
story.append(sub3_header("Nerves"))
story.append(body("<b>1. Accessory Nerve [CN XI]</b> <i>(Gray's Anatomy for Students, p. 1167)</i>"))
story.append(body(
"Exits the cranial cavity via the jugular foramen → passes deep to posterior belly of digastric → "
"enters and innervates the sternocleidomastoid → crosses the posterior triangle obliquely within the investing "
"layer of cervical fascia → enters and innervates the trapezius from its deep surface. "
"Its <b>superficial course makes it highly vulnerable to iatrogenic injury</b> during lymph node biopsy."
))
story.append(sp(3))
story.append(body("<b>2. Cutaneous Branches of Cervical Plexus</b> — emerge at <b>Erb's Point</b> (posterior border of SCM, junction of upper and lower halves):"))
plexus = [
["Lesser occipital nerve", "C2", "Scalp behind the auricle"],
["Great auricular nerve", "C2, C3", "Skin over parotid gland, mastoid, lower pinna"],
["Transverse cervical nerve", "C2, C3", "Skin over the anterior triangle"],
["Supraclavicular nerves (medial, intermediate, lateral)", "C3, C4", "Skin over clavicle, shoulder, upper chest"],
]
story.append(make_table(["Nerve", "Roots", "Distribution"], plexus, [6*cm, 2.5*cm, 8.5*cm]))
story.append(sp(3))
story.append(body("<b>3. Phrenic Nerve (C3, C4, C5)</b> — <i>'C3, 4, 5 keeps the diaphragm alive'</i>"))
story.append(body(
"Arises from anterior rami of C3–C5 within the cervical plexus; hooks around the upper lateral border of the "
"anterior scalene muscle and descends on its anterior surface within the prevertebral fascia to enter the thorax. "
"Supplies the diaphragm with both motor and sensory innervation. "
"<i>(Gray's Anatomy for Students, p. 1168)</i>"
))
story.append(sp(3))
story.append(body("<b>4. Brachial Plexus (C5–T1)</b>"))
bp = [
["Roots (C5–T1)", "Emerge between anterior and middle scalene muscles"],
["Upper trunk (C5, C6)", "Formed between the scalene muscles in the triangle"],
["Middle trunk (C7)", "Continuation of C7 root"],
["Lower trunk (C8, T1)", "Crosses the base; lies on the 1st rib"],
]
story.append(make_table(["Component", "Location/Course"], bp, [5.5*cm, 11.5*cm]))
story.append(sp(4))
# --- Clinical Landmarks: Posterior ---
story.append(subsection_header("C. Clinical Landmarks — Posterior Triangle"))
story.append(sp(2))
post_clinical = [
("Accessory Nerve Injury", "During lymph node biopsy or neck dissection — trapezius palsy: drooping shoulder, inability to shrug, winging of scapula, chronic neck-shoulder pain"),
("Erb's Point", "Posterior border of SCM at junction of upper/lower halves; all 4 cutaneous branches of cervical plexus emerge here — site for superficial cervical plexus block"),
("Erb-Duchenne Palsy", "Upper trunk injury (C5–C6) — 'waiter's tip' deformity; arm internally rotated, extended, pronated; from traction during difficult delivery"),
("Klumpke's Palsy", "Lower trunk injury (C8–T1) — intrinsic hand muscle paralysis + Horner's syndrome; from forceps delivery or hyperabduction"),
("External Jugular Vein", "Visible across SCM into posterior triangle; used for IV access and JVP assessment (>3 cm above sternal angle = elevated right atrial pressure)"),
("Subclavian CVC", "Inserted in the omoclavicular triangle just above the clavicle; risks: pneumothorax, haemothorax, subclavian artery puncture"),
("Interscalene Block", "Brachial plexus block between anterior and middle scalene muscles; used for shoulder and upper arm surgery"),
("Thoracic Outlet Syndrome", "Compression of subclavian artery/lower trunk brachial plexus between anterior scalene and 1st rib, or by cervical rib; upper limb pain, paraesthesia, weakness"),
("Phrenic Nerve Injury", "Unilateral: elevated hemidiaphragm on CXR, dyspnoea on exertion. Bilateral: respiratory failure. Risk during neck surgery, central line insertion"),
("Cystic Hygroma", "Multilocular lymphatic malformation in posterior triangle of neonates/infants; can compress airway — requires early surgical or sclerotherapy treatment"),
("Level V Lymph Nodes", "Posterior cervical chain in this triangle; enlarged in lymphoma, nasopharyngeal carcinoma, TB (scrofula)"),
("Cervical Rib", "Anomalous rib from C7 vertebra; palpable in posterior triangle above clavicle; can cause thoracic outlet syndrome"),
]
story.append(clinical_box(post_clinical))
story.append(sp(4))
story.append(PageBreak())
# ============================================================
# SECTION 3: SUMMARY COMPARISON
# ============================================================
story.append(section_header("III. SUMMARY COMPARISON TABLE"))
story.append(sp(4))
summary = [
["Key dividing muscle", "SCM (anterior border)", "SCM (posterior border) + Trapezius"],
["Base", "Inferior border of mandible", "Clavicle (middle one-third)"],
["Apex", "Suprasternal notch", "Occipital bone (mastoid area)"],
["Subdivisions", "Submental, Submandibular,\nCarotid, Muscular", "Occipital, Omoclavicular (Subclavian)"],
["Sub-dividing muscle", "Digastric + superior belly of omohyoid", "Inferior belly of omohyoid"],
["Floor", "Pharynx, larynx, thyroid gland", "Prevertebral muscles\n(splenius, levator scapulae, scalenes)"],
["Key artery", "Common carotid / external carotid", "3rd part of subclavian artery"],
["Key vein", "Internal jugular vein", "External jugular / Subclavian vein"],
["Key nerves", "CN X, XI, XII,\nAnsa cervicalis", "CN XI, Brachial plexus,\nPhrenic nerve"],
["Viscera", "Thyroid, parathyroid,\nLarynx, pharynx", "None"],
["Key clinical procedure", "Carotid endarterectomy,\nTracheostomy, Thyroidectomy", "Subclavian CVC, Interscalene block,\nCervical lymph node biopsy"],
]
story.append(make_table(
["Feature", "Anterior Triangle", "Posterior Triangle"],
summary,
[5*cm, 6*cm, 6*cm]
))
story.append(sp(6))
# Reference box
ref_data = [[
Paragraph(
"<b>Reference</b><br/>"
"Drake, R.L., Vogl, A.W., & Mitchell, A.W.M. (2020). <i>Gray's Anatomy for Students</i>, 4th Edition. "
"Elsevier. Chapter 8: Head and Neck, pp. 1145–1170.<br/><br/>"
"Figures reproduced from: Fig. 8.169 (p. 1145), Fig. 8.186 (p. 1163), Fig. 8.189 (p. 1166), Fig. 8.190 (p. 1167).<br/>"
"Table 8.14 (p. 1162): Subdivisions of the anterior triangle of the neck.",
S("RB", fontSize=9, fontName="Helvetica", textColor=DARK_BLUE, leading=14, alignment=TA_LEFT)
)
]]
ref_tbl = Table(ref_data, colWidths=[17*cm])
ref_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
("LINEABOVE", (0,0), (-1,0), 2, DARK_BLUE),
("LINEBELOW", (0,-1),(-1,-1), 2, DARK_BLUE),
("LINEBEFORE", (0,0), (0,-1), 4, MED_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
]))
story.append(ref_tbl)
# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story,
onFirstPage=on_first_page,
onLaterPages=on_page)
print(f"PDF created: {OUTPUT}")
import os
print(f"File size: {os.path.getsize(OUTPUT):,} bytes")