~/neoplasia-summary/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import ListFlowable, ListItem
OUTPUT = "/home/daytona/workspace/neoplasia-summary/Neoplasia_Summary.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2.2*cm, bottomMargin=2.2*cm,
title="Neoplasia - Medical Student Summary",
author="Orris Medical AI"
)
W = A4[0] - 4*cm # usable width
# ── Colour palette ────────────────────────────────────────────────
NAVY = colors.HexColor("#0B2545")
TEAL = colors.HexColor("#1A6E8E")
LIGHT = colors.HexColor("#E8F4F8")
ACCENT = colors.HexColor("#D64045")
YELLOW = colors.HexColor("#FFF3CD")
GREEN = colors.HexColor("#E8F5E9")
GREY = colors.HexColor("#F5F5F5")
WHITE = colors.white
DARK = colors.HexColor("#1C1C1C")
styles = getSampleStyleSheet()
# ── Custom styles ─────────────────────────────────────────────────
TITLE_STYLE = ParagraphStyle(
"DocTitle", parent=styles["Title"],
fontSize=28, textColor=WHITE, spaceAfter=4,
alignment=TA_CENTER, fontName="Helvetica-Bold", leading=34
)
SUBTITLE_STYLE = ParagraphStyle(
"DocSubtitle", parent=styles["Normal"],
fontSize=13, textColor=colors.HexColor("#B8D4E0"),
alignment=TA_CENTER, fontName="Helvetica", spaceAfter=0
)
H1 = ParagraphStyle(
"H1", parent=styles["Heading1"],
fontSize=14, textColor=WHITE, fontName="Helvetica-Bold",
spaceBefore=14, spaceAfter=6, leading=18,
backColor=NAVY, leftIndent=-6, rightIndent=-6,
borderPadding=(4, 6, 4, 6)
)
H2 = ParagraphStyle(
"H2", parent=styles["Heading2"],
fontSize=11.5, textColor=NAVY, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=4, leading=15,
borderPadding=(0,0,2,0)
)
BODY = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=9.5, textColor=DARK, fontName="Helvetica",
spaceAfter=5, leading=14, alignment=TA_JUSTIFY
)
BODY_BOLD = ParagraphStyle(
"BodyBold", parent=BODY,
fontName="Helvetica-Bold"
)
BULLET = ParagraphStyle(
"Bullet", parent=BODY,
leftIndent=14, bulletIndent=4, spaceAfter=3
)
CAPTION = ParagraphStyle(
"Caption", parent=BODY,
fontSize=8, textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, fontName="Helvetica-Oblique", spaceAfter=6
)
FOOTER_STYLE = ParagraphStyle(
"Footer", parent=BODY,
fontSize=7.5, textColor=colors.HexColor("#888888"),
alignment=TA_CENTER
)
def h1(text):
return Paragraph(text, H1)
def h2(text):
return Paragraph(text, H2)
def body(text):
return Paragraph(text, BODY)
def bullet(items, bullet_char="•"):
return [Paragraph(f"<b>{bullet_char}</b> {i}", BULLET) for i in items]
def space(n=6):
return Spacer(1, n)
def rule(color=TEAL, thickness=0.8):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=4, spaceBefore=2)
def make_table(header_row, data_rows, col_widths=None, header_bg=NAVY, alt_bg=LIGHT):
"""Build a styled table."""
all_rows = [header_row] + data_rows
if col_widths is None:
n = len(header_row)
col_widths = [W / n] * n
tbl = Table(all_rows, colWidths=col_widths, repeatRows=1)
style = [
# Header
("BACKGROUND", (0,0), (-1,0), header_bg),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
# Body text
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("TEXTCOLOR", (0,1), (-1,-1), DARK),
# Grid
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("LINEBELOW", (0,0), (-1,0), 1.0, TEAL),
]
# Alternating rows
for i in range(1, len(all_rows)):
bg = alt_bg if i % 2 == 0 else WHITE
style.append(("BACKGROUND", (0,i), (-1,i), bg))
tbl.setStyle(TableStyle(style))
return tbl
def callout_box(text, bg=YELLOW, border=ACCENT):
data = [[Paragraph(text, ParagraphStyle("cb", parent=BODY, fontSize=9, leading=13))]]
t = Table(data, colWidths=[W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING",(0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("LINEONSIDES", (0,0),(0,-1), 3, border),
("BOX", (0,0),(-1,-1), 0.5, border),
]))
return t
# ─────────────────────────────────────────────────────────────────
# TITLE BLOCK (dark banner)
# ─────────────────────────────────────────────────────────────────
def title_banner():
data = [[
Paragraph("NEOPLASIA", TITLE_STYLE),
Paragraph("A Comprehensive Medical Student Summary", SUBTITLE_STYLE),
Paragraph("Based on Robbins & Kumar Pathologic Basis of Disease", SUBTITLE_STYLE),
]]
t = Table([[
Paragraph("NEOPLASIA", TITLE_STYLE),
]], colWidths=[W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 18),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
("BOX", (0,0), (-1,-1), 2, TEAL),
]))
sub = Table([[
Paragraph("A Comprehensive Medical Student Summary | Based on Robbins & Kumar Pathologic Basis of Disease", SUBTITLE_STYLE),
]], colWidths=[W])
sub.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING",(0,0),(-1,-1), 7),
]))
return [t, sub, space(10)]
# ─────────────────────────────────────────────────────────────────
# BUILD STORY
# ─────────────────────────────────────────────────────────────────
story = []
story += title_banner()
# ══════════════════════════════════════════════════════════════════
# 1. DEFINITION
# ══════════════════════════════════════════════════════════════════
story.append(h1("1. DEFINITION & TERMINOLOGY"))
story.append(space(4))
story.append(body(
"<b>Neoplasia</b> (Greek: <i>neos</i> = new, <i>plasia</i> = growth) is defined as a disorder of cell "
"growth triggered by acquired — or, less commonly, inherited — mutations affecting a single cell and "
"its clonal progeny. These mutations give neoplastic cells a growth advantage, resulting in "
"excessive proliferation <b>independent of physiologic growth signals and controls.</b>"
))
story.append(space(4))
terms = [
["Term", "Meaning"],
["Neoplasm / Tumor", "Any abnormal, uncontrolled, purposeless new growth of cells"],
["Oncology", "Study of tumors (Greek: oncos = tumor)"],
["Parenchyma", "The neoplastic cells — determine tumor classification and behavior"],
["Stroma", "Reactive connective tissue, vessels, and immune cells — support tumor growth"],
["Desmoplasia", "Abundant collagen deposition stimulated by tumor (e.g., scirrhous breast cancer)"],
["Anaplasia", "Loss of differentiation — hallmark of malignancy"],
]
story.append(make_table(terms[0], terms[1:], col_widths=[W*0.3, W*0.7]))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 2. NOMENCLATURE
# ══════════════════════════════════════════════════════════════════
story.append(h1("2. NOMENCLATURE"))
story.append(space(4))
story.append(h2("Benign Tumors — suffix \"-oma\""))
benign = [
["Cell / Tissue of Origin", "Benign Tumor Name"],
["Fibroblast", "Fibroma"],
["Cartilage", "Chondroma"],
["Bone", "Osteoma"],
["Smooth muscle", "Leiomyoma"],
["Fat", "Lipoma"],
["Blood vessel", "Hemangioma"],
["Glandular epithelium", "Adenoma"],
["Epithelial surface (wart-like projections)", "Papilloma"],
["Glandular cystic mass", "Cystadenoma"],
]
story.append(make_table(benign[0], benign[1:], col_widths=[W*0.55, W*0.45]))
story.append(space(8))
story.append(h2("Malignant Tumors"))
malignant = [
["Cell of Origin", "Malignant Tumor Name"],
["Epithelium (any)", "Carcinoma"],
["Glandular epithelium", "Adenocarcinoma"],
["Squamous epithelium", "Squamous cell carcinoma"],
["Fibroblast", "Fibrosarcoma"],
["Smooth muscle", "Leiomyosarcoma"],
["Cartilage", "Chondrosarcoma"],
["Bone", "Osteosarcoma"],
["Melanocyte", "Melanoma"],
["Lymphocyte", "Lymphoma"],
["Plasma cell", "Multiple myeloma"],
["Hepatocyte", "Hepatocellular carcinoma"],
]
story.append(make_table(malignant[0], malignant[1:], col_widths=[W*0.55, W*0.45]))
story.append(space(4))
story.append(callout_box(
"<b>Important Exceptions:</b> Lymphoma, melanoma, and mesothelioma all end in -oma but are "
"<b>malignant.</b> A teratoma is derived from all three germ layers. A hamartoma is "
"disorganized but mature tissue — not truly neoplastic.",
bg=YELLOW, border=ACCENT
))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 3. BENIGN vs. MALIGNANT
# ══════════════════════════════════════════════════════════════════
story.append(h1("3. BENIGN vs. MALIGNANT: KEY DIFFERENCES"))
story.append(space(4))
bvm = [
["Feature", "Benign", "Malignant"],
["Differentiation", "Well differentiated", "Poorly differentiated / anaplastic"],
["Resemblance to origin", "Close", "Distant or absent"],
["Growth rate", "Slow", "Rapid (variable)"],
["Borders / Capsule", "Circumscribed, often encapsulated", "Poorly demarcated, no capsule"],
["Local invasion", "No — expands by compression", "Yes — infiltrates surrounding tissue"],
["Metastasis", "Never", "Hallmark feature"],
["Necrosis / hemorrhage", "Rare", "Common in rapidly growing tumors"],
["Nuclear morphology", "Normal", "Hyperchromatic, enlarged, irregular"],
["Mitoses", "Rare, normal", "Frequent, often atypical"],
["Prognosis", "Usually excellent", "Variable; often poor without treatment"],
]
story.append(make_table(bvm[0], bvm[1:], col_widths=[W*0.32, W*0.34, W*0.34]))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 4. MORPHOLOGIC FEATURES OF MALIGNANCY
# ══════════════════════════════════════════════════════════════════
story.append(h1("4. MORPHOLOGIC FEATURES OF MALIGNANCY (ANAPLASIA)"))
story.append(space(4))
story.append(body(
"The following cytologic features are used to recognize malignancy on histopathology:"
))
feats = [
"<b>Pleomorphism:</b> Variation in cell and nuclear size/shape",
"<b>Abnormal nuclear morphology:</b> Large nuclei; N:C ratio approaches 1:1 (normal 1:4–1:6); hyperchromatic, coarsely clumped chromatin; large nucleoli",
"<b>Atypical mitoses:</b> Bizarre tripolar/quadripolar spindle figures (more significant than just increased mitoses)",
"<b>Tumor giant cells:</b> Single huge polymorphic nucleus or multiple large hyperchromatic nuclei",
"<b>Loss of polarity:</b> Cells grow in disorganized sheets, losing normal orientation to stroma/basement membrane",
"<b>Areas of necrosis:</b> Central necrosis due to outgrowth of vascular supply",
]
story += bullet(feats)
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 5. PRE-MALIGNANT LESIONS
# ══════════════════════════════════════════════════════════════════
story.append(h1("5. PRE-MALIGNANT LESIONS"))
story.append(space(4))
premalig = [
["Term", "Definition", "Clinical Example"],
["Metaplasia", "Replacement of one mature cell type by another (adaptation to chronic injury)", "Barrett esophagus: squamous → columnar epithelium from acid reflux"],
["Dysplasia", "Disordered growth: pleomorphism, hyperchromasia, loss of polarity, mitoses above basal layer", "Cervical intraepithelial neoplasia (CIN) from HPV"],
["Carcinoma in situ (CIS)", "Full-thickness dysplasia without breach of basement membrane", "DCIS of breast; CIS of cervix"],
]
story.append(make_table(premalig[0], premalig[1:], col_widths=[W*0.2, W*0.38, W*0.42]))
story.append(space(4))
story.append(callout_box(
"<b>Key Point:</b> Dysplasia does <i>not</i> inevitably progress to cancer — it may regress "
"if the causative stimulus is removed (e.g., smoking cessation in bronchial dysplasia).",
bg=GREEN, border=TEAL
))
story.append(space(8))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════
# 6. METASTASIS
# ══════════════════════════════════════════════════════════════════
story.append(h1("6. METASTASIS"))
story.append(space(4))
story.append(body(
"Metastasis — spread to distant, non-contiguous sites — is the <b>single most reliable indicator "
"of malignancy</b> and the cause of most cancer deaths."
))
story.append(space(4))
story.append(h2("Routes of Spread"))
routes = [
["Route", "Typical Tumors", "Common Secondary Sites"],
["Lymphatic", "Carcinomas (most)", "Regional lymph nodes first, then distant nodes"],
["Hematogenous", "Sarcomas; also carcinomas (late)", "Liver (portal), Lung (systemic), Bone, Brain, Adrenal"],
["Direct seeding", "Ovarian carcinoma, GI tumors", "Peritoneal cavity, pleural cavity"],
["Perineural spread", "Prostate, head & neck carcinomas", "Along nerve sheaths to distant sites"],
["Transcoelomic", "Mesothelioma, ovarian cancer", "Pleura, pericardium, peritoneum"],
]
story.append(make_table(routes[0], routes[1:], col_widths=[W*0.22, W*0.32, W*0.46]))
story.append(space(6))
story.append(h2("Steps in Metastatic Cascade"))
steps = [
"<b>1. Local invasion</b> of basement membrane and ECM (matrix metalloproteases, EMT)",
"<b>2. Intravasation</b> — entry into blood or lymphatic vessels",
"<b>3. Survival in circulation</b> — resistance to shear stress, immune attack",
"<b>4. Arrest and extravasation</b> — at distant capillary bed",
"<b>5. Colonization</b> — formation of micrometastases; depends on organ microenvironment (\"seed and soil\" theory)",
]
story += bullet(steps, bullet_char="→")
story.append(space(4))
story.append(callout_box(
"<b>Carcinomas</b> spread predominantly via <b>lymphatics</b>. "
"<b>Sarcomas</b> spread predominantly via the <b>hematogenous route</b>.",
bg=LIGHT, border=NAVY
))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 7. HALLMARKS OF CANCER
# ══════════════════════════════════════════════════════════════════
story.append(h1("7. HALLMARKS OF CANCER (Hanahan & Weinberg)"))
story.append(space(4))
story.append(body(
"Cancer cells acquire a set of biologic capabilities that distinguish them from normal cells. "
"These hallmarks form the basis for targeted cancer therapies."
))
story.append(space(4))
hallmarks = [
["Hallmark", "Mechanism", "Key Genes / Examples"],
["1. Self-sufficiency in growth signals", "Oncogene activation → constitutive growth signals without external stimuli", "RAS, EGFR, HER2, MYC, BCR-ABL"],
["2. Insensitivity to growth-inhibitory signals", "Tumor suppressor gene inactivation", "RB, TP53, APC, SMAD4, CDKN2A"],
["3. Evasion of apoptosis", "Resistance to programmed cell death", "BCL-2 overexpression; TP53 loss; IAP proteins"],
["4. Limitless replicative potential (immortality)", "Telomerase upregulation prevents telomere shortening", "TERT activation; ALT pathway"],
["5. Sustained angiogenesis", "Tumor-induced new vessel formation", "VEGF overexpression; VEGFR signaling"],
["6. Invasion and metastasis", "EMT; matrix protease activity; loss of E-cadherin", "MMP2, MMP9; TWIST; SNAIL; CDH1 loss"],
["7. Altered cellular metabolism (Warburg effect)", "Switch to aerobic glycolysis for biosynthetic needs", "HIF-1α; IDH1/2 mutations; mTOR pathway"],
["8. Evasion of immune surveillance", "Immune checkpoint exploitation; antigen loss", "PD-L1/PD-1; CTLA-4; MHC-I downregulation"],
]
story.append(make_table(hallmarks[0], hallmarks[1:], col_widths=[W*0.26, W*0.40, W*0.34]))
story.append(space(4))
story.append(body(
"<b>Enabling characteristics</b> that accelerate acquisition of the above: "
"(a) <b>Genomic instability</b> (mutator phenotype) and (b) <b>Tumor-promoting inflammation.</b>"
))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 8. MOLECULAR BASIS
# ══════════════════════════════════════════════════════════════════
story.append(h1("8. MOLECULAR BASIS: ONCOGENES & TUMOR SUPPRESSORS"))
story.append(space(4))
story.append(h2("Oncogenes — Gain-of-Function Mutations"))
story.append(body(
"Proto-oncogenes are normal cellular genes that regulate growth. Mutations convert them to "
"<b>oncogenes</b>, which drive excessive proliferation. Mechanisms include point mutation, "
"gene amplification, chromosomal translocation, and overexpression."
))
onco = [
["Category", "Example Oncogene", "Cancer Association"],
["Growth factors", "PDGF-B", "Astrocytoma, fibrosarcoma"],
["Growth factor receptors", "ERBB2 (HER2)", "Breast, gastric cancer"],
["Signal transducers (GTPase)", "RAS (KRAS, NRAS, HRAS)", "Colorectal, pancreatic, lung (~30% of all cancers)"],
["Non-receptor tyrosine kinase", "ABL", "CML (t(9;22) — Philadelphia chromosome)"],
["Transcription factors", "MYC (c-MYC)", "Burkitt lymphoma; many cancers"],
["Cell cycle regulators", "Cyclin D1 (CCND1)", "Breast, head & neck cancers"],
["Anti-apoptotic proteins", "BCL-2", "Follicular lymphoma (t(14;18))"],
]
story.append(make_table(onco[0], onco[1:], col_widths=[W*0.28, W*0.28, W*0.44]))
story.append(space(8))
story.append(h2("Tumor Suppressor Genes — Loss-of-Function (Knudson Two-Hit Hypothesis)"))
story.append(body(
"Both alleles must be inactivated for tumor suppression to be lost. "
"In hereditary cancers, one mutant allele is inherited (first hit); somatic loss of the second "
"allele (second hit) causes disease."
))
story.append(space(4))
tsg = [
["Gene", "Normal Function", "Cancer Association"],
["RB (13q14)", "\"Governor of the Cell Cycle\" — binds E2F to block S-phase entry; phosphorylation by cyclin D/CDK releases E2F", "Retinoblastoma; osteosarcoma; many carcinomas"],
["TP53 (17p13)", "\"Guardian of the Genome\" — activates DNA repair, G1 arrest, or apoptosis in response to DNA damage", ">50% of all human cancers; Li-Fraumeni syndrome"],
["APC (5q21)", "Destroys β-catenin; inhibits Wnt signaling and cell proliferation", "Familial adenomatous polyposis (FAP); colorectal cancer"],
["BRCA1 / BRCA2", "DNA double-strand break repair (homologous recombination)", "Hereditary breast and ovarian cancer"],
["CDKN2A (p16)", "Inhibits CDK4/6 → prevents Rb phosphorylation", "Melanoma, pancreatic cancer"],
["SMAD2 / SMAD4", "TGF-β signaling — inhibits cell proliferation", "Pancreatic cancer; colorectal cancer"],
["VHL", "Regulates HIF-1α; promotes angiogenesis when mutated", "Clear cell renal cell carcinoma; VHL disease"],
]
story.append(make_table(tsg[0], tsg[1:], col_widths=[W*0.2, W*0.44, W*0.36]))
story.append(space(8))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════
# 9. CARCINOGENESIS
# ══════════════════════════════════════════════════════════════════
story.append(h1("9. CARCINOGENESIS: A MULTISTEP PROCESS"))
story.append(space(4))
story.append(body(
"Cancer develops through the <b>accumulation of multiple mutations</b> over time. "
"No single mutation is sufficient; instead, a progressive series of genetic alterations leads "
"from normal cell to invasive cancer. This is best illustrated by the colorectal cancer model:"
))
story.append(space(4))
# Pathway diagram as table
pathway = [
["Normal Epithelium", "→", "Early Adenoma", "→", "Intermediate Adenoma", "→", "Late Adenoma", "→", "Carcinoma"],
["", "", "APC loss", "", "KRAS mutation", "", "SMAD4 loss", "", "TP53 mutation"],
]
p_style = ParagraphStyle("pway", parent=BODY, fontSize=8.5, alignment=TA_CENTER, textColor=NAVY, fontName="Helvetica-Bold")
p_arrow = ParagraphStyle("arrow", parent=BODY, fontSize=12, alignment=TA_CENTER, textColor=TEAL, fontName="Helvetica-Bold")
p_mut = ParagraphStyle("mut", parent=BODY, fontSize=8, alignment=TA_CENTER, textColor=ACCENT, fontName="Helvetica-Oblique")
pdata = [
[Paragraph("Normal\nEpithelium", p_style), Paragraph("→", p_arrow),
Paragraph("Early\nAdenoma", p_style), Paragraph("→", p_arrow),
Paragraph("Intermediate\nAdenoma", p_style), Paragraph("→", p_arrow),
Paragraph("Late\nAdenoma", p_style), Paragraph("→", p_arrow),
Paragraph("Carcinoma\n(→ Metastasis)", p_style)],
[Paragraph("", p_mut), Paragraph("", p_mut),
Paragraph("APC loss", p_mut), Paragraph("", p_mut),
Paragraph("KRAS mutation", p_mut), Paragraph("", p_mut),
Paragraph("SMAD4 loss", p_mut), Paragraph("", p_mut),
Paragraph("TP53 mutation", p_mut)],
]
ptbl = Table(pdata, colWidths=[W*0.13, W*0.05, W*0.13, W*0.05, W*0.15, W*0.05, W*0.13, W*0.05, W*0.165])
ptbl.setStyle(TableStyle([
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("BACKGROUND", (0,0), (0,0), LIGHT),
("BACKGROUND", (2,0), (2,0), colors.HexColor("#D5E8D4")),
("BACKGROUND", (4,0), (4,0), YELLOW),
("BACKGROUND", (6,0), (6,0), colors.HexColor("#FFE6CC")),
("BACKGROUND", (8,0), (8,0), colors.HexColor("#F8CECC")),
("BOX", (0,0), (0,1), 0.5, TEAL),
("BOX", (2,0), (2,1), 0.5, TEAL),
("BOX", (4,0), (4,1), 0.5, TEAL),
("BOX", (6,0), (6,1), 0.5, TEAL),
("BOX", (8,0), (8,1), 0.5, ACCENT),
]))
story.append(ptbl)
story.append(Paragraph("Colorectal cancer multistep carcinogenesis model (Vogelstein)", CAPTION))
story.append(space(4))
story.append(h2("Classical Stages of Chemical Carcinogenesis"))
stages = [
["Stage", "Description", "Key Features"],
["Initiation", "Irreversible DNA mutation caused by a carcinogen (initiator)", "Single exposure sufficient; affects a single cell; permanent"],
["Promotion", "Clonal expansion of initiated cells by a promoter", "Reversible; requires repeated/prolonged exposure; not mutagenic alone"],
["Progression", "Further mutations → malignant conversion, invasion, metastasis", "Irreversible; increasing genomic instability; karyotypic changes"],
]
story.append(make_table(stages[0], stages[1:], col_widths=[W*0.18, W*0.42, W*0.40]))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 10. EPIDEMIOLOGY & RISK FACTORS
# ══════════════════════════════════════════════════════════════════
story.append(h1("10. EPIDEMIOLOGY & RISK FACTORS"))
story.append(space(4))
story.append(body(
"In 2020, cancer caused over <b>9.9 million deaths worldwide</b> (~1 in 6 of all deaths). "
"By 2030, global cases are projected to reach <b>21.4 million</b>. Most common cancers: "
"<b>Males</b> — Prostate > Lung > Colorectal; "
"<b>Females</b> — Breast > Lung > Colorectal."
))
story.append(space(4))
risks = [
["Category", "Examples", "Associated Cancer"],
["Chemical carcinogens", "Tobacco smoke, aflatoxin B1, aromatic amines, benzene, vinyl chloride", "Lung, hepatocellular, bladder, leukemia, angiosarcoma"],
["Physical carcinogens", "UV radiation, ionizing radiation (X-ray, gamma, nuclear)", "Melanoma, skin SCC; leukemia, thyroid, breast"],
["Oncogenic viruses", "HPV 16/18, EBV, HBV/HCV, HTLV-1, KSHV (HHV-8)", "Cervical, NPC/Burkitt, HCC, ATL, Kaposi sarcoma"],
["Oncogenic bacteria", "H. pylori", "Gastric adenocarcinoma, MALT lymphoma"],
["Parasites", "Schistosoma haematobium, Opisthorchis viverrini", "Bladder carcinoma, cholangiocarcinoma"],
["Chronic inflammation", "IBD, Barrett esophagus, chronic pancreatitis, chronic hepatitis", "Colorectal, esophageal, pancreatic, hepatocellular"],
["Hereditary syndromes", "Germline TP53 (Li-Fraumeni), BRCA1/2, APC (FAP), MLH1/MSH2 (Lynch)", "Many; specific to gene involved"],
["Age", "Accumulation of somatic mutations over a lifetime", "Most carcinomas peak in 6th–8th decade"],
]
story.append(make_table(risks[0], risks[1:], col_widths=[W*0.23, W*0.42, W*0.35]))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 11. GRADING & STAGING
# ══════════════════════════════════════════════════════════════════
story.append(h1("11. GRADING & STAGING"))
story.append(space(4))
grade_stage = [
[
Paragraph("<b>GRADING</b> (Histopathologic — degree of differentiation)", H2),
Paragraph("<b>TNM STAGING</b> (Clinical — extent of spread)", H2),
],
[
Table([
[Paragraph("<b>Grade</b>", BODY_BOLD), Paragraph("<b>Description</b>", BODY_BOLD)],
[Paragraph("G1", BODY), Paragraph("Well differentiated (low grade)", BODY)],
[Paragraph("G2", BODY), Paragraph("Moderately differentiated", BODY)],
[Paragraph("G3", BODY), Paragraph("Poorly differentiated (high grade)", BODY)],
[Paragraph("G4", BODY), Paragraph("Undifferentiated / anaplastic", BODY)],
], colWidths=[W*0.12, W*0.34], style=TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
("FONTSIZE", (0,0),(-1,-1), 8.5),
])),
Table([
[Paragraph("<b>T</b>", BODY_BOLD), Paragraph("Primary tumor size/extent (T1–T4)", BODY)],
[Paragraph("<b>N</b>", BODY_BOLD), Paragraph("Regional lymph node spread (N0–N3)", BODY)],
[Paragraph("<b>M</b>", BODY_BOLD), Paragraph("Distant metastasis (M0 = absent, M1 = present)", BODY)],
[Paragraph("<b>Stage I–IV</b>", BODY_BOLD), Paragraph("Derived from T, N, M combination; guides prognosis and treatment", BODY)],
], colWidths=[W*0.13, W*0.35], style=TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
("FONTSIZE", (0,0),(-1,-1), 8.5),
])),
]
]
outer = Table(grade_stage, colWidths=[W*0.48, W*0.52])
outer.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0),(-1,-1), 4),
("RIGHTPADDING", (0,0),(-1,-1), 4),
("TOPPADDING", (0,0),(-1,-1), 4),
]))
story.append(outer)
story.append(space(4))
story.append(callout_box(
"<b>Clinical Pearl:</b> Staging is a <b>stronger predictor of prognosis</b> than grading. "
"A well-differentiated (G1) tumor that has metastasized (M1) carries a far worse prognosis "
"than a poorly differentiated (G3) tumor confined to its organ of origin.",
bg=LIGHT, border=TEAL
))
story.append(space(8))
# ══════════════════════════════════════════════════════════════════
# 12. QUICK REFERENCE SUMMARY
# ══════════════════════════════════════════════════════════════════
story.append(h1("12. QUICK REFERENCE SUMMARY"))
story.append(space(4))
summary = [
["Topic", "Key Points"],
["Definition", "Clonal, mutation-driven cell growth independent of physiologic signals"],
["Benign vs. Malignant", "Metastasis = only reliable criterion; also: differentiation, invasion, growth rate"],
["Anaplasia features", "Pleomorphism, hyperchromatic nuclei, atypical mitoses, giant cells, necrosis"],
["Pre-malignant lesions", "Metaplasia → Dysplasia → Carcinoma in situ → Invasive carcinoma"],
["Metastasis routes", "Carcinomas: lymphatic; Sarcomas: hematogenous; also seeding, perineural"],
["Hallmarks of cancer", "Growth self-sufficiency, growth inhibitor evasion, apoptosis resistance, immortality, angiogenesis, invasion, Warburg effect, immune evasion"],
["Key oncogenes", "RAS (most common), MYC, ERBB2/HER2, BCL-2, ABL (CML), Cyclin D1"],
["Key tumor suppressors", "TP53 (>50% of cancers), RB (\"governor\"), APC (colon), BRCA1/2 (breast/ovary)"],
["Carcinogenesis", "Multistep: initiation → promotion → progression; requires multiple mutations"],
["Staging system", "TNM: T (tumor), N (nodes), M (metastasis); Stage I–IV; stronger prognostic than grade"],
["Common carcinogens", "Tobacco #1 cause; HPV → cervical; HBV/HCV → HCC; H. pylori → gastric"],
]
story.append(make_table(summary[0], summary[1:], col_widths=[W*0.28, W*0.72]))
story.append(space(10))
# Footer reference
story.append(rule(color=TEAL, thickness=0.5))
story.append(Paragraph(
"Source: Robbins, Cotran & Kumar — Pathologic Basis of Disease (10th ed.) | "
"Robbins & Kumar Basic Pathology (11th ed.) | "
"Hanahan D, Weinberg RA. Hallmarks of Cancer: The Next Generation. Cell. 2011;144:646. | "
"Generated by Orris Medical AI",
FOOTER_STYLE
))
# ─────────────────────────────────────────────────────────────────
# BUILD
# ─────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")