Make me a presentation on gastric carcinoma from textbook of Bailay and love 28th edition, withh all classifications and important content with images, make it in a pdf form

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Finding Sources
Reading File
Finding Sources
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Loading Image
Reading File
Running Command

mkdir -p /home/daytona/workspace/gastric-carcinoma && ls /home/daytona/workspace/

Writing File

~/gastric-carcinoma/build_pdf.py

#!/usr/bin/env python3
"""
Gastric Carcinoma Presentation PDF
Source: Bailey and Love's Short Practice of Surgery, 28th Edition
"""

import urllib.request
import os
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_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    Image, PageBreak, HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas
from io import BytesIO

# ── Colour palette ────────────────────────────────────────────────────────────
DARK_BLUE   = colors.HexColor("#1a3a5c")
MID_BLUE    = colors.HexColor("#2563a8")
ACCENT_BLUE = colors.HexColor("#3b82c4")
LIGHT_BLUE  = colors.HexColor("#dbeafe")
GOLD        = colors.HexColor("#d97706")
LIGHT_GOLD  = colors.HexColor("#fef3c7")
WHITE       = colors.white
DARK_GREY   = colors.HexColor("#1f2937")
MID_GREY    = colors.HexColor("#6b7280")
LIGHT_GREY  = colors.HexColor("#f3f4f6")
RED         = colors.HexColor("#dc2626")
GREEN       = colors.HexColor("#16a34a")

W, H = A4  # 595.28 x 841.89 pts

OUT_DIR = "/home/daytona/workspace/gastric-carcinoma"
IMG_DIR = os.path.join(OUT_DIR, "images")
os.makedirs(IMG_DIR, exist_ok=True)

# ── Download images ────────────────────────────────────────────────────────────
IMAGES = {
    "early_gastric": "https://cdn.orris.care/cdss_images/89aa76f152d4f848dcd4b9d0f6f21d21c75988950663b7788ae9fb5240202b89.png",
    "borrmann_diagram": "https://cdn.orris.care/cdss_images/e2a9544ef8bd7315ec013638d703fbc78ca4e6e4a2c8c46a3647d276220ef9e6.png",
    "endoscopy_early_a": "https://cdn.orris.care/cdss_images/d0b26380e5fc4c55144711efaad3a786a8e302557a2b6855ac29652a4941fe6c.png",
    "endoscopy_early_b": "https://cdn.orris.care/cdss_images/15e91c26ea70521a3fb46289687eb1b600b49063cb0d62009bdc355dc4282a23.png",
}

img_paths = {}
for name, url in IMAGES.items():
    path = os.path.join(IMG_DIR, f"{name}.png")
    if not os.path.exists(path):
        try:
            urllib.request.urlretrieve(url, path)
            print(f"Downloaded {name}")
        except Exception as e:
            print(f"Failed to download {name}: {e}")
    else:
        print(f"Already have {name}")
    img_paths[name] = path


# ── Custom Flowables ──────────────────────────────────────────────────────────
class SlideDivider(Flowable):
    """Full-width decorative divider."""
    def __init__(self, width=None, color=MID_BLUE, thickness=2):
        super().__init__()
        self.width = width or (W - 4*cm)
        self.color = color
        self.thickness = thickness
        self.height = self.thickness + 2

    def draw(self):
        self.canv.setStrokeColor(self.color)
        self.canv.setLineWidth(self.thickness)
        self.canv.line(0, self.thickness/2, self.width, self.thickness/2)


class ColorBox(Flowable):
    """Coloured background box with text."""
    def __init__(self, text, bg=DARK_BLUE, fg=WHITE, width=None, height=50, font_size=18, font="Helvetica-Bold"):
        super().__init__()
        self.text = text
        self.bg = bg
        self.fg = fg
        self.width = width or (W - 4*cm)
        self.height = height
        self.font_size = font_size
        self.font = font

    def draw(self):
        c = self.canv
        c.setFillColor(self.bg)
        c.roundRect(0, 0, self.width, self.height, 6, fill=1, stroke=0)
        c.setFillColor(self.fg)
        c.setFont(self.font, self.font_size)
        c.drawCentredString(self.width / 2, (self.height - self.font_size) / 2 + 2, self.text)


# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

def make_style(name, parent="Normal", **kw):
    return ParagraphStyle(name, parent=styles[parent], **kw)

title_style = make_style("MyTitle",
    fontName="Helvetica-Bold", fontSize=28, textColor=WHITE,
    alignment=TA_CENTER, spaceAfter=6, leading=34)

subtitle_style = make_style("MySubtitle",
    fontName="Helvetica", fontSize=14, textColor=LIGHT_BLUE,
    alignment=TA_CENTER, spaceAfter=4, leading=18)

source_style = make_style("MySource",
    fontName="Helvetica-Oblique", fontSize=11, textColor=LIGHT_GOLD,
    alignment=TA_CENTER, spaceAfter=4)

slide_heading = make_style("SlideHeading",
    fontName="Helvetica-Bold", fontSize=20, textColor=DARK_BLUE,
    spaceBefore=8, spaceAfter=8, leading=24)

section_heading = make_style("SectionHeading",
    fontName="Helvetica-Bold", fontSize=15, textColor=MID_BLUE,
    spaceBefore=10, spaceAfter=5, leading=19)

body_style = make_style("Body",
    fontName="Helvetica", fontSize=11, textColor=DARK_GREY,
    spaceAfter=4, leading=16, alignment=TA_JUSTIFY)

bullet_style = make_style("Bullet",
    fontName="Helvetica", fontSize=11, textColor=DARK_GREY,
    spaceAfter=3, leading=15, leftIndent=14, firstLineIndent=0)

caption_style = make_style("Caption",
    fontName="Helvetica-Oblique", fontSize=9, textColor=MID_GREY,
    alignment=TA_CENTER, spaceAfter=6, leading=12)

table_header_style = make_style("TableHeader",
    fontName="Helvetica-Bold", fontSize=10, textColor=WHITE,
    alignment=TA_CENTER, leading=13)

table_cell_style = make_style("TableCell",
    fontName="Helvetica", fontSize=10, textColor=DARK_GREY,
    alignment=TA_LEFT, leading=13)

key_point_style = make_style("KeyPoint",
    fontName="Helvetica-Bold", fontSize=11, textColor=DARK_BLUE,
    spaceAfter=3, leading=15, leftIndent=14)

warning_style = make_style("Warning",
    fontName="Helvetica-Bold", fontSize=11, textColor=RED,
    spaceAfter=3, leading=15)

# ── Helper functions ──────────────────────────────────────────────────────────
def bullet(text, color=MID_BLUE, bold_prefix=None):
    """Return a bullet paragraph."""
    bullet_char = "•"
    if bold_prefix:
        text = f"<b>{bold_prefix}</b> {text}"
    return Paragraph(f'<font color="{color.hexval()}">{bullet_char}</font>  {text}', bullet_style)

def sub_bullet(text):
    st = make_style("SubBullet", fontName="Helvetica", fontSize=10.5, textColor=DARK_GREY,
                    spaceAfter=2, leading=14, leftIndent=28, firstLineIndent=0)
    return Paragraph(f'<font color="{MID_GREY.hexval()}">◦</font>  {text}', st)

def slide_title(text):
    return ColorBox(text, bg=DARK_BLUE, fg=WHITE, height=52, font_size=20)

def section_divider_title(text):
    return ColorBox(text, bg=MID_BLUE, fg=WHITE, height=44, font_size=17)

def info_box(text, bg=LIGHT_BLUE, fg=DARK_BLUE):
    st = make_style("InfoBox", fontName="Helvetica", fontSize=10.5, textColor=fg,
                    leading=15, leftIndent=8, rightIndent=8)
    tbl = Table([[Paragraph(text, st)]], colWidths=[W - 4*cm])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), bg),
        ("ROUNDEDCORNERS", [6]),
        ("BOX", (0,0), (-1,-1), 1, ACCENT_BLUE),
        ("TOPPADDING", (0,0), (-1,-1), 8),
        ("BOTTOMPADDING", (0,0), (-1,-1), 8),
        ("LEFTPADDING", (0,0), (-1,-1), 10),
        ("RIGHTPADDING", (0,0), (-1,-1), 10),
    ]))
    return tbl

def image_with_caption(path, caption, max_w=None, max_h=None):
    items = []
    try:
        max_w = max_w or (W - 6*cm)
        max_h = max_h or 10*cm
        img = Image(path, width=max_w, height=max_h, kind="proportional")
        items.append(img)
        items.append(Paragraph(caption, caption_style))
    except Exception as e:
        items.append(Paragraph(f"[Image: {caption}]", caption_style))
    return items

def two_col_images(path1, cap1, path2, cap2, col_w=None):
    col_w = col_w or ((W - 5*cm) / 2)
    try:
        i1 = Image(path1, width=col_w, height=9*cm, kind="proportional")
    except:
        i1 = Paragraph(f"[{cap1}]", caption_style)
    try:
        i2 = Image(path2, width=col_w, height=9*cm, kind="proportional")
    except:
        i2 = Paragraph(f"[{cap2}]", caption_style)

    tbl = Table(
        [[i1, i2],
         [Paragraph(cap1, caption_style), Paragraph(cap2, caption_style)]],
        colWidths=[col_w + 0.2*cm, col_w + 0.2*cm]
    )
    tbl.setStyle(TableStyle([
        ("ALIGN", (0,0), (-1,-1), "CENTER"),
        ("VALIGN", (0,0), (-1,-1), "TOP"),
        ("LEFTPADDING", (0,0), (-1,-1), 4),
        ("RIGHTPADDING", (0,0), (-1,-1), 4),
        ("TOPPADDING", (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ]))
    return tbl


def std_table(headers, rows, col_widths=None):
    header_row = [Paragraph(h, table_header_style) for h in headers]
    data = [header_row]
    for r in rows:
        data.append([Paragraph(str(c), table_cell_style) for c in r])
    if col_widths is None:
        col_widths = [(W - 4*cm) / len(headers)] * len(headers)
    tbl = Table(data, colWidths=col_widths, repeatRows=1)
    style = [
        ("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
        ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GREY]),
        ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d1d5db")),
        ("BOX", (0,0), (-1,-1), 1, MID_BLUE),
        ("TOPPADDING", (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("RIGHTPADDING", (0,0), (-1,-1), 8),
        ("VALIGN", (0,0), (-1,-1), "TOP"),
    ]
    tbl.setStyle(TableStyle(style))
    return tbl

sp = lambda n=1: Spacer(1, n * 0.3*cm)
hr = lambda: SlideDivider(color=ACCENT_BLUE, thickness=1)


# ── Page template with header/footer ─────────────────────────────────────────
class PresentationCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._saved_page_states = []

    def showPage(self):
        self._saved_page_states.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_decoration(num_pages)
            super().showPage()
        super().save()

    def draw_page_decoration(self, total):
        page_num = self._pageNumber
        # Top bar
        self.setFillColor(DARK_BLUE)
        self.rect(0, H - 22, W, 22, fill=1, stroke=0)
        self.setFillColor(GOLD)
        self.setFont("Helvetica-Bold", 8)
        self.drawString(1.5*cm, H - 15, "GASTRIC CARCINOMA")
        self.setFillColor(colors.HexColor("#93c5fd"))
        self.setFont("Helvetica", 8)
        self.drawRightString(W - 1.5*cm, H - 15, "Bailey & Love's Short Practice of Surgery, 28th Ed.")
        # Bottom bar
        self.setFillColor(DARK_BLUE)
        self.rect(0, 0, W, 20, fill=1, stroke=0)
        self.setFillColor(WHITE)
        self.setFont("Helvetica", 8)
        if page_num > 1:
            self.drawCentredString(W/2, 6, f"Page {page_num} of {total}")


def on_first_page(canvas, doc):
    pass

def on_later_pages(canvas, doc):
    pass


# ═══════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ═══════════════════════════════════════════════════════════════════════════════
story = []

# ────────────────────────────────────────────────────────────────────────────
# SLIDE 1: TITLE PAGE
# ────────────────────────────────────────────────────────────────────────────
story.append(Spacer(1, 2.5*cm))

title_tbl = Table(
    [[Paragraph("GASTRIC CARCINOMA", title_style)],
     [Paragraph("A Comprehensive Overview", subtitle_style)],
     [Spacer(1, 0.4*cm)],
     [Paragraph("Source: Bailey and Love's Short Practice of Surgery, 28th Edition", source_style)],
     [Paragraph("Chapter 67 – Stomach and Duodenum", source_style)],
    ],
    colWidths=[W - 4*cm]
)
title_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("ROUNDEDCORNERS", [10]),
    ("TOPPADDING", (0,0), (-1,-1), 16),
    ("BOTTOMPADDING", (0,0), (-1,-1), 16),
    ("LEFTPADDING", (0,0), (-1,-1), 20),
    ("RIGHTPADDING", (0,0), (-1,-1), 20),
]))
story.append(title_tbl)

story.append(sp(2))

# Key stats highlight box
stats_data = [
    [Paragraph("<b>~15/100k</b>", make_style("s", fontName="Helvetica-Bold", fontSize=14, textColor=GOLD, alignment=TA_CENTER)),
     Paragraph("<b>5th</b>", make_style("s", fontName="Helvetica-Bold", fontSize=14, textColor=GOLD, alignment=TA_CENTER)),
     Paragraph("<b>~5–10%</b>", make_style("s", fontName="Helvetica-Bold", fontSize=14, textColor=GOLD, alignment=TA_CENTER)),
     Paragraph("<b>90%</b>", make_style("s", fontName="Helvetica-Bold", fontSize=14, textColor=GOLD, alignment=TA_CENTER))],
    [Paragraph("UK Incidence\n(per 100,000/yr)", make_style("ss", fontName="Helvetica", fontSize=9, textColor=WHITE, alignment=TA_CENTER)),
     Paragraph("Most Common\nCancer Worldwide", make_style("ss", fontName="Helvetica", fontSize=9, textColor=WHITE, alignment=TA_CENTER)),
     Paragraph("Overall\nCure Rate (West)", make_style("ss", fontName="Helvetica", fontSize=9, textColor=WHITE, alignment=TA_CENTER)),
     Paragraph("5-yr Survival in\nEarly Gastric CA", make_style("ss", fontName="Helvetica", fontSize=9, textColor=WHITE, alignment=TA_CENTER))],
]
stats_tbl = Table(stats_data, colWidths=[(W-4*cm)/4]*4)
stats_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#1e3a5f")),
    ("BOX", (0,0), (-1,-1), 1, ACCENT_BLUE),
    ("LINEAFTER", (0,0), (2,-1), 1, colors.HexColor("#3b82c4")),
    ("TOPPADDING", (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("ALIGN", (0,0), (-1,-1), "CENTER"),
    ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(stats_tbl)
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 2: TABLE OF CONTENTS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("TABLE OF CONTENTS"))
story.append(sp())

toc_items = [
    ("1", "Epidemiology & Incidence"),
    ("2", "Aetiology & Risk Factors"),
    ("3", "Clinical Features"),
    ("4", "Site of Origin"),
    ("5", "Pathology & Histology"),
    ("6", "Classifications (Laurén, Japanese EGC, Borrmann, TCGA Molecular)"),
    ("7", "Staging (TNM / UICC 8th Edition)"),
    ("8", "Spread of Gastric Carcinoma"),
    ("9", "Lymphatic Drainage"),
    ("10", "Investigations"),
    ("11", "Treatment – Surgery (Radical & Palliative)"),
    ("12", "Chemotherapy & Multimodal Therapy"),
    ("13", "Prognosis"),
]

toc_data = [[Paragraph(f'<b>{n}.</b>', make_style("TN", fontName="Helvetica-Bold", fontSize=12, textColor=GOLD)),
             Paragraph(t, make_style("TT", fontName="Helvetica", fontSize=12, textColor=DARK_GREY, leading=16))]
            for n, t in toc_items]

toc_tbl = Table(toc_data, colWidths=[1.2*cm, W - 4*cm - 1.2*cm])
toc_tbl.setStyle(TableStyle([
    ("ROWBACKGROUNDS", (0,0), (-1,-1), [WHITE, LIGHT_GREY]),
    ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#e5e7eb")),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(toc_tbl)
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 3: EPIDEMIOLOGY
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("1. EPIDEMIOLOGY & INCIDENCE"))
story.append(sp())

story.append(Paragraph("Global Overview", section_heading))
epi_data = [
    ["Region", "Incidence (per 100,000/yr)", "Notes"],
    ["Japan", "~70", "Highest; screening programmes in place"],
    ["China (select areas)", "Even higher", "Environmental/dietary factors dominant"],
    ["Eastern Europe", "~40", "High incidence zone"],
    ["United Kingdom", "~15", "Falling ~1%/yr; proximal shift occurring"],
    ["USA", "~10", "Lower incidence"],
]
story.append(std_table(epi_data[0], epi_data[1:], col_widths=[5*cm, 5.5*cm, W-4*cm-10.5*cm]))
story.append(sp())

story.append(Paragraph("Key Epidemiological Trends", section_heading))
story.append(bullet("Gastric cancer is one of the most common causes of cancer death worldwide."))
story.append(bullet("In the West, incidence is falling ~1%/year, exclusively affecting body and distal tumours."))
story.append(bullet("Proximal gastric cancer (near gastro-oesophageal junction, GOJ) is <b>increasing</b> in the West."))
story.append(bullet("Men are more affected than women; incidence rises with age."))
story.append(bullet("Distal/body cancers are more common in low socioeconomic groups; proximal cancers affect higher socioeconomic groups."))
story.append(bullet("Japan: ~1/3 of gastric cancers are diagnosed as early gastric cancer due to screening."))
story.append(sp())
story.append(info_box(
    "<b>Key Insight:</b> The geographical variation strongly suggests an <i>environmental aetiology</i>. "
    "Japanese families who migrate to the USA show declining incidence over generations, confirming dietary/environmental drivers."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 4: AETIOLOGY & RISK FACTORS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("2. AETIOLOGY & RISK FACTORS"))
story.append(sp())

story.append(Paragraph("Gastric cancer is multifactorial. Key risk factors include:", body_style))
story.append(sp(0.5))

rf_data = [
    ["Risk Factor", "Details"],
    ["H. pylori infection", "Associated with body & distal cancers (intestinal type). NOT associated with proximal cancer. Insufficient evidence for eradication in asymptomatic patients."],
    ["Pernicious anaemia & gastric atrophy", "Increased risk due to achlorhydria and intestinal metaplasia."],
    ["Gastric adenomatous polyps", "Pre-malignant lesions; require surveillance."],
    ["Previous peptic ulcer surgery", "Billroth II, Pólya gastrectomy, gastroenterostomy, pyloroplasty → 4× average risk. Related to bile reflux and intestinal metaplasia."],
    ["Cigarette smoking", "Established risk factor."],
    ["Industrial dust ingestion", "Various occupational exposures."],
    ["Diet", "Excessive salt intake, deficiency of antioxidants (Vit C, E), exposure to N-nitroso compounds. High nitrate diet."],
    ["Obesity", "Associated with proximal gastric cancer."],
    ["Intestinal metaplasia", "Hallmark precursor for intestinal-type gastric cancer."],
    ["Genetic / molecular factors", "CDH1 mutations (hereditary diffuse GC), BRCA2, TP53, ARID1A mutations."],
]
story.append(std_table(rf_data[0], rf_data[1:], col_widths=[5.5*cm, W-4*cm-5.5*cm]))
story.append(sp())
story.append(info_box(
    "<b>Correa Cascade (Intestinal Type):</b> Normal mucosa → Chronic gastritis (H. pylori) → "
    "Atrophic gastritis → Intestinal metaplasia → Dysplasia → Carcinoma"
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 5: CLINICAL FEATURES
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("3. CLINICAL FEATURES"))
story.append(sp())

story.append(Paragraph("Early Gastric Cancer", section_heading))
story.append(bullet("No specific features to distinguish from benign dyspepsia."))
story.append(bullet("High index of suspicion is required; endoscopy is key."))
story.append(bullet("Patients with alarm symptoms must be referred promptly."))
story.append(sp(0.5))

story.append(Paragraph("Advanced Gastric Cancer", section_heading))

cf_col = (W - 4*cm) / 2 - 0.2*cm
cf_data = [
    [Paragraph("<b>Symptoms</b>", make_style("ch", fontName="Helvetica-Bold", fontSize=12, textColor=WHITE, alignment=TA_CENTER)),
     Paragraph("<b>Signs & Special Features</b>", make_style("ch", fontName="Helvetica-Bold", fontSize=12, textColor=WHITE, alignment=TA_CENTER))],
    [
        "\n".join([
            "• Early satiety",
            "• Bloating and distension",
            "• Vomiting (esp. with pyloric involvement)",
            "• Dysphagia (proximal/GOJ tumours)",
            "• Epigastric pain/fullness",
            "• Profound weight loss",
            "• Iron deficiency anaemia (occult bleeding)",
        ]),
        "\n".join([
            "• Palpable epigastric mass",
            "• Ascites (peritoneal spread)",
            "• Virchow's node (Troisier's sign): left supraclavicular LN",
            "• Sister Mary Joseph nodule: periumbilical LN",
            "• Blumer's shelf: rectal shelf on PR exam",
            "• Krukenberg tumour: ovarian metastasis",
            "• Trousseau's sign: migratory thrombophlebitis",
            "• DVT / thromboembolic events",
        ])
    ]
]
cf_tbl = Table(cf_data, colWidths=[cf_col, cf_col])
cf_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
    ("BACKGROUND", (0,1), (0,1), LIGHT_GREY),
    ("BACKGROUND", (1,1), (1,1), LIGHT_BLUE),
    ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d1d5db")),
    ("TOPPADDING", (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
    ("RIGHTPADDING", (0,0), (-1,-1), 8),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
    ("FONTSIZE", (0,1), (-1,1), 10),
]))
story.append(cf_tbl)
story.append(sp())
story.append(info_box(
    "<b>Alarm Features (require urgent endoscopy):</b> Dysphagia, unintentional weight loss, "
    "progressive difficulty eating, persistent vomiting, haematemesis, epigastric mass, anaemia."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 6: SITE OF ORIGIN
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("4. SITE OF ORIGIN"))
story.append(sp())

story.append(Paragraph("Anatomical Distribution", section_heading))
story.append(bullet("Proximal stomach / gastro-oesophageal junction (GOJ): <b>most common site in the West</b> (rising incidence)."))
story.append(bullet("Adenocarcinoma near the GOJ has <b>doubled in incidence</b> in the UK over the last 30 years."))
story.append(bullet("Approximately 60% of all upper GI malignancies occur in proximity to the GOJ."))
story.append(bullet("Japan and most of the world: <b>distal cancer still predominates</b>."))
story.append(sp(0.5))

story.append(Paragraph("Regional Differences", section_heading))
site_data = [
    ["Site", "Western Countries", "Japan / Rest of World"],
    ["Proximal / GOJ", "Most common (increasing)", "Less common"],
    ["Body / Distal", "Declining", "Most common"],
    ["H. pylori link", "Not for proximal; Yes for distal", "Strong for distal"],
    ["Associated risk", "Obesity, GORD, high SES", "H. pylori, diet, low SES"],
]
story.append(std_table(site_data[0], site_data[1:], col_widths=[4.5*cm, 5*cm, W-4*cm-9.5*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 7: PATHOLOGY & CLASSIFICATIONS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("5–6. PATHOLOGY & CLASSIFICATIONS"))
story.append(sp())

# 5A - Lauren
story.append(section_divider_title("A. Laurén Classification (Most Clinically Useful)"))
story.append(sp(0.5))
lauren_data = [
    ["Type", "Characteristics", "Gross Appearance", "Prognosis"],
    ["Intestinal Type", "Resembles carcinoma elsewhere in GI tract. Arises in intestinal metaplasia. Well to moderately differentiated.", "Polypoid or ulcerating mass", "Better prognosis. Responds to H. pylori treatment strategies."],
    ["Diffuse Type", "Infiltrates deeply into gastric wall. Signet ring cells. No obvious mass lesion. Spreads extensively.", "Linitis plastica (leather bottle stomach)", "Much worse prognosis. Spreads early via submucosal lymphatics."],
    ["Mixed / Indeterminate", "Features of both types", "Variable", "Intermediate prognosis"],
]
story.append(std_table(lauren_data[0], lauren_data[1:], col_widths=[3*cm, 5.5*cm, 4*cm, W-4*cm-12.5*cm]))
story.append(sp())
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 8: JAPANESE EARLY GASTRIC CANCER CLASSIFICATION
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("6B. Japanese Classification of Early Gastric Cancer"))
story.append(sp())

story.append(Paragraph(
    "<b>Early gastric cancer (EGC)</b> is defined as cancer limited to the mucosa "
    "and submucosa (T1), with or without lymph node involvement. "
    "5-year survival ~90% when detected at this stage.",
    body_style
))
story.append(sp(0.5))

egc_data = [
    ["Type", "Name", "Description"],
    ["Type I", "Protruding", "Tumour protrudes into gastric lumen (polypoid)"],
    ["Type IIa", "Superficial Elevated", "Slightly elevated above mucosal surface"],
    ["Type IIb", "Superficial Flat", "No elevation or depression; nearly invisible"],
    ["Type IIc", "Superficial Depressed", "Slightly depressed below mucosal level; most common EGC type"],
    ["Type III", "Excavated / Ulcerated", "Ulcer with cancer at its base; must distinguish from benign ulcer"],
]
story.append(std_table(egc_data[0], egc_data[1:], col_widths=[2.5*cm, 4.5*cm, W-4*cm-7*cm]))
story.append(sp())

# Two images side by side
two_img = two_col_images(
    img_paths["early_gastric"],
    "Fig 67.25 – Japanese classification of EGC\n(Type I: protruding; IIa–IIc: superficial; III: ulcerated)",
    img_paths["endoscopy_early_a"],
    "Fig 67.26 – Endoscopic appearance of early gastric cancer"
)
story.append(two_img)
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 9: BORRMANN CLASSIFICATION
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("6C. Borrmann Classification – Advanced Gastric Cancer"))
story.append(sp())

story.append(Paragraph(
    "Advanced gastric cancer involves the muscularis propria and beyond. "
    "Macroscopic appearance is classified by Borrmann into four types. "
    "<b>Types III and IV are commonly incurable.</b>",
    body_style
))
story.append(sp(0.5))

borrmann_data = [
    ["Type", "Gross Appearance", "Description", "Prognosis"],
    ["Type 1", "Polypoid", "Well-circumscribed polypoid mass. Pushing borders. No ulceration.", "Best"],
    ["Type 2", "Ulcerating", "Ulcer with raised, sharply defined edges. Tumour mass with central crater.", "Good"],
    ["Type 3", "Infiltrating / Ulcerating", "Ulcer with infiltrating raised edges. Indistinct borders. Invasion beyond ulcer.", "Poor"],
    ["Type 4", "Infiltrating / Linitis Plastica", "Diffuse infiltration of entire stomach wall. 'Leather bottle stomach'. Signet ring cells.", "Worst – often incurable"],
]
story.append(std_table(borrmann_data[0], borrmann_data[1:], col_widths=[2.2*cm, 3.5*cm, 6*cm, W-4*cm-11.7*cm]))
story.append(sp())

story.append(image_with_caption(
    img_paths["borrmann_diagram"],
    "Fig 67.27 – Borrmann classification: Type 1 (polypoid), Type 2 (ulcerating), "
    "Type 3 (infiltrating/ulcerating), Type 4 (infiltrating/linitis plastica)",
    max_w=11*cm, max_h=10*cm
)[0])
story.append(image_with_caption(img_paths["borrmann_diagram"], "")[1])
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 10: MOLECULAR / TCGA CLASSIFICATION
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("6D. Molecular Classification (TCGA)"))
story.append(sp())

story.append(Paragraph(
    "The Cancer Genome Atlas (TCGA) group described <b>four molecular subtypes</b> of gastric cancer "
    "based on next-generation sequencing. Recognition of these subgroups enables targeted therapy.",
    body_style
))
story.append(sp(0.5))

mol_data = [
    ["Subtype", "Key Features", "Implications"],
    ["Epstein–Barr Virus (EBV) Positive", "PIK3CA mutations, CDKN2A silencing, extreme DNA hypermethylation", "PD-L1 overexpression → potential immunotherapy target"],
    ["Microsatellite Unstable (MSI)", "Hypermutation, MLH1 silencing, elevated mutation burden", "Best response to checkpoint inhibitors (pembrolizumab)"],
    ["Genomically Stable (GS)", "Diffuse type, CDH1/RHOA mutations, CLDN18–ARHGAP fusions", "Worst prognosis; includes diffuse/Lauren type"],
    ["Chromosomal Instability (CIN)", "TP53 mutations, receptor tyrosine kinase amplifications (EGFR, VEGFA, HER2)", "HER2 overexpression → trastuzumab eligible (TOGA trial)"],
]
story.append(std_table(mol_data[0], mol_data[1:], col_widths=[4*cm, 6*cm, W-4*cm-10*cm]))
story.append(sp())

story.append(Paragraph("Key Mutated Genes in Gastric Cancer", section_heading))
gene_groups = [
    ("Genome integrity:", "BRCA2, TP53, ARID1A"),
    ("Chromatin remodelling:", "SMARCA1, CHD3, CHD4"),
    ("Cell adhesion & motility:", "RHOA, CDH1"),
    ("Cell signalling:", "PIK3CA, KRAS, ERBB2 (HER2), FGFR2, MET"),
]
for label, genes in gene_groups:
    story.append(bullet(f"<b>{label}</b> {genes}"))
story.append(sp())
story.append(info_box(
    "<b>Clinical Impact:</b> HER2+ (CIN subtype) → Trastuzumab (TOGA trial). "
    "MSI-H → Pembrolizumab/nivolumab. EBV+ → PD-L1 inhibitors under investigation."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 11: WHO HISTOLOGICAL CLASSIFICATION
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("6E. WHO Histological Classification"))
story.append(sp())

who_data = [
    ["Category", "Types"],
    ["Malignant Epithelial Tumours", "Adenocarcinoma (tubular, papillary, mucinous, poorly cohesive incl. signet ring cell); Adenosquamous carcinoma; Squamous cell carcinoma; Undifferentiated carcinoma"],
    ["Carcinoid (NET)", "Well-differentiated neuroendocrine tumour; associated with ECL cell hyperplasia in atrophic gastritis"],
    ["Non-epithelial Tumours", "GIST (gastrointestinal stromal tumour), leiomyosarcoma, Kaposi sarcoma"],
    ["Lymphoma", "MALT lymphoma (most common gastric lymphoma), DLBCL"],
    ["Secondary Tumours", "Metastases from breast, lung, melanoma, colorectal"],
]
story.append(std_table(who_data[0], who_data[1:], col_widths=[5*cm, W-4*cm-5*cm]))
story.append(sp())

story.append(Paragraph("Histological Grading", section_heading))
grade_data = [
    ["Grade", "Designation", "Description"],
    ["G1", "Well differentiated", "Resembles normal gastric mucosa. Glandular architecture preserved."],
    ["G2", "Moderately differentiated", "Partial glandular formation. More nuclear pleomorphism."],
    ["G3", "Poorly differentiated", "Minimal glandular formation. Marked nuclear atypia."],
    ["G4", "Undifferentiated", "No glandular formation. Worst prognosis."],
]
story.append(std_table(grade_data[0], grade_data[1:], col_widths=[1.8*cm, 4.5*cm, W-4*cm-6.3*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 12: TNM STAGING
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("7. TNM / UICC STAGING (8th Edition)"))
story.append(sp())

story.append(Paragraph("T – Primary Tumour", section_heading))
t_data = [
    ["Stage", "Description"],
    ["T1a", "Tumour invades lamina propria or muscularis mucosae (mucosa only)"],
    ["T1b", "Tumour invades submucosa"],
    ["T2", "Tumour invades muscularis propria"],
    ["T3", "Tumour invades subserosa (without penetrating visceral peritoneum)"],
    ["T4a", "Tumour perforates visceral peritoneum (serosa) – without invading adjacent structures"],
    ["T4b", "Tumour invades adjacent structures (pancreas, colon, liver, spleen, etc.)"],
]
story.append(std_table(t_data[0], t_data[1:], col_widths=[2.2*cm, W-4*cm-2.2*cm]))
story.append(sp(0.5))

story.append(Paragraph("N – Regional Lymph Nodes", section_heading))
n_data = [
    ["Stage", "Description"],
    ["N0", "No regional lymph node metastasis"],
    ["N1", "Metastasis in 1–2 regional lymph nodes"],
    ["N2", "Metastasis in 3–6 regional lymph nodes"],
    ["N3a", "Metastasis in 7–15 regional lymph nodes"],
    ["N3b", "Metastasis in ≥16 regional lymph nodes"],
]
story.append(std_table(n_data[0], n_data[1:], col_widths=[2.2*cm, W-4*cm-2.2*cm]))
story.append(sp(0.5))

story.append(Paragraph("M – Distant Metastasis", section_heading))
m_data = [
    ["Stage", "Description"],
    ["M0", "No distant metastasis"],
    ["M1", "Distant metastasis (liver, lung, bone, peritoneum, distant LNs)"],
]
story.append(std_table(m_data[0], m_data[1:], col_widths=[2.2*cm, W-4*cm-2.2*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 13: OVERALL STAGE GROUPING
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("7. UICC Stage Grouping (8th Edition)"))
story.append(sp())

stage_data = [
    ["Overall Stage", "T", "N", "M", "Approximate 5-yr Survival"],
    ["Stage IA", "T1", "N0", "M0", "~90–95% (EGC)"],
    ["Stage IB", "T1 / T2", "N1 / N0", "M0", "~75–85%"],
    ["Stage IIA", "T1–T3", "N2 / N1 / N0", "M0", "~50–60%"],
    ["Stage IIB", "T1–T4a", "N3a / N2 / N1 / N0", "M0", "~35–50%"],
    ["Stage IIIA", "T2–T4a", "N3b / N3a / N2", "M0", "~20–35%"],
    ["Stage IIIB", "T3–T4b", "N3b / N3a / N2 / N1", "M0", "~10–25%"],
    ["Stage IIIC", "T4a–T4b", "N3b / N3a", "M0", "~5–15%"],
    ["Stage IV", "Any T", "Any N", "M1", "<5%"],
]
story.append(std_table(stage_data[0], stage_data[1:], col_widths=[3*cm, 2.5*cm, 2.5*cm, 2.5*cm, W-4*cm-10.5*cm]))
story.append(sp())
story.append(info_box(
    "<b>Note:</b> Japanese staging (JGCA) uses a similar but not identical system. Japan also uses "
    "a separate D1/D2/D3 classification for lymphadenectomy extent. "
    "Adequate nodal sampling requires ≥16 nodes for accurate staging (UICC requirement)."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 14: SPREAD
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("8. SPREAD OF GASTRIC CARCINOMA"))
story.append(sp())

story.append(Paragraph("Routes of Spread", section_heading))

spread_data = [
    [Paragraph("<b>Mode</b>", table_header_style),
     Paragraph("<b>Details</b>", table_header_style),
     Paragraph("<b>Clinical Features</b>", table_header_style)],
    [Paragraph("<b>Direct</b>", table_cell_style),
     Paragraph("Tumour penetrates muscularis, serosa, then adjacent organs (pancreas, colon, liver)", table_cell_style),
     Paragraph("Fistula formation; unresectability in T4b", table_cell_style)],
    [Paragraph("<b>Lymphatic</b>", table_cell_style),
     Paragraph("Both permeation and emboli; spreads along gastric vessels to regional and distant nodes", table_cell_style),
     Paragraph("Troisier's sign (Virchow's node in left supraclavicular fossa). Diffuse type spreads via submucosal plexus early.", table_cell_style)],
    [Paragraph("<b>Blood-borne</b>", table_cell_style),
     Paragraph("Portal vein → liver (most common visceral metastasis). Then lung, bone", table_cell_style),
     Paragraph("Uncommon without nodal disease. RUQ pain, jaundice, raised LFTs", table_cell_style)],
    [Paragraph("<b>Transperitoneal</b>", table_cell_style),
     Paragraph("Once serosa is breached → peritoneal seeding anywhere in cavity", table_cell_style),
     Paragraph("Ascites, Krukenberg tumours (ovary), Sister Mary Joseph nodule (umbilicus), Blumer's shelf (rectovesical pouch). Indicates incurability.", table_cell_style)],
]
spread_tbl = Table(spread_data, colWidths=[3*cm, 5.5*cm, W-4*cm-8.5*cm])
spread_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
    ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GREY]),
    ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d1d5db")),
    ("TOPPADDING", (0,0), (-1,-1), 7),
    ("BOTTOMPADDING", (0,0), (-1,-1), 7),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
    ("RIGHTPADDING", (0,0), (-1,-1), 8),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(spread_tbl)
story.append(sp())

story.append(Paragraph("Peritoneal Metastatic Deposits – Named Signs", section_heading))
signs_data = [
    ["Sign/Finding", "Location", "Significance"],
    ["Troisier's sign (Virchow's node)", "Left supraclavicular fossa (Virchow's node)", "Lymphatic spread along thoracic duct"],
    ["Sister Mary Joseph nodule", "Periumbilical nodule", "Peritoneal/lymphatic spread to umbilicus"],
    ["Blumer's shelf", "Anterior rectal wall (PR exam)", "Peritoneal drop metastasis to rectovesical pouch"],
    ["Krukenberg tumour", "Bilateral ovarian masses", "Transperitoneal/haematogenous spread; signet ring cells"],
]
story.append(std_table(signs_data[0], signs_data[1:], col_widths=[5*cm, 4.5*cm, W-4*cm-9.5*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 15: LYMPHATIC DRAINAGE
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("9. LYMPHATIC DRAINAGE OF THE STOMACH"))
story.append(sp())

story.append(Paragraph("The stomach has an extensive and complex lymphatic network. Lymph node stations are numbered according to the Japanese Gastric Cancer Association (JGCA). Surgical clearance (D2 gastrectomy) targets N1 and N2 nodal groups.", body_style))
story.append(sp(0.5))

story.append(Paragraph("Nodal Tiers", section_heading))
tier_data = [
    ["Tier / Group", "Nodes Included", "Surgical Relevance"],
    ["N1 (First Tier / Perigastric)", "Nodes along lesser curvature (stations 1–4), greater curvature (stations 4–6), suprapyloric, infrapyloric", "D1 gastrectomy removes these"],
    ["N2 (Second Tier / Principal Arterial)", "Nodes along left gastric artery (7), common hepatic artery (8), coeliac artery (9), splenic artery (11)", "D2 gastrectomy removes N1 + N2; proven survival benefit in experienced centres"],
    ["N3 (Third Tier / Para-aortic)", "Paraaortic nodes, hepatoduodenal ligament nodes, root of mesentery", "D3/extended resection; controversial benefit"],
]
story.append(std_table(tier_data[0], tier_data[1:], col_widths=[4.5*cm, 5.5*cm, W-4*cm-10*cm]))
story.append(sp())
story.append(info_box(
    "<b>D2 Gastrectomy:</b> Recommended standard in Japan and high-volume Western centres. "
    "The Dutch D1 vs D2 trial initially showed higher morbidity with D2, but long-term 15-year "
    "follow-up demonstrated a survival benefit. Surgeon experience is critical."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 16: INVESTIGATIONS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("10. INVESTIGATIONS"))
story.append(sp())

story.append(Paragraph("Diagnostic Work-Up", section_heading))
inv_data = [
    ["Investigation", "Findings / Purpose"],
    ["Upper GI Endoscopy (OGD)", "Gold standard. Direct visualisation + biopsy. Required ≥8 biopsies for accurate diagnosis. Endoscopic ultrasound (EUS) for T & N staging."],
    ["CT Chest / Abdomen / Pelvis", "Staging: identifies T4 invasion, nodal disease, peritoneal/visceral metastases. Detects M1 disease. Sensitivity for M1 ~80%."],
    ["Endoscopic Ultrasound (EUS)", "Best for T staging (especially T1b vs T2). Identifies lymph node involvement."],
    ["PET-CT (FDG)", "Detects unsuspected distant metastases. Less sensitive for signet ring/mucinous tumours (low FDG uptake)."],
    ["Diagnostic Laparoscopy", "Mandatory before curative resection. Identifies peritoneal seedlings not seen on CT (~25% of cases). Peritoneal wash cytology."],
    ["Barium Meal", "Largely replaced by endoscopy. 'Leather bottle' appearance in linitis plastica."],
    ["Bloods", "FBC (anaemia), LFTs, CEA/CA19-9 (tumour markers – poor sensitivity/specificity)."],
    ["HER2 Testing", "IHC ± FISH on biopsy specimen. If HER2+, trastuzumab eligible."],
    ["MSI / MMR Testing", "For immunotherapy eligibility (pembrolizumab in MSI-H/dMMR)."],
]
story.append(std_table(inv_data[0], inv_data[1:], col_widths=[4.5*cm, W-4*cm-4.5*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 17: SURGICAL TREATMENT
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("11. SURGICAL TREATMENT"))
story.append(sp())

story.append(info_box(
    "<b>The only curative treatment for gastric cancer is resectional surgery.</b> "
    "Operability depends on fitness, stage, and absence of M1 disease."
))
story.append(sp(0.5))

story.append(Paragraph("Operability Assessment", section_heading))
story.append(bullet("Staging laparoscopy + peritoneal cytology is <b>mandatory</b> before curative resection."))
story.append(bullet("Positive peritoneal cytology alone (without visible seedlings) may preclude curative resection."))
story.append(bullet("Fitness for major surgery: cardiopulmonary assessment, nutritional optimisation."))
story.append(sp(0.5))

story.append(Paragraph("Total Gastrectomy", section_heading))
story.append(bullet("Indicated for: proximal cancers, diffuse-type cancers, cancers of the body of stomach."))
story.append(bullet("Reconstruction: typically Roux-en-Y oesophagojejunostomy."))
story.append(bullet("Splenectomy: formerly routine; now avoided unless direct splenic/hilar involvement."))
story.append(bullet("D2 lymphadenectomy (N1 + N2 nodes) is standard at expert centres."))
story.append(sp(0.5))

story.append(Paragraph("Subtotal Gastrectomy", section_heading))
story.append(bullet("Indicated for: distal tumours (antrum, pylorus, distal body) with ≥5 cm proximal clearance."))
story.append(bullet("Reconstruction: Roux-en-Y gastrojejunostomy or Billroth II."))
story.append(bullet("Preserves some gastric function; better nutritional outcomes vs total gastrectomy."))
story.append(sp(0.5))

story.append(Paragraph("Palliative Surgery", section_heading))
story.append(bullet("Bypass procedures: gastrojejunostomy for pyloric obstruction."))
story.append(bullet("Palliative gastrectomy: sometimes performed to control bleeding or obstruction."))
story.append(bullet("Endoscopic stenting: for malignant obstruction (GOJ or pyloric) in unfit patients."))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 18: POST-OP COMPLICATIONS & LONG-TERM SEQUELAE
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("11B. COMPLICATIONS OF GASTRECTOMY"))
story.append(sp())

story.append(Paragraph("Early (Post-operative) Complications", section_heading))
early_data = [
    ["Complication", "Details"],
    ["Anastomotic leak", "Most feared. Presents day 5–7. May require re-operation or radiological drainage."],
    ["Duodenal stump leak", "After Billroth II/total gastrectomy. Bile peritonitis."],
    ["Haemorrhage", "From anastomosis or staple lines."],
    ["Chest complications", "Pneumonia, pleural effusion, especially after total gastrectomy."],
    ["Pulmonary embolism", "DVT prophylaxis mandatory."],
    ["Wound infection / dehiscence", "Higher risk in malnourished patients."],
]
story.append(std_table(early_data[0], early_data[1:], col_widths=[5*cm, W-4*cm-5*cm]))
story.append(sp(0.5))

story.append(Paragraph("Long-term Complications", section_heading))
late_data = [
    ["Syndrome", "Mechanism", "Features"],
    ["Dumping syndrome (Early)", "Rapid gastric emptying → osmotic fluid shift into small bowel", "Postprandial flushing, tachycardia, sweating, diarrhoea within 15–30 min"],
    ["Dumping syndrome (Late)", "Reactive hypoglycaemia 1–3 hrs post-meal", "Sweating, weakness, confusion 1–3 hrs after eating"],
    ["Reflux (bile) gastritis", "Loss of pyloric function → bile reflux", "Epigastric pain, bilious vomiting"],
    ["Nutritional deficiencies", "B12 malabsorption (loss of intrinsic factor after total gastrectomy)", "Megaloblastic anaemia → requires lifelong B12 injections"],
    ["Iron deficiency anaemia", "Reduced acid secretion, loss of gastric reservoir", "Common; requires iron supplementation"],
    ["Malabsorption", "Bacterial overgrowth, rapid transit, pancreatic insufficiency", "Weight loss, steatorrhoea"],
    ["Afferent loop syndrome", "After Billroth II: obstruction of afferent limb", "Postprandial bilious vomiting, relieved by vomiting"],
]
story.append(std_table(late_data[0], late_data[1:], col_widths=[3.5*cm, 4.5*cm, W-4*cm-8*cm]))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 19: CHEMOTHERAPY & MULTIMODAL THERAPY
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("12. CHEMOTHERAPY & MULTIMODAL THERAPY"))
story.append(sp())

story.append(Paragraph("Chemotherapy improves survival both perioperatively (around surgery) and in advanced disease.", body_style))
story.append(sp(0.5))

chemo_data = [
    ["Approach", "Regimen / Trial", "Key Findings"],
    ["Perioperative (Pre- + Post-op)", "FLOT4 (5-FU, leucovorin, oxaliplatin, docetaxel) – FLOT4-AIO trial", "Improved OS and PFS vs ECF/ECX. FLOT now standard in fit patients."],
    ["Perioperative (Pre- + Post-op)", "ECF/ECX/EOF/EOX (MAGIC trial)", "Significant survival benefit over surgery alone. Established perioperative chemo standard."],
    ["Post-operative (Adjuvant)", "Capecitabine + oxaliplatin (CLASSIC trial); S-1 (ACTS-GC)", "Significant OS benefit in East Asian populations."],
    ["Post-op Chemoradiotherapy", "5-FU + leucovorin + RT (MacDonald/INT-0116)", "Benefit shown, especially when inadequate nodal dissection performed."],
    ["Advanced/Palliative", "Platinum + fluoropyrimidine ± trastuzumab (HER2+)", "TOGA trial: trastuzumab + cisplatin/capecitabine → OS benefit in HER2+."],
    ["Second-line", "Ramucirumab ± paclitaxel (RAINBOW trial); Irinotecan", "Ramucirumab (anti-VEGFR2) improves OS in 2nd line."],
    ["Immunotherapy", "Pembrolizumab (MSI-H), nivolumab (ATTRACTION-4)", "Checkpoint inhibitors in MSI-H/dMMR and PD-L1 high tumours."],
]
story.append(std_table(chemo_data[0], chemo_data[1:], col_widths=[4.5*cm, 5*cm, W-4*cm-9.5*cm]))
story.append(sp())
story.append(info_box(
    "<b>FLOT4 is now the preferred perioperative regimen</b> in fit patients in the UK and Europe, "
    "having superseded ECF/ECX. FLOT = docetaxel, oxaliplatin, leucovorin, 5-FU × 4 cycles before and 4 cycles after surgery."
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 20: PROGNOSIS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("13. PROGNOSIS"))
story.append(sp())

story.append(Paragraph("Overall prognosis remains poor in the West due to late-stage presentation. Early detection markedly improves outcomes.", body_style))
story.append(sp(0.5))

prognosis_data = [
    ["Stage at Diagnosis", "Approximate 5-yr Survival (West)", "Notes"],
    ["Early Gastric Cancer (T1, any N)", "~90%", "Japan: 1/3 of cases detected here due to screening"],
    ["Localised (Stage I-II)", "~40–70%", "Curative resection possible"],
    ["Locally Advanced (Stage III)", "~15–35%", "Multimodal therapy required"],
    ["Metastatic (Stage IV)", "<5%", "Palliative intent only"],
    ["Overall (Western series)", "~15–25%", "Poor due to late presentation"],
    ["Overall (Japan)", "~60–70%", "Screening and early detection"],
]
story.append(std_table(prognosis_data[0], prognosis_data[1:], col_widths=[5*cm, 4.5*cm, W-4*cm-9.5*cm]))
story.append(sp(0.5))

story.append(Paragraph("Prognostic Factors", section_heading))
pf_col = (W - 4*cm) / 2 - 0.2*cm
pf_data = [
    [Paragraph("<b>Favourable Factors</b>", make_style("ph", fontName="Helvetica-Bold", fontSize=12, textColor=WHITE)),
     Paragraph("<b>Adverse Factors</b>", make_style("ph", fontName="Helvetica-Bold", fontSize=12, textColor=WHITE))],
    [
        "\n".join([
            "• Early stage (T1–T2)",
            "• No lymph node involvement",
            "• Intestinal-type (Laurén)",
            "• Distal location",
            "• R0 resection achieved",
            "• ≥16 nodes examined",
            "• High-volume surgical centre",
            "• Perioperative chemotherapy",
            "• HER2+ (trastuzumab eligible)",
        ]),
        "\n".join([
            "• Advanced stage (T3–T4)",
            "• Lymph node metastases (N+)",
            "• Diffuse type / signet ring (Laurén)",
            "• Proximal location",
            "• R1/R2 resection",
            "• Peritoneal dissemination",
            "• Distant metastases (M1)",
            "• Poor nutritional status",
            "• Linitis plastica (Borrmann 4)",
        ])
    ]
]
pf_tbl = Table(pf_data, colWidths=[pf_col, pf_col])
pf_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), GREEN),
    ("BACKGROUND", (1,0), (1,0), RED),
    ("BACKGROUND", (0,1), (0,1), colors.HexColor("#f0fdf4")),
    ("BACKGROUND", (1,1), (1,1), colors.HexColor("#fef2f2")),
    ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d1d5db")),
    ("TOPPADDING", (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING", (0,0), (-1,-1), 10),
    ("RIGHTPADDING", (0,0), (-1,-1), 10),
    ("FONTSIZE", (0,1), (-1,1), 10),
    ("LEADING", (0,1), (-1,1), 15),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(pf_tbl)
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 21: SUMMARY / KEY POINTS
# ────────────────────────────────────────────────────────────────────────────
story.append(slide_title("SUMMARY: KEY POINTS"))
story.append(sp())

summary_items = [
    ("Epidemiology", "5th most common cancer globally; major mortality cause. UK ~15/100k. Japan ~70/100k. Incidence falling in West but proximal GOJ cancers rising."),
    ("Aetiology", "Multifactorial: H. pylori (distal), diet (salt, nitrosamines), pernicious anaemia, post-surgical stomach, smoking, genetic mutations (CDH1)."),
    ("EGC", "Early gastric cancer (T1, any N) → 90% 5-yr survival. Japanese classification: Type I (protruding), IIa/b/c (superficial), III (ulcerated)."),
    ("Laurén", "Intestinal type: polypoid/ulcer; arises from metaplasia; better prognosis. Diffuse type: infiltrative, signet ring cells; worse prognosis; linitis plastica = Borrmann 4."),
    ("Borrmann", "Advanced cancer classification: Type 1 (polypoid), 2 (ulcerating), 3 (ulcero-infiltrating), 4 (infiltrating/linitis plastica). Types 3 & 4 often incurable."),
    ("TCGA Molecular", "4 subtypes: EBV+, MSI, Genomically Stable, CIN. MSI → immunotherapy. EBV+ → PD-L1. CIN → HER2 amplification → trastuzumab."),
    ("Staging", "UICC 8th edition TNM. Stage I ≈ T1–T2 N0. Stage IV = M1. Need ≥16 nodes for adequate N staging."),
    ("Surgery", "Only curative option: R0 resection. Total vs subtotal gastrectomy. D2 lymphadenectomy standard. Diagnostic laparoscopy mandatory before curative resection."),
    ("Chemotherapy", "Perioperative FLOT (or MAGIC ECF/ECX) improves survival. Trastuzumab for HER2+. Pembrolizumab/nivolumab for MSI-H. Ramucirumab 2nd line."),
    ("Prognosis", "Overall ~5–15% cure rate in West. Late presentation drives poor outcomes. Japan: ~60–70% due to screening. Early detection is key."),
]

for i, (topic, text) in enumerate(summary_items):
    row_bg = LIGHT_BLUE if i % 2 == 0 else LIGHT_GOLD
    tbl = Table(
        [[Paragraph(f"<b>{topic}</b>", make_style("st", fontName="Helvetica-Bold", fontSize=11, textColor=DARK_BLUE)),
          Paragraph(text, make_style("sb", fontName="Helvetica", fontSize=10.5, textColor=DARK_GREY, leading=15))]],
        colWidths=[3*cm, W-4*cm-3*cm]
    )
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), row_bg),
        ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d1d5db")),
        ("TOPPADDING", (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("RIGHTPADDING", (0,0), (-1,-1), 8),
        ("VALIGN", (0,0), (-1,-1), "TOP"),
    ]))
    story.append(tbl)

story.append(sp())
story.append(info_box(
    "<b>Bailey & Love 28th Ed. reminder:</b> 'Gastric cancer is an eminently curable disease and early diagnosis is key to success. "
    "Unfortunately, late presentation is common and the cause of poor overall survival figures. The only curative treatment is resectional surgery.'"
))
story.append(PageBreak())


# ────────────────────────────────────────────────────────────────────────────
# SLIDE 22: BACK COVER
# ────────────────────────────────────────────────────────────────────────────
story.append(Spacer(1, 3*cm))
back_tbl = Table(
    [[Paragraph("END OF PRESENTATION", make_style("BT", fontName="Helvetica-Bold", fontSize=22, textColor=WHITE, alignment=TA_CENTER))],
     [Spacer(1, 0.5*cm)],
     [Paragraph("Gastric Carcinoma", make_style("BS", fontName="Helvetica", fontSize=15, textColor=LIGHT_BLUE, alignment=TA_CENTER))],
     [Paragraph("Bailey and Love's Short Practice of Surgery, 28th Edition", make_style("BS2", fontName="Helvetica-Oblique", fontSize=11, textColor=LIGHT_GOLD, alignment=TA_CENTER))],
     [Paragraph("Chapter 67 – Stomach and Duodenum", make_style("BS3", fontName="Helvetica-Oblique", fontSize=11, textColor=LIGHT_GOLD, alignment=TA_CENTER))],
    ],
    colWidths=[W - 4*cm]
)
back_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("ROUNDEDCORNERS", [10]),
    ("TOPPADDING", (0,0), (-1,-1), 20),
    ("BOTTOMPADDING", (0,0), (-1,-1), 20),
]))
story.append(back_tbl)


# ── Build PDF ─────────────────────────────────────────────────────────────────
out_path = os.path.join(OUT_DIR, "Gastric_Carcinoma_Bailey_Love_28e.pdf")

doc = SimpleDocTemplate(
    out_path,
    pagesize=A4,
    leftMargin=2*cm,
    rightMargin=2*cm,
    topMargin=1.8*cm,
    bottomMargin=1.5*cm,
    title="Gastric Carcinoma – Bailey & Love 28th Edition",
    author="Orris Medical AI",
    subject="Gastric Carcinoma Presentation",
)

doc.build(story, canvasmaker=PresentationCanvas)
print(f"\nPDF created successfully: {out_path}")
import os
size = os.path.getsize(out_path)
print(f"File size: {size:,} bytes ({size/1024:.1f} KB)")
Running Command

cd /home/daytona/workspace/gastric-carcinoma && python3 build_pdf.py 2>&1

The PDF was built successfully (3.5 MB). Here is your download link:

Gastric Carcinoma Bailey Love 28e

PDF Document · PDF


What's Inside (22 slides / ~18 pages)

The presentation is sourced directly from Bailey & Love's Short Practice of Surgery, 28th Edition, Chapter 67 – Stomach and Duodenum, and covers:
SlideContent
1Title page with key statistics (incidence, cure rates)
2Table of Contents
3Epidemiology & Incidence (worldwide table: Japan 70/100k vs UK 15/100k)
4Aetiology & Risk Factors (H. pylori, Correa cascade, diet, genetics)
5Clinical Features - early vs advanced; named signs (Troisier, Krukenberg, Blumer)
6Site of Origin - proximal shift in the West vs Japan
7Laurén Classification - Intestinal vs Diffuse vs Mixed
8Japanese EGC Classification - Types I, IIa, IIb, IIc, III + textbook diagrams
9Borrmann Classification - Types 1-4 (advanced) + textbook diagram
10TCGA Molecular Classification - EBV+, MSI, GS, CIN + gene mutations
11WHO Histological Classification + Grading G1-G4
12TNM/UICC 8th Edition Staging - T, N, M descriptors
13Overall Stage Grouping I-IV with 5-yr survival data
14Routes of Spread - direct, lymphatic, blood-borne, transperitoneal
15Lymphatic Drainage - D1/D2/D3 nodal tiers, JGCA stations
16Investigations - OGD, CT, EUS, PET-CT, diagnostic laparoscopy, HER2/MSI testing
17Surgery - total/subtotal gastrectomy, D2 lymphadenectomy, palliative options
18Complications of Gastrectomy - early + long-term (dumping, B12 deficiency)
19Chemotherapy - FLOT4, MAGIC, TOGA (HER2), pembrolizumab (MSI-H), ramucirumab
20Prognosis - stage-by-stage survival, favourable vs adverse factors
21Summary Key Points (10 topics)
22Back cover
All textbook figures (Japanese EGC diagram, Borrmann classification diagram, endoscopic photos of early gastric cancer) are embedded directly from the Bailey & Love source.
This is a shared conversation. Sign in to Orris to start your own chat.