Explain Histology of Tonsils


| Feature | Description |
|---|---|
| Nodule packing | Nodules are densely packed and often merge with each other |
| Germinal centers (GC) | Many nodules show prominent pale-staining germinal centers, indicating active B-cell proliferation and differentiation |
| Mantle zone | Dark-staining small lymphocytes surround the germinal center (not always obvious) |
| Nodule location | Principally in the mucosa, with walls of crypts also contributing |
| Feature | Palatine Tonsil | Pharyngeal Tonsil (Adenoid) | Lingual Tonsil |
|---|---|---|---|
| Epithelium | Non-keratinized stratified squamous | Pseudostratified ciliated columnar (respiratory) | Non-keratinized stratified squamous |
| Crypts | Deep, branching (10-15) | Shallower folds/furrows | Shallow, simple crypts |
| Capsule | Hemicapsule present | Incomplete capsule | Incomplete/absent |
| Location | Tonsillar fossa (lateral oropharynx) | Posterior nasopharynx | Base of tongue |
Create a quick reference PDF comparing palatine, pharyngeal, and lingual tonsil histology
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace does not exist"
mkdir -p /home/daytona/workspace/tonsil-pdf && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "from reportlab.lib.pagesizes import A4; print('A4 ok')"
~/tonsil-pdf/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.graphics.shapes import Drawing, Rect, String, Circle, Line
from reportlab.graphics import renderPDF
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2744")
MID_BLUE = colors.HexColor("#2c4a8c")
LIGHT_BLUE = colors.HexColor("#d9e4f5")
PALE_GREY = colors.HexColor("#f4f6fb")
ACCENT_GOLD = colors.HexColor("#c8860a")
TEXT_DARK = colors.HexColor("#1c1c2e")
TEXT_MID = colors.HexColor("#3a3a5c")
WHITE = colors.white
# Palatine / Pharyngeal / Lingual accent colours
COL_PAL = colors.HexColor("#2c6fad") # blue
COL_PHA = colors.HexColor("#1e8a5e") # teal-green
COL_LIN = colors.HexColor("#8e3abf") # purple
PATH = "/home/daytona/workspace/tonsil-pdf/Tonsil_Histology_Reference.pdf"
doc = SimpleDocTemplate(
PATH,
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=1.6*cm, bottomMargin=1.8*cm,
title="Tonsil Histology Quick Reference",
author="Orris Medical Library",
)
W, H = A4
BODY_W = W - 3.6*cm
styles = getSampleStyleSheet()
def s(name, **kw):
return ParagraphStyle(name, parent=styles["Normal"], **kw)
# ── Custom paragraph styles ──────────────────────────────────────────────────
TITLE_S = s("TITLE_S", fontSize=20, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=26)
SUBTITLE_S = s("SUBTITLE_S", fontSize=10, textColor=colors.HexColor("#c8d8f0"),
fontName="Helvetica", alignment=TA_CENTER, leading=14)
SEC_HEAD = s("SEC_HEAD", fontSize=13, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_LEFT, leading=17)
BODY_S = s("BODY_S", fontSize=8.5, textColor=TEXT_DARK,
fontName="Helvetica", leading=12, alignment=TA_JUSTIFY)
BOLD_S = s("BOLD_S", fontSize=8.5, textColor=TEXT_DARK,
fontName="Helvetica-Bold", leading=12)
LABEL_S = s("LABEL_S", fontSize=7.5, textColor=TEXT_MID,
fontName="Helvetica-BoldOblique", leading=10)
FOOT_S = s("FOOT_S", fontSize=7, textColor=TEXT_MID,
fontName="Helvetica-Oblique", alignment=TA_CENTER, leading=9)
KEY_S = s("KEY_S", fontSize=8, textColor=TEXT_DARK,
fontName="Helvetica", leading=11)
KEY_BOLD= s("KEY_BOLD", fontSize=8, textColor=TEXT_DARK,
fontName="Helvetica-Bold", leading=11)
# ── Flowable helpers ─────────────────────────────────────────────────────────
class ColorRect(Flowable):
def __init__(self, w, h, fill, radius=4):
self.width, self.height, self.fill, self.radius = w, h, fill, radius
def draw(self):
self.canv.setFillColor(self.fill)
self.canv.roundRect(0, 0, self.width, self.height, self.radius, fill=1, stroke=0)
class HeaderBanner(Flowable):
def __init__(self, w):
self.width = w
self.height = 70
def draw(self):
c = self.canv
# gradient-like background using two rects
c.setFillColor(DARK_NAVY)
c.roundRect(0, 0, self.width, self.height, 6, fill=1, stroke=0)
# top accent stripe
c.setFillColor(MID_BLUE)
c.rect(0, self.height - 8, self.width, 8, fill=1, stroke=0)
c.setFillColor(ACCENT_GOLD)
c.rect(0, self.height - 11, self.width, 3, fill=1, stroke=0)
class SectionBanner(Flowable):
def __init__(self, text, accent_color, w):
self.text = text
self.accent = accent_color
self.width = w
self.height = 28
def draw(self):
c = self.canv
c.setFillColor(DARK_NAVY)
c.roundRect(0, 0, self.width, self.height, 4, fill=1, stroke=0)
c.setFillColor(self.accent)
c.rect(0, 0, 5, self.height, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(WHITE)
c.drawString(12, 9, self.text)
class DiagramFlowable(Flowable):
"""Hand-drawn schematic cross-section of each tonsil type."""
def __init__(self, tonsil_type, w=None, h=None):
self.tonsil_type = tonsil_type
self.width = w or 5.5*cm
self.height = h or 4.2*cm
def draw(self):
c = self.canv
W, H = self.width, self.height
cx, cy = W/2, H/2
if self.tonsil_type == "palatine":
self._draw_palatine(c, W, H)
elif self.tonsil_type == "pharyngeal":
self._draw_pharyngeal(c, W, H)
else:
self._draw_lingual(c, W, H)
def _draw_palatine(self, c, W, H):
# Outer border
c.setFillColor(colors.HexColor("#e8f0fa"))
c.roundRect(2, 2, W-4, H-4, 5, fill=1, stroke=0)
# Capsule
c.setStrokeColor(colors.HexColor("#6a8cbf"))
c.setLineWidth(1.5)
c.roundRect(4, 4, W-8, H-8, 4, fill=0, stroke=1)
# Lymphoid nodules with GC
nodule_positions = [(W*0.27, H*0.62), (W*0.58, H*0.65), (W*0.43, H*0.35)]
for nx, ny in nodule_positions:
r = W*0.13
c.setFillColor(colors.HexColor("#9db8d6"))
c.setStrokeColor(colors.HexColor("#6a8cbf"))
c.setLineWidth(0.5)
c.circle(nx, ny, r, fill=1, stroke=1)
# germinal centre
c.setFillColor(colors.HexColor("#d4e3f7"))
c.circle(nx, ny, r*0.45, fill=1, stroke=0)
# Crypts (invaginations from top surface)
c.setStrokeColor(colors.HexColor("#3a5a8c"))
c.setLineWidth(1)
for cx_pos in [W*0.22, W*0.42, W*0.65]:
c.line(cx_pos, H-4, cx_pos, H*0.7)
# Surface epithelium label line
c.setFillColor(colors.HexColor("#1a2744"))
c.setFont("Helvetica-Bold", 5)
c.drawString(3, H-8, "SSE")
c.setFont("Helvetica", 5)
c.drawString(3, H*0.6, "GC")
c.drawString(W*0.7, H*0.25, "Capsule")
c.drawString(W*0.32, H-12, "Crypts")
def _draw_pharyngeal(self, c, W, H):
c.setFillColor(colors.HexColor("#e8f5ee"))
c.roundRect(2, 2, W-4, H-4, 5, fill=1, stroke=0)
c.setStrokeColor(colors.HexColor("#3a8a5e"))
c.setLineWidth(1.5)
c.roundRect(4, 4, W-8, H-8, 4, fill=0, stroke=1)
# Pseudo-ciliated columnar surface (wavy top)
c.setStrokeColor(colors.HexColor("#3a8a5e"))
c.setLineWidth(1)
path = c.beginPath()
path.moveTo(6, H-7)
for i in range(8):
x = 6 + i*(W-12)/7
y_up = H - 4
y_dn = H - 10
path.curveTo(x + 3, y_up, x + 5, y_dn, x + (W-12)/7, H - 7)
c.drawPath(path, stroke=1, fill=0)
# Folds/furrows (less deep than crypts)
for fx in [W*0.3, W*0.55]:
c.line(fx, H-10, fx, H*0.6)
# Lymphoid tissue
nodule_positions = [(W*0.28, H*0.42), (W*0.55, H*0.45), (W*0.42, H*0.25)]
for nx, ny in nodule_positions:
r = W*0.12
c.setFillColor(colors.HexColor("#7dc2a0"))
c.setStrokeColor(colors.HexColor("#3a8a5e"))
c.setLineWidth(0.5)
c.circle(nx, ny, r, fill=1, stroke=1)
c.setFillColor(colors.HexColor("#c8ead9"))
c.circle(nx, ny, r*0.45, fill=1, stroke=0)
c.setFillColor(colors.HexColor("#1a2744"))
c.setFont("Helvetica-Bold", 5)
c.drawString(3, H-8, "PCCE")
c.setFont("Helvetica", 5)
c.drawString(W*0.34, H-14, "Folds")
c.drawString(W*0.3, H*0.22, "GC")
def _draw_lingual(self, c, W, H):
c.setFillColor(colors.HexColor("#f0e8fa"))
c.roundRect(2, 2, W-4, H-4, 5, fill=1, stroke=0)
c.setStrokeColor(colors.HexColor("#7a3abf"))
c.setLineWidth(1.5)
c.roundRect(4, 4, W-8, H-8, 4, fill=0, stroke=1)
# Flat SSE surface
c.setFillColor(colors.HexColor("#c8b0e0"))
c.rect(4, H-12, W-8, 8, fill=1, stroke=0)
# Single simple crypt
c.setStrokeColor(colors.HexColor("#5a2a8c"))
c.setLineWidth(1.2)
c.line(W/2, H-12, W/2, H*0.5)
# Fewer lymphoid nodules
for nx, ny in [(W*0.3, H*0.38), (W*0.65, H*0.42)]:
r = W*0.12
c.setFillColor(colors.HexColor("#b088d4"))
c.setStrokeColor(colors.HexColor("#7a3abf"))
c.setLineWidth(0.5)
c.circle(nx, ny, r, fill=1, stroke=1)
c.setFillColor(colors.HexColor("#ddd0f0"))
c.circle(nx, ny, r*0.45, fill=1, stroke=0)
c.setFillColor(colors.HexColor("#1a2744"))
c.setFont("Helvetica-Bold", 5)
c.drawString(3, H-8, "SSE")
c.setFont("Helvetica", 5)
c.drawString(W*0.35, H-16, "Crypt")
c.drawString(W*0.28, H*0.22, "GC")
c.drawString(W*0.55, H*0.18, "(fewer)")
# ════════════════════════════════════════════════════════════════════════════
# BUILD CONTENT
# ════════════════════════════════════════════════════════════════════════════
story = []
# ── HEADER ──────────────────────────────────────────────────────────────────
story.append(HeaderBanner(BODY_W))
# Title text overlaid (achieved by table-in-table trick)
title_table = Table([[
Paragraph("TONSIL HISTOLOGY", TITLE_S),
]], colWidths=[BODY_W])
title_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.transparent),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
]))
story.append(Spacer(1, -60))
story.append(title_table)
sub_table = Table([[
Paragraph("Quick Reference · Palatine · Pharyngeal · Lingual · Waldeyer's Ring", SUBTITLE_S)
]], colWidths=[BODY_W])
sub_table.setStyle(TableStyle([("TOPPADDING",(0,0),(-1,-1),0),("BOTTOMPADDING",(0,0),(-1,-1),10)]))
story.append(sub_table)
story.append(Spacer(1, 10))
# ── WALDEYER'S RING INTRO BOX ────────────────────────────────────────────────
intro_text = (
"<b>Waldeyer's ring</b> is a circular arrangement of secondary lymphoid tissue guarding the "
"openings of the respiratory and digestive tracts. It comprises the <b>palatine tonsils</b> "
"(paired, lateral oropharynx), <b>pharyngeal tonsil / adenoids</b> (posterior nasopharynx), "
"<b>lingual tonsil</b> (tongue base), and tubal tonsils (near Eustachian tube openings). "
"All tonsils share lymphoid nodules with germinal centres but differ in epithelial type, "
"crypt morphology, and capsule completeness."
)
intro_box = Table([[Paragraph(intro_text, BODY_S)]], colWidths=[BODY_W-16])
intro_box.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_BLUE),
("BOX", (0,0),(-1,-1), 0.5, MID_BLUE),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("ROUNDEDCORNERS", (0,0),(-1,-1), [4,4,4,4]),
]))
story.append(intro_box)
story.append(Spacer(1, 10))
# ── THREE TONSIL COLUMNS ─────────────────────────────────────────────────────
COL_W = (BODY_W - 8) / 3
def tonsil_section(name, accent, diagram_key, rows):
"""Build one tonsil column card."""
elements = []
# Header
hdr = Table([[Paragraph(name, SEC_HEAD)]], colWidths=[COL_W])
hdr.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), accent),
("LEFTPADDING", (0,0),(-1,-1), 8),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
("ROUNDEDCORNERS",(0,0),(-1,-1),[4,4,0,0]),
]))
elements.append(hdr)
# Diagram
diag = DiagramFlowable(diagram_key, w=COL_W-4, h=3.6*cm)
diag_tbl = Table([[diag]], colWidths=[COL_W])
diag_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), PALE_GREY),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
]))
elements.append(diag_tbl)
# Rows
for label, value in rows:
row_tbl = Table([
[Paragraph(label, LABEL_S)],
[Paragraph(value, BODY_S)],
], colWidths=[COL_W])
row_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), colors.HexColor("#eef2fc")),
("BACKGROUND", (0,1), (0,1), WHITE),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("TOPPADDING", (0,0),(0,0), 3),
("BOTTOMPADDING",(0,0),(0,0), 2),
("TOPPADDING", (0,1),(0,1), 2),
("BOTTOMPADDING",(0,1),(0,1), 4),
("LINEBELOW", (0,1),(0,1), 0.3, colors.HexColor("#dde4f0")),
]))
elements.append(row_tbl)
# Bottom border
bot = Table([[""]], colWidths=[COL_W])
bot.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), accent),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("ROUNDEDCORNERS",(0,0),(-1,-1),[0,0,4,4]),
]))
elements.append(bot)
return elements
palatine_rows = [
("LOCATION", "Tonsillar fossa, lateral oropharynx; between palatoglossal and palatopharyngeal arches"),
("EPITHELIUM", "<b>Non-keratinized stratified squamous epithelium (SSE)</b>; heavily infiltrated by lymphocytes"),
("CRYPTS", "<b>10–15 deep, branching crypts</b> extending full thickness of tonsil; walls bear lymphoid nodules"),
("CAPSULE", "Well-defined fibrous <b>hemicapsule</b> on deep (lateral) surface; separates from superior constrictor"),
("LYMPHOID TISSUE", "Dense, packed secondary lymphoid nodules with prominent <b>germinal centres (GC)</b>; mantle zones present"),
("GERMINAL CENTRES", "Active B-cell proliferation; centroblasts, centrocytes, follicular dendritic cells, tingible body macrophages"),
("LYMPHOCYTE INVASION","Characteristic: lymphocytes massively invade SSE, disrupting epithelial–CT boundary; 'reticular' / lympho-epithelium"),
("SUBMUCOSA", "Dense connective tissue deep to nodules; mucus-secreting compound tubuloacinar glands at crypt bases"),
("AFFERENT LYMPHATICS","<b>Absent</b> – antigen sampling occurs directly via crypt epithelium and infiltrating lymphocytes"),
("EFFERENT LYMPHATICS","Present; drain to jugulodigastric (tonsillar) nodes"),
("BLOOD SUPPLY", "Tonsillar branch of facial artery (main); paratonsillar vein runs lateral to capsule (risk in tonsillectomy)"),
("INVOLUTION", "Hypertrophied in childhood; <b>atrophies at puberty</b>; remnants persist in old age"),
("CLINICAL NOTE", "Deep crypts harbour bacteria → recurrent tonsillitis; asymmetric enlargement warrants biopsy to exclude lymphoma"),
]
pharyngeal_rows = [
("LOCATION", "Posterior roof of nasopharynx; forms posterosuperior arc of Waldeyer's ring"),
("EPITHELIUM", "<b>Pseudostratified ciliated columnar (respiratory) epithelium</b> predominates; patches of SSE may occur"),
("CRYPTS", "Shallower folds/furrows (not true branching crypts); less complex than palatine"),
("CAPSULE", "<b>Incomplete capsule</b>; fibrous septa extend into the lymphoid tissue forming lobules"),
("LYMPHOID TISSUE", "Secondary lymphoid nodules with germinal centres; organised into lobules separated by fibrous septa"),
("GERMINAL CENTRES", "Present but less prominent than in palatine tonsil"),
("LYMPHOCYTE INVASION","Present but less marked; ciliated epithelium partially replaced by infiltrating lymphocytes in active tissue"),
("SUBMUCOSA", "Fibromuscular layer; mucous glands present"),
("AFFERENT LYMPHATICS","<b>Absent</b>"),
("EFFERENT LYMPHATICS","Drain to retropharyngeal and upper deep cervical nodes"),
("BLOOD SUPPLY", "Ascending pharyngeal artery; branches of internal maxillary artery"),
("INVOLUTION", "Maximal size 3–7 years; <b>regresses by puberty</b>; clinically insignificant in adults"),
("CLINICAL NOTE", "Hypertrophy (adenoids) causes nasal obstruction, otitis media; adenoidectomy common in children"),
]
lingual_rows = [
("LOCATION", "Posterior third (base) of tongue, behind sulcus terminalis; forms inferior arc of Waldeyer's ring"),
("EPITHELIUM", "<b>Non-keratinized stratified squamous epithelium (SSE)</b>, similar to palatine tonsil"),
("CRYPTS", "<b>Single, simple, unbranched crypt</b> per lymphoid nodule – much simpler than palatine"),
("CAPSULE", "<b>No capsule</b>; lymphoid tissue merges with tongue musculature"),
("LYMPHOID TISSUE", "Discrete but smaller lymphoid follicles; germinal centres present; less densely packed than palatine"),
("GERMINAL CENTRES", "Present; less conspicuous; fewer nodules per unit area"),
("LYMPHOCYTE INVASION","Present in SSE; less dramatic than palatine"),
("SUBMUCOSA", "Merges with tongue musculature; mucous and serous glands (of von Ebner) nearby"),
("AFFERENT LYMPHATICS","<b>Absent</b>"),
("EFFERENT LYMPHATICS","Drain to submental and submandibular nodes"),
("BLOOD SUPPLY", "Lingual artery (dorsal lingual branches)"),
("INVOLUTION", "Hypertrophy may occur if palatine tonsils removed (compensatory); gradual regression with age"),
("CLINICAL NOTE", "May enlarge post-tonsillectomy; lingual tonsillar hypertrophy can cause dysphagia/snoring"),
]
pal_col = tonsil_section("PALATINE TONSIL", COL_PAL, "palatine", palatine_rows)
pha_col = tonsil_section("PHARYNGEAL TONSIL", COL_PHA, "pharyngeal", pharyngeal_rows)
lin_col = tonsil_section("LINGUAL TONSIL", COL_LIN, "lingual", lingual_rows)
# Zip all rows together
max_rows = max(len(pal_col), len(pha_col), len(lin_col))
def pad(lst, n): return lst + [Spacer(1,1)] * (n - len(lst))
pal_col = pad(pal_col, max_rows)
pha_col = pad(pha_col, max_rows)
lin_col = pad(lin_col, max_rows)
three_col_data = [[p, q, r] for p, q, r in zip(pal_col, pha_col, lin_col)]
three_col = Table(three_col_data, colWidths=[COL_W, COL_W, COL_W], spaceBefore=0)
three_col.setStyle(TableStyle([
("VALIGN", (0,0),(-1,-1), "TOP"),
("LEFTPADDING", (0,0),(-1,-1), 2),
("RIGHTPADDING", (0,0),(-1,-1), 2),
("TOPPADDING", (0,0),(-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
]))
story.append(three_col)
story.append(Spacer(1, 10))
# ── COMPARISON TABLE ─────────────────────────────────────────────────────────
story.append(SectionBanner("AT-A-GLANCE COMPARISON", ACCENT_GOLD, BODY_W))
story.append(Spacer(1, 6))
comp_headers = ["Feature", "Palatine", "Pharyngeal (Adenoid)", "Lingual"]
comp_rows = [
["Epithelium", "Non-kerat. SSE", "Pseudostrat. ciliated columnar", "Non-kerat. SSE"],
["Crypts", "10–15, deep, branching", "Shallow folds/furrows", "1 per nodule, simple"],
["Capsule", "Hemicapsule (well-defined)","Incomplete", "Absent"],
["Lymph. nodules", "Dense, closely packed", "Lobulated by septa", "Discrete, fewer"],
["Germinal centres", "Prominent", "Present", "Present, less conspicuous"],
["Lymphocyte invasion\nof epithelium", "Marked – hallmark feature", "Moderate", "Mild–moderate"],
["Afferent lymphatics","Absent", "Absent", "Absent"],
["Involution", "Puberty", "Puberty (3–7 yrs peak)", "Gradual"],
["Waldeyer's position","Lateral", "Posterosuperior", "Inferior (tongue base)"],
]
comp_col_w = [BODY_W*0.22, BODY_W*0.24, BODY_W*0.27, BODY_W*0.27]
comp_style = [
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, PALE_GREY]),
("ALIGN", (0,0), (-1,-1), "LEFT"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#c0cce0")),
("TEXTCOLOR", (1,1), (1,-1), COL_PAL),
("TEXTCOLOR", (2,1), (2,-1), COL_PHA),
("TEXTCOLOR", (3,1), (3,-1), COL_LIN),
]
header_cells = [Paragraph(h, ParagraphStyle("CH", fontName="Helvetica-Bold",
fontSize=7.5, textColor=WHITE, leading=10)) for h in comp_headers]
body_cells = [[Paragraph(str(cell), ParagraphStyle("CC", fontName="Helvetica" if i>0 else "Helvetica-Bold",
fontSize=7.5, textColor=COL_PAL if i==1 else (COL_PHA if i==2 else (COL_LIN if i==3 else TEXT_DARK)),
leading=10)) for i, cell in enumerate(row)] for row in comp_rows]
comp_tbl = Table([header_cells] + body_cells, colWidths=comp_col_w)
comp_tbl.setStyle(TableStyle(comp_style))
story.append(comp_tbl)
story.append(Spacer(1, 10))
# ── KEY HISTOLOGICAL FEATURES BOX ────────────────────────────────────────────
story.append(SectionBanner("KEY HISTOLOGICAL FEATURES & MNEMONICS", MID_BLUE, BODY_W))
story.append(Spacer(1, 6))
key_data = [
["Lympho-epithelium",
"Lymphocytes migrate into the surface/crypt epithelium of all tonsils. In palatine tonsil this is most extreme – the epithelial–CT boundary is obliterated and epithelial islands (Ep) appear within lymphoid nodules."],
["No afferent lymphatics",
"Unlike lymph nodes, tonsils have NO afferent lymphatic vessels. Antigens enter directly through the overlying epithelium/crypts. Efferent lymphatics are present."],
["Germinal centres",
"Pale-staining zones within dark lymphoid nodules. Contain centroblasts (large, dividing B cells), centrocytes, follicular dendritic cells, and tingible body macrophages (starry-sky appearance in reactive GCs)."],
["Capsule mnemonic",
"P-P-L rule: Palatine → Partial (hemi)capsule | Pharyngeal → Partial (incomplete) | Lingual → Lacks capsule entirely"],
["Epithelium mnemonic",
"Pharyngeal = P for Pseudostratified (respiratory) – faces the airway. Palatine & Lingual = Squamous – faces food/swallowing."],
["Crypt complexity",
"Palatine > Lingual > Pharyngeal. Palatine crypts are deep and branching, making them prone to harbouring debris and bacteria."],
]
key_rows_tbl_data = []
for feature, desc in key_data:
key_rows_tbl_data.append([
Paragraph(feature, KEY_BOLD),
Paragraph(desc, KEY_S),
])
key_tbl = Table(key_rows_tbl_data, colWidths=[BODY_W*0.22, BODY_W*0.78])
key_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LIGHT_BLUE),
("BACKGROUND", (1,0), (1,-1), WHITE),
("ROWBACKGROUNDS",(1,0), (1,-1), [WHITE, PALE_GREY]),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-2), 0.3, colors.HexColor("#c0cce0")),
("BOX", (0,0), (-1,-1), 0.5, MID_BLUE),
]))
story.append(key_tbl)
story.append(Spacer(1, 8))
# ── FOOTER ───────────────────────────────────────────────────────────────────
story.append(HRFlowable(width=BODY_W, thickness=0.5, color=ACCENT_GOLD))
story.append(Spacer(1, 3))
story.append(Paragraph(
"Sources: Histology: A Text and Atlas with Correlated Cell and Molecular Biology (Ross & Pawlina), pp. 1256–1259 · "
"Scott-Brown's Otorhinolaryngology Head & Neck Surgery · Orris Medical Library | Generated June 2026",
FOOT_S
))
# ── BUILD ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved to: {PATH}")
cd /home/daytona/workspace/tonsil-pdf && python3 build_pdf.py 2>&1
ls -lh /home/daytona/workspace/tonsil-pdf/Tonsil_Histology_Reference.pdf
Tonsil Histology Reference
PDF Document · PDF