Need a high yeild pdf of forensic for neet pg including images

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Searching Content

"cdn.orris.care" in /textbooks/9789366165257

Reading File
Reading File
Reading File
Reading File
Running Command

mkdir -p /home/daytona/workspace/forensic-neet-pg && ls /home/daytona/workspace/…

Writing File

~/forensic-neet-pg/generate_pdf.py

#!/usr/bin/env python3
"""High-Yield Forensic Medicine PDF Generator for NEET PG"""

import requests
import io
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, white, black, Color
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
                                 TableStyle, Image, PageBreak, HRFlowable,
                                 KeepTogether)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus.flowables import Flowable
from reportlab.lib import colors
from PIL import Image as PILImage

# Color palette
DEEP_BLUE = HexColor('#1a3a5c')
MED_BLUE = HexColor('#2563EB')
LIGHT_BLUE = HexColor('#EFF6FF')
ORANGE = HexColor('#EA580C')
LIGHT_ORANGE = HexColor('#FFF7ED')
GREEN = HexColor('#166534')
LIGHT_GREEN = HexColor('#F0FDF4')
RED = HexColor('#DC2626')
LIGHT_RED = HexColor('#FEF2F2')
YELLOW_BG = HexColor('#FEFCE8')
YELLOW_BORDER = HexColor('#CA8A04')
GRAY = HexColor('#6B7280')
LIGHT_GRAY = HexColor('#F9FAFB')
DARK_TEXT = HexColor('#1F2937')

OUTPUT_PATH = "/home/daytona/workspace/forensic-neet-pg/Forensic_Medicine_HighYield_NEET_PG.pdf"

def get_image_from_url(url, max_width=14*cm, max_height=8*cm):
    """Download image from URL and return ReportLab Image object."""
    try:
        resp = requests.get(url, timeout=15)
        resp.raise_for_status()
        img_data = io.BytesIO(resp.content)
        pil_img = PILImage.open(img_data)
        w, h = pil_img.size
        aspect = h / w
        img_w = min(max_width, w)
        img_h = img_w * aspect
        if img_h > max_height:
            img_h = max_height
            img_w = img_h / aspect
        img_data.seek(0)
        return Image(img_data, width=img_w, height=img_h)
    except Exception as e:
        print(f"Image load failed: {url} -> {e}")
        return None

def build_styles():
    styles = getSampleStyleSheet()

    styles.add(ParagraphStyle(
        'CoverTitle', fontName='Helvetica-Bold', fontSize=28,
        textColor=white, alignment=TA_CENTER, spaceAfter=10
    ))
    styles.add(ParagraphStyle(
        'CoverSub', fontName='Helvetica', fontSize=14,
        textColor=HexColor('#BFD7ED'), alignment=TA_CENTER, spaceAfter=6
    ))
    styles.add(ParagraphStyle(
        'ChapterTitle', fontName='Helvetica-Bold', fontSize=18,
        textColor=white, alignment=TA_LEFT, spaceAfter=2, spaceBefore=0
    ))
    styles.add(ParagraphStyle(
        'SectionTitle', fontName='Helvetica-Bold', fontSize=13,
        textColor=DEEP_BLUE, alignment=TA_LEFT, spaceAfter=4, spaceBefore=10,
        leftIndent=0
    ))
    styles.add(ParagraphStyle(
        'SubSection', fontName='Helvetica-Bold', fontSize=11,
        textColor=MED_BLUE, alignment=TA_LEFT, spaceAfter=3, spaceBefore=6
    ))
    styles.add(ParagraphStyle(
        'BodyText', fontName='Helvetica', fontSize=10,
        textColor=DARK_TEXT, alignment=TA_JUSTIFY, spaceAfter=4, leading=14
    ))
    styles.add(ParagraphStyle(
        'BulletText', fontName='Helvetica', fontSize=10,
        textColor=DARK_TEXT, alignment=TA_LEFT, spaceAfter=3, leading=14,
        leftIndent=15, bulletIndent=5
    ))
    styles.add(ParagraphStyle(
        'KeyPoint', fontName='Helvetica-Bold', fontSize=10,
        textColor=GREEN, alignment=TA_LEFT, spaceAfter=2, leading=14,
        leftIndent=10
    ))
    styles.add(ParagraphStyle(
        'AlertText', fontName='Helvetica-Bold', fontSize=10,
        textColor=RED, alignment=TA_LEFT, spaceAfter=2, leading=14
    ))
    styles.add(ParagraphStyle(
        'Caption', fontName='Helvetica-Oblique', fontSize=9,
        textColor=GRAY, alignment=TA_CENTER, spaceAfter=6
    ))
    styles.add(ParagraphStyle(
        'TableHeader', fontName='Helvetica-Bold', fontSize=9,
        textColor=white, alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'TableCell', fontName='Helvetica', fontSize=9,
        textColor=DARK_TEXT, alignment=TA_LEFT, leading=12
    ))
    styles.add(ParagraphStyle(
        'MCQStem', fontName='Helvetica-Bold', fontSize=10,
        textColor=DEEP_BLUE, alignment=TA_LEFT, spaceAfter=2, leading=14
    ))
    styles.add(ParagraphStyle(
        'MCQOption', fontName='Helvetica', fontSize=10,
        textColor=DARK_TEXT, alignment=TA_LEFT, spaceAfter=1, leading=13,
        leftIndent=15
    ))
    styles.add(ParagraphStyle(
        'MCQAnswer', fontName='Helvetica-Bold', fontSize=10,
        textColor=GREEN, alignment=TA_LEFT, spaceAfter=4, leading=13,
        leftIndent=15
    ))
    return styles

def chapter_header(title, subtitle, styles):
    """Returns chapter header table."""
    data = [[Paragraph(f'<b>{title}</b>', styles['ChapterTitle']),
             Paragraph(subtitle, styles['CoverSub'])]]
    t = Table(data, colWidths=[11*cm, 6*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), DEEP_BLUE),
        ('TOPPADDING', (0,0), (-1,-1), 10),
        ('BOTTOMPADDING', (0,0), (-1,-1), 10),
        ('LEFTPADDING', (0,0), (-1,-1), 14),
        ('RIGHTPADDING', (0,0), (-1,-1), 10),
        ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
        ('ROUNDEDCORNERS', [6, 6, 6, 6]),
    ]))
    return t

def info_box(text, styles, bg=LIGHT_BLUE, border=MED_BLUE):
    """Styled info/alert box."""
    data = [[Paragraph(text, styles['BodyText'])]]
    t = Table(data, colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), bg),
        ('BOX', (0,0), (-1,-1), 1.5, border),
        ('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 t

def key_fact_box(title, items, styles):
    """Yellow highlighted key fact box."""
    content = [Paragraph(f'<b>⭐ {title}</b>', ParagraphStyle('KF', fontName='Helvetica-Bold', fontSize=10, textColor=YELLOW_BORDER))]
    for item in items:
        content.append(Paragraph(f'• {item}', styles['BulletText']))
    data = [content]
    flat_data = [[Paragraph(f'<b>⭐ {title}</b><br/>' + '<br/>'.join([f'• {i}' for i in items]), styles['BodyText'])]]
    t = Table([[Paragraph('<b>⭐ ' + title + '</b><br/>' + ' | '.join(items), ParagraphStyle('kf2', fontName='Helvetica', fontSize=9.5, textColor=HexColor('#713f12'), leading=14))]], colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), YELLOW_BG),
        ('BOX', (0,0), (-1,-1), 1.5, YELLOW_BORDER),
        ('TOPPADDING', (0,0), (-1,-1), 7),
        ('BOTTOMPADDING', (0,0), (-1,-1), 7),
        ('LEFTPADDING', (0,0), (-1,-1), 10),
        ('RIGHTPADDING', (0,0), (-1,-1), 10),
    ]))
    return t

def mnemonic_box(mnemonic, meaning, styles):
    header = Paragraph(f'<b>MNEMONIC: {mnemonic}</b>', ParagraphStyle('mn', fontName='Helvetica-Bold', fontSize=10, textColor=HexColor('#5b21b6')))
    body = Paragraph(meaning, ParagraphStyle('mnb', fontName='Helvetica', fontSize=9.5, textColor=DARK_TEXT, leading=13))
    t = Table([[header], [body]], colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), HexColor('#F5F3FF')),
        ('BOX', (0,0), (-1,-1), 1.5, HexColor('#7C3AED')),
        ('TOPPADDING', (0,0), (-1,-1), 7),
        ('BOTTOMPADDING', (0,0), (-1,-1), 7),
        ('LEFTPADDING', (0,0), (-1,-1), 10),
        ('RIGHTPADDING', (0,0), (-1,-1), 10),
    ]))
    return t

def make_table(headers, rows, styles):
    data = [[Paragraph(h, styles['TableHeader']) for h in headers]]
    for row in rows:
        data.append([Paragraph(str(cell), styles['TableCell']) for cell in row])
    col_w = 17*cm / len(headers)
    t = Table(data, colWidths=[col_w]*len(headers), repeatRows=1)
    ts = TableStyle([
        ('BACKGROUND', (0,0), (-1,0), DEEP_BLUE),
        ('TEXTCOLOR', (0,0), (-1,0), white),
        ('GRID', (0,0), (-1,-1), 0.5, HexColor('#D1D5DB')),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_BLUE]),
        ('TOPPADDING', (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 6),
        ('RIGHTPADDING', (0,0), (-1,-1), 6),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ])
    t.setStyle(ts)
    return t

def mcq_block(q, opts, ans_idx, explanation, styles):
    """Single MCQ block."""
    elems = [Paragraph(f'Q. {q}', styles['MCQStem'])]
    letters = ['A', 'B', 'C', 'D']
    for i, o in enumerate(opts):
        elems.append(Paragraph(f'{letters[i]}. {o}', styles['MCQOption']))
    ans_letter = letters[ans_idx]
    elems.append(Paragraph(f'✓ Ans: {ans_letter}. {opts[ans_idx]} — {explanation}', styles['MCQAnswer']))
    return elems

def generate():
    styles = build_styles()
    doc = SimpleDocTemplate(
        OUTPUT_PATH, pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=2.2*cm, bottomMargin=2.2*cm,
        title="High-Yield Forensic Medicine - NEET PG",
        author="Orris Medical"
    )

    story = []

    # ─── COVER PAGE ──────────────────────────────────────────────────────────
    cover_bg_data = [[
        Paragraph('<b>HIGH-YIELD</b>', styles['CoverTitle']),
    ]]

    story.append(Spacer(1, 1.5*cm))

    cover_title = Table([
        [Paragraph('<b>FORENSIC MEDICINE</b><br/><font size=16>& Toxicology</font>', ParagraphStyle('ct', fontName='Helvetica-Bold', fontSize=30, textColor=white, alignment=TA_CENTER, leading=36))],
        [Paragraph('NEET PG  |  High-Yield Rapid Revision Notes', ParagraphStyle('cs', fontName='Helvetica', fontSize=13, textColor=HexColor('#BFD7ED'), alignment=TA_CENTER))],
        [Spacer(1, 0.3*cm)],
        [Paragraph('Based on: Essentials of Forensic Medicine & Toxicology (36th Ed., 2026)', ParagraphStyle('csrc', fontName='Helvetica-Oblique', fontSize=10, textColor=HexColor('#93C5FD'), alignment=TA_CENTER))],
        [Paragraph('PC Dikshit Textbook | Parikh\'s Textbook', ParagraphStyle('csrc2', fontName='Helvetica-Oblique', fontSize=10, textColor=HexColor('#93C5FD'), alignment=TA_CENTER))],
    ], colWidths=[17*cm])
    cover_title.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), DEEP_BLUE),
        ('TOPPADDING', (0,0), (-1,-1), 18),
        ('BOTTOMPADDING', (0,0), (-1,-1), 18),
        ('LEFTPADDING', (0,0), (-1,-1), 20),
        ('RIGHTPADDING', (0,0), (-1,-1), 20),
        ('ROUNDEDCORNERS', [8, 8, 8, 8]),
    ]))
    story.append(cover_title)
    story.append(Spacer(1, 0.8*cm))

    topics_data = [
        ['📋 Forensic Thanatology', '🩸 Wounds & Injuries', '🔫 Firearm Injuries'],
        ['🪢 Asphyxia & Hanging', '☠️ Toxicology', '🧬 Identification'],
        ['⚖️ Medical Jurisprudence', '🔬 Viscera Preservation', '📝 NEET PG MCQs'],
    ]
    topics_flat = [[Paragraph(f'<b>{c}</b>', ParagraphStyle('tp', fontName='Helvetica-Bold', fontSize=10, textColor=DEEP_BLUE, alignment=TA_CENTER)) for c in row] for row in topics_data]
    topics_table = Table(topics_flat, colWidths=[5.6*cm, 5.6*cm, 5.6*cm])
    topics_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), LIGHT_BLUE),
        ('BOX', (0,0), (-1,-1), 1, MED_BLUE),
        ('INNERGRID', (0,0), (-1,-1), 0.5, HexColor('#BFDBFE')),
        ('TOPPADDING', (0,0), (-1,-1), 8),
        ('BOTTOMPADDING', (0,0), (-1,-1), 8),
        ('ALIGN', (0,0), (-1,-1), 'CENTER'),
    ]))
    story.append(topics_table)
    story.append(Spacer(1, 0.5*cm))
    story.append(info_box('🎯 <b>Tip:</b> Focus on high-frequency NEET PG topics: Postmortem changes, Rigor Mortis, Asphyxia types, Hanging vs Strangulation, Firearm wounds, Poisons (Organophosphate, CO, Cyanide), and Medicolegal aspects.', styles))
    story.append(PageBreak())

    # ─── CHAPTER 1: POSTMORTEM CHANGES ───────────────────────────────────────
    story.append(chapter_header('CHAPTER 1', 'SIGNS OF DEATH & POSTMORTEM CHANGES', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Signs of Death', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    # Postmortem changes table
    pm_headers = ['Stage', 'Timing', 'Key Changes']
    pm_rows = [
        ['Immediate (Somatic)', 'At death', 'Loss of consciousness, cessation of respiration & circulation'],
        ['Early (Cellular)', 'Minutes-hours', 'Pallor, eye changes, primary flaccidity, algor mortis, lividity, rigor mortis'],
        ['Late (Decomposition)', 'Days-weeks', 'Putrefaction, adipocere, mummification'],
    ]
    story.append(make_table(pm_headers, pm_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Algor Mortis (Cooling of Body)', styles['SubSection']))
    story.append(Paragraph('The body cools after death primarily by conduction and convection. The body temperature does NOT fall for the first 0.5–1 hour after death (postmortem temperature plateau / isothermic phase). After this, cooling occurs at 0.4–0.6°C/hr for 12–16 hours.', styles['BodyText']))

    story.append(key_fact_box('Key Formula (Henssge Nomogram)',
        ['PMI ≈ (37 - rectal temp) / 1.5 hrs (rough estimate)',
         'Actual rate: 0.4–0.6°C/hr after plateau',
         'Plateau lasts 0.5–1 hr post death',
         'Ambient temperature is the most important external factor']))
    story.append(Spacer(1, 0.4*cm))

    # Cooling chart image
    cooling_img = get_image_from_url('https://cdn.orris.care/cdss_images/1e72a43bdeaf2fe2d491405f9a78063c76eb8132ecef4bb266ae083fc0c53b50.png')
    if cooling_img:
        story.append(KeepTogether([
            cooling_img,
            Paragraph('Fig 1.1: Chart showing the major changes to estimate time since death (Cooling → Lividity → Decomposition phases)', styles['Caption'])
        ]))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Postmortem Lividity (Livor Mortis / Hypostasis)', styles['SubSection']))
    story.append(Paragraph('Caused by gravitational settling of blood in dependent vessels after circulation stops. Blood gives a pinkish-red discoloration to dependent areas.', styles['BodyText']))

    lividity_headers = ['Feature', 'Detail']
    lividity_rows = [
        ['Color', 'Pinkish-red/purplish-red (CO poisoning: cherry red; cyanide: cherry red/brick red; decomposition: green)'],
        ['Onset', '30 min–2 hrs after death'],
        ['Fixed (unfixable)', 'After 6–8 hrs (India: may fix earlier due to heat)'],
        ['Fully fixed', '12–18 hrs (cannot be shifted by pressure or repositioning)'],
        ['Blanching', 'Blanches on pressure initially; does not blanch after fixation'],
        ['NEET High-yield', 'Fixed lividity → body has not been moved after 6–8 hrs; shifted lividity → body repositioned'],
    ]
    story.append(make_table(lividity_headers, lividity_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Rigor Mortis', styles['SubSection']))
    story.append(Paragraph('Stiffening of muscles due to formation of insoluble actomyosin complex from actin + myosin when ATP is depleted. Lactic acid accumulates. Occurs in all muscles including heart and smooth muscle.', styles['BodyText']))

    rigor_headers = ['Parameter', 'India (Tropical)', 'Temperate Countries']
    rigor_rows = [
        ['Onset', '1–2 hrs after death', '3–6 hrs after death'],
        ['Complete in', '6 hrs (summer)', '12 hrs'],
        ['Duration', '24–48 hrs (winter) / 18–36 hrs (summer)', '2–3 days'],
        ['Order of appearance', 'Jaw → neck → trunk → upper limbs → lower limbs (Nysten\'s law)', 'Same'],
        ['Disappearance', 'Same order as appearance', 'Same'],
    ]
    story.append(make_table(rigor_headers, rigor_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(key_fact_box('Factors Affecting Rigor Mortis',
        ['EARLY onset + SHORT duration: Exhausting diseases (cholera, typhoid, TB, cancer), violent death (gunshot, electrocution), organophosphate poisoning, hot climate',
         'DELAYED onset + LONG duration: Asphyxia, severe hemorrhage, cold climate, CO poisoning (delays disappearance)',
         'Cadaveric Spasm: Instantaneous rigor at moment of death; no primary flaccidity; medico-legal importance (suicide vs murder)',
         'Nysten\'s Rule: Rigor proceeds downward (jaw → neck → trunk → limbs)']))
    story.append(Spacer(1, 0.3*cm))

    story.append(mnemonic_box('CITE (Conditions causing EARLY Rigor)',
        'C = Cholera/Cancer | I = Infections (exhausting) | T = Trauma (violent death) | E = Electrocution/Extreme heat'))
    story.append(Spacer(1, 0.4*cm))

    # Rigor comparison table
    story.append(Paragraph('Rigor Mortis vs Cadaveric Spasm', styles['SubSection']))
    rc_headers = ['Feature', 'Rigor Mortis', 'Cadaveric Spasm']
    rc_rows = [
        ['Mechanism', 'Gradual ATP depletion → actomyosin', 'Instantaneous; occurs at moment of death'],
        ['Primary flaccidity', 'Present before rigor', 'Absent (no primary flaccidity)'],
        ['Group of muscles', 'All muscles eventually', 'Only muscles in use at time of death'],
        ['Medico-legal value', 'Time since death', 'Victim\'s last act; weapon in hand = likely suicide'],
        ['Can be broken', 'Yes (will not return)', 'No (extremely firm)'],
    ]
    story.append(make_table(rc_headers, rc_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Putrefaction', styles['SubSection']))
    story.append(Paragraph('Decomposition of organic matter by micro-organisms. Starts in the cecum (colon). The face, neck, and abdomen show green discoloration first. In summer (India), putrefaction begins in 24 hrs.', styles['BodyText']))

    put_headers = ['Stage/Sign', 'Timing']
    put_rows = [
        ['Green discoloration (skin over cecum)', '24–48 hrs (summer India)'],
        ['Bloating (gas distension)', '2–3 days'],
        ['Skin slippage (skin loosens)', '3–5 days'],
        ['Liquefaction of soft tissues', '2–3 weeks'],
        ['Complete skeletonization', 'Months to years'],
    ]
    story.append(make_table(put_headers, put_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Adipocere & Mummification', styles['SubSection']))
    ad_headers = ['Feature', 'Adipocere', 'Mummification']
    ad_rows = [
        ['Definition', 'Saponification of body fat → waxy substance', 'Desiccation/drying of tissues'],
        ['Condition favoring', 'Warm, moist environment; burial in wet soil', 'Hot, dry climate; dry air'],
        ['Time to form', 'Weeks to months', 'Months'],
        ['Consistency', 'Waxy, yellowish-white, soapy', 'Brown, leathery, shrunken'],
        ['Identification possible', 'Yes (features preserved)', 'Yes (features preserved)'],
        ['Medico-legal', 'May persist for years; helps ID; injuries preserved', 'May persist for years'],
    ]
    story.append(make_table(ad_headers, ad_rows, styles))
    story.append(PageBreak())

    # ─── CHAPTER 2: ASPHYXIA ─────────────────────────────────────────────────
    story.append(chapter_header('CHAPTER 2', 'ASPHYXIA, HANGING & STRANGULATION', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Cardinal Signs of Asphyxia', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    story.append(info_box(
        '<b>C-P-C-F-L</b><br/>'
        '1. <b>C</b>yanosis (bluish discoloration)<br/>'
        '2. <b>P</b>etechial hemorrhages (Tardieu\'s spots)<br/>'
        '3. <b>C</b>ongestion of internal organs<br/>'
        '4. <b>F</b>luidity of blood (dark fluid blood)<br/>'
        '5. <b>L</b>eft heart chambers empty; right side contains dark fluid blood',
        styles, bg=LIGHT_BLUE, border=MED_BLUE
    ))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Tardieu\'s Spots', styles['SubSection']))
    story.append(Paragraph('Petechial hemorrhages caused by raised venous pressure and rupture of thin-walled venules. Found above the level of obstruction in strangulation. Locations: conjunctivae, sclerae, skin of face/forehead, visceral pleura, pericardium, thymus.', styles['BodyText']))

    story.append(Spacer(1, 0.3*cm))
    story.append(Paragraph('Hanging', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    story.append(Paragraph('"Hanging is always SUICIDAL unless proven otherwise." — Standard medico-legal principle', ParagraphStyle('quote', fontName='Helvetica-Oblique', fontSize=10, textColor=MED_BLUE, alignment=TA_CENTER, spaceAfter=8)))

    hang_headers = ['Classification', 'Type A', 'Type B']
    hang_rows = [
        ['By suspension', 'Complete (entire body suspended)', 'Partial (toes/feet touch ground)'],
        ['By knot position', 'Typical (knot at occiput/back of neck)', 'Atypical (knot at front or sides)'],
    ]
    story.append(make_table(hang_headers, hang_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Cause of Death in Hanging', styles['SubSection']))
    story.append(Paragraph('Most common: (3) Combined asphyxia + venous congestion. In order:', styles['BodyText']))
    cods = [
        '1. Asphyxia (most common overall) — airway compression',
        '2. Venous congestion — jugular vein compression (2 kg force)',
        '3. Combined asphyxia + venous congestion (commonest cause)',
        '4. Cerebral anemia — carotid artery compression (4–5 kg force)',
        '5. Reflex vagal inhibition (cardiac arrest)',
        '6. Fracture/dislocation of cervical vertebrae (judicial hanging — CI and CII)',
    ]
    for c in cods:
        story.append(Paragraph(c, styles['BulletText']))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Ligature Mark in Hanging vs Strangulation', styles['SubSection']))
    lig_headers = ['Feature', 'Hanging', 'Strangulation']
    lig_rows = [
        ['Direction', 'Oblique/upward (inverted V shape)', 'Horizontal/transverse'],
        ['Position', 'Above thyroid cartilage', 'At/below thyroid cartilage'],
        ['Continuity', 'Discontinuous (gap at knot site)', 'Continuous (encircles neck)'],
        ['Depth', 'Shallow (partial suspension) / deep (complete)', 'Deep, uniform'],
        ['Vital reaction', 'May be present', 'Usually well-marked'],
        ['Abrasions', 'May show parchment-like hardness', 'Ecchymosis/bruising common'],
        ['Nature', 'Usually suicidal', 'Usually homicidal'],
    ]
    story.append(make_table(lig_headers, lig_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Drowning', styles['SubSection']))
    drown_headers = ['Type', 'Mechanism', 'Features']
    drown_rows = [
        ['Wet drowning (80–90%)', 'Water enters lungs', 'Frothy fluid in airways, lungs pale/waterlogged'],
        ['Dry drowning (10–20%)', 'Laryngospasm; no water in lungs', 'Lungs dry, asphyxial features'],
        ['Immersion syndrome', 'Sudden cardiac arrest on cold water contact', 'Vagal inhibition; body found in water with no drowning signs'],
        ['Secondary drowning', 'Delayed pulmonary edema hours after near-drowning', 'ARDS pattern'],
    ]
    story.append(make_table(drown_headers, drown_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(key_fact_box('Diatom Test (Gold Standard for Drowning)',
        ['Diatoms are siliceous algae found in water',
         'Found in blood/organs (bone marrow most reliable) in antemortem drowning',
         'Postmortem immersion: diatoms only in lungs/airways, NOT in deeper organs',
         'Best preserved in: bone marrow (resistant to decomposition)']))
    story.append(PageBreak())

    # ─── CHAPTER 3: WOUNDS ───────────────────────────────────────────────────
    story.append(chapter_header('CHAPTER 3', 'WOUNDS & INJURIES', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Classification of Mechanical Injuries', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    w_headers = ['Type', 'Weapon / Mechanism', 'Key Features']
    w_rows = [
        ['Abrasion', 'Blunt/rough surface; friction', 'Superficial; preserves direction of force; heals without scar; grazed abrasion shows direction'],
        ['Contusion (Bruise)', 'Blunt force; rupture of capillaries', 'No skin breach; color changes with age (red→blue→green→yellow→brown)'],
        ['Laceration', 'Blunt force, overstretching', 'Irregular margins, tissue bridges, contamination; no true incised wound'],
        ['Incised wound', 'Sharp cutting weapon (knife, glass)', 'Clean cut edges, longer than deep, bleeding profuse, no tissue bridges'],
        ['Stab/Puncture', 'Sharp pointed weapon', 'Depth > length; weapon type identifiable; most fatal weapon'],
        ['Chop wound', 'Heavy sharp weapon (axe, hatchet)', 'Combination of incised + contused; bone may be split'],
    ]
    story.append(make_table(w_headers, w_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Age of Bruise (Color Changes)', styles['SubSection']))
    bruise_headers = ['Color', 'Approximate Age']
    bruise_rows = [
        ['Red (erythema)', 'Fresh — immediately'],
        ['Blue/purple', '1–3 days'],
        ['Green', '4–5 days (biliverdin)'],
        ['Yellow', '5–7 days (bilirubin)'],
        ['Brown/Yellowish', '7–14 days (hemosiderin)'],
        ['Disappears', '2–4 weeks'],
    ]
    story.append(make_table(bruise_headers, bruise_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Firearm Wounds', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    fw_headers = ['Feature', 'Entry Wound', 'Exit Wound']
    fw_rows = [
        ['Size', 'Smaller (skin elasticity)', 'Larger (blow-out)'],
        ['Shape', 'Round/oval, punched-out', 'Irregular, slit-like, stellate'],
        ['Margins', 'Inverted/punched-in', 'Everted/blown out'],
        ['Abrasion collar', 'Present (circular, around wound)', 'Absent'],
        ['Soiling/soot', 'Present (esp. contact/close range)', 'Absent'],
        ['Burning/singeing', 'May be present (close range)', 'Absent'],
        ['Bone injury', 'Punched-out (internal beveling)', 'Beveled outward'],
        ['Bleeding', 'Less at entry', 'Profuse at exit'],
    ]
    story.append(make_table(fw_headers, fw_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    # Shotgun wound image
    sg_img = get_image_from_url('https://cdn.orris.care/cdss_images/1f584c599e379f23aef818476636a25300cb15be4380b7a4f61102d9c96dc6ee.png')
    if sg_img:
        story.append(KeepTogether([
            sg_img,
            Paragraph('Fig 3.1: Tight-contact shotgun wound of chest — muzzle imprint visible (upper right)', styles['Caption'])
        ]))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Range of Firearm Discharge', styles['SubSection']))
    range_headers = ['Range', 'Characteristics']
    range_rows = [
        ['Contact (hard contact)', 'Muzzle imprint on skin; star-shaped laceration; gas enters wound; soot inside wound track'],
        ['Close range (<15 cm)', 'Blackening (soot deposit), burning, tattooing (unburnt powder), singeing of hair'],
        ['Intermediate (15–60 cm)', 'Tattooing/stippling (powder grains embedded in skin); no blackening'],
        ['Distant (>60 cm)', 'No soot, no tattooing; abrasion collar only'],
    ]
    story.append(make_table(range_headers, range_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(key_fact_box('NEET PG High-Yield: Firearm',
        ['Ricochet bullet: irregular entry wound; no burning/tattooing',
         'Homicidal shooting: shot in back, multiple wounds, unknown range',
         'Suicidal shooting: contact wound at temple/mouth; soot +++ inside skull',
         'Accidental shooting: any range; context dependent',
         '"Rat-hole" entry: when barrel pressed firmly against skin']))
    story.append(PageBreak())

    # ─── CHAPTER 4: TOXICOLOGY ────────────────────────────────────────────────
    story.append(chapter_header('CHAPTER 4', 'FORENSIC TOXICOLOGY', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Corrosive Poisons', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    corr_headers = ['Poison', 'Key Features', 'Antidote']
    corr_rows = [
        ['Sulphuric Acid (Oil of Vitriol)', 'Black burns/eschar; teeth eroded; DNA of victim may be found in clothes', 'Dilution + alkali; NO emetic'],
        ['Nitric Acid', 'Yellow stains (xanthoproteic reaction); yellow eschar', 'As above'],
        ['Hydrochloric Acid (Muriatic acid)', 'White stains; fumes; acid test on metals', 'Alkali'],
        ['Carbolic Acid (Phenol)', 'White eschar → brown; peculiar smell; cardiac arrest; nephrotoxic', 'Gastric lavage with castor oil/olive oil'],
        ['Oxalic Acid', 'Colorless crystals; hypocalcemia; tetany; renal failure; green burns', 'Calcium gluconate; dilute lime water'],
    ]
    story.append(make_table(corr_headers, corr_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Organophosphate (OP) Poisoning', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    story.append(Paragraph('Most common agricultural/suicidal poison in India. Inhibits acetylcholinesterase → accumulation of acetylcholine.', styles['BodyText']))
    story.append(mnemonic_box('DUMBELS (Muscarinic effects of OP)',
        'D = Diarrhea/Defecation | U = Urination | M = Miosis | B = Bradycardia/Bronchospasm | E = Emesis | L = Lacrimation | S = Salivation/Sweating'))
    story.append(Spacer(1, 0.2*cm))
    story.append(mnemonic_box('MTWTF (Nicotinic effects)',
        'M = Muscle weakness | T = Twitching | W = Weakness | T = Tremor | F = Fasciculations (then paralysis)'))
    story.append(Spacer(1, 0.3*cm))

    op_headers = ['Feature', 'Detail']
    op_rows = [
        ['Mechanism', 'Irreversible inhibition of AChE; phosphorylation of serine residue'],
        ['Diagnosis (lab)', 'Plasma/RBC cholinesterase levels (RBC ChE more sensitive)'],
        ['Antidote 1', 'Atropine (blocks muscarinic effects) — high dose; titrate to dry secretions'],
        ['Antidote 2', 'Pralidoxime (PAM 2-PAM) — reactivates AChE; give early before "aging" occurs'],
        ['Aging', 'Irreversible binding of OP to AChE; PAM ineffective after 24–48 hrs'],
        ['Intermediate syndrome', 'Occurs 24–96 hrs after apparent recovery; proximal muscle weakness, CN palsies, respiratory failure'],
        ['Rigor mortis', 'Appears early in OP poisoning'],
    ]
    story.append(make_table(op_headers, op_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Carbon Monoxide (CO) Poisoning', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    co_headers = ['Feature', 'Detail']
    co_rows = [
        ['Source', 'Incomplete combustion; car exhaust, coal gas, fires'],
        ['Mechanism', 'CO + Hb → COHb (carboxyhemoglobin); 240× higher affinity than O₂'],
        ['COHb %', '<10%: no symptoms | 20–30%: headache | 40–50%: collapse | >60%: death'],
        ['Color of body/skin/blood', 'Cherry red (COHb is cherry red) — PM lividity cherry red'],
        ['Postmortem lividity', 'Cherry red / bright pink'],
        ['Rigor mortis', 'Delayed onset and prolonged duration'],
        ['Treatment', 'Remove from source; 100% O₂; hyperbaric O₂ for severe cases'],
        ['Antidote', 'Oxygen (100%); hyperbaric O₂ (COHb > 25%)'],
    ]
    story.append(make_table(co_headers, co_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Cyanide Poisoning', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    cy_headers = ['Feature', 'Detail']
    cy_rows = [
        ['Mechanism', 'Inhibits cytochrome c oxidase (complex IV); histotoxic hypoxia'],
        ['Sources', 'Hydrocyanic acid (HCN), potassium/sodium cyanide, bitter almonds, cassava'],
        ['Smell', 'Bitter almonds (HCN)'],
        ['Color of blood/organs', 'Bright red (oxygenated venous blood — cannot use O₂)'],
        ['Speed', 'Fastest-acting poison; death in minutes'],
        ['Antidote', '1. Amyl nitrite (inhale) → methemoglobin formation → binds CN⁻ 2. Sodium nitrite IV 3. Sodium thiosulfate IV (converts CN to thiocyanate) 4. Hydroxocobalamin (preferred in modern practice)'],
    ]
    story.append(make_table(cy_headers, cy_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(info_box(
        '<b>Comparison: CO vs Cyanide</b><br/>'
        'Both → cherry/bright red blood | CO → COHb | CN → prevents O₂ utilization<br/>'
        'CO → slow onset | CN → rapid (minutes)<br/>'
        'CO antidote: O₂ | CN antidote: Hydroxocobalamin / Nitrite-thiosulfate',
        styles, bg=LIGHT_RED, border=RED
    ))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Alcohol (Ethanol) — Medico-Legal Aspects', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    alc_headers = ['Blood Alcohol Level (mg%)', 'Effects']
    alc_rows = [
        ['10–50 mg% (subclinical)', 'Mild euphoria; reaction time slightly impaired'],
        ['50–100 mg% (euphoria)', 'Talkativeness; slight incoordination; driving impairment'],
        ['100–150 mg% (excitement)', 'Slurred speech, incoordination, impaired judgment'],
        ['150–200 mg% (confusion)', 'Staggering gait, emotional lability'],
        ['200–300 mg% (stupor)', 'Stupor, vomiting, incontinence'],
        ['300–400 mg% (coma)', 'Coma, loss of pain response, respiratory depression'],
        ['>400–500 mg% (death)', 'Respiratory failure, death'],
    ]
    story.append(make_table(alc_headers, alc_rows, styles))
    story.append(Spacer(1, 0.3*cm))
    story.append(key_fact_box('Legal Limits',
        ['India (Motor Vehicles Act): ≥30 mg/100 mL blood (or 150 µg/100 mL breath) = drunk driving',
         'Widmark formula: C = A / (W × r) — estimates BAC; r factor: men 0.68, women 0.55',
         'Forensic sample: Vitreous humor best preserved post-mortem (most reliable)']))
    story.append(PageBreak())

    # ─── CHAPTER 5: IDENTIFICATION ───────────────────────────────────────────
    story.append(chapter_header('CHAPTER 5', 'IDENTIFICATION & MEDICOLEGAL ASPECTS', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Age Estimation', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    age_headers = ['Age Group', 'Method', 'Key Landmarks']
    age_rows = [
        ['Fetus', 'Crown-heel length (Haase rule)', 'Months 1–5: length = month² cm; months 5–9: length = month × 5 cm'],
        ['Infant', 'Ossification centers + teeth', 'Cuboid at birth; capitate/hamate at 3–6 mo; teeth eruption (lower central incisor at 6–8 mo)'],
        ['Child (1–15 yr)', 'Dental eruption + bone fusion', 'Deciduous complete at 2.5 yr; permanent complete (except 3rd molar) by 12–13 yr'],
        ['Adult', 'Radiological bone changes + wisdom tooth + epiphyseal fusion', '3rd molar: 17–25 yr; Gustafson\'s method for dental age; iliac crest fuses at 22–25 yr'],
        ['Elderly (>45 yr)', 'Degenerative changes, pubic symphysis, sternal ossification', 'Gustafson\'s 6 criteria: attrition, secondary dentin, cementum apposition, root resorption, periodontal disease, root translucency'],
    ]
    story.append(make_table(age_headers, age_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Sex Determination', styles['SubSection']))
    sex_headers = ['Bone', 'Male', 'Female']
    sex_rows = [
        ['Skull', 'Heavy, supra-orbital ridges prominent, mastoid large', 'Light, smooth, mastoid small'],
        ['Pelvis', 'Android (heart-shaped inlet, narrow)', 'Gynecoid (oval inlet, wide)'],
        ['Sciatic notch', 'Narrow (<60°)', 'Wide (>60°)'],
        ['Sub-pubic angle', 'Acute (<70°)', 'Obtuse (>90°)'],
        ['Femur', 'Longer, vertical neck-shaft angle', 'Shorter, more oblique'],
    ]
    story.append(make_table(sex_headers, sex_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Fingerprint Identification', styles['SubSection']))
    story.append(Paragraph('Fingerprints are permanent, unique, and can be classified. Sir Francis Galton established their uniqueness. Sir Edward Henry developed the classification system used in India.', styles['BodyText']))

    fp_headers = ['Type', 'Features']
    fp_rows = [
        ['Loop (65–70%)', 'Most common; one delta; ulnar loop (opens toward ulnar side) > radial loop'],
        ['Whorl (25–30%)', 'Two deltas; most complex pattern'],
        ['Arch (5%)', 'No delta; simplest pattern; ridges curve upward'],
        ['Composite', 'Combination patterns (tented arch, etc.)'],
    ]
    story.append(make_table(fp_headers, fp_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(mnemonic_box('GALTON\'S Fingerprint Contributions',
        'G = Galton (uniqueness) | H = Henry (classification system for India) | Loops = Most common | Whorls = Two deltas'))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Key Medicolegal Acts', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    acts_headers = ['Act/Section', 'Key Provision']
    acts_rows = [
        ['IPC Section 299/300', 'Culpable homicide vs Murder; S.300 = Murder (4 conditions); S.299 = Culpable homicide not amounting to murder'],
        ['IPC Section 304A', 'Death by negligence (not intentional); relevant for medical negligence'],
        ['IPC Section 320', 'Defines grievous hurt (8 types); includes emasculation, eye injury, permanent disfiguration'],
        ['IPC Section 375/376', 'Rape and punishment (minimum 7 years/10 years life for gang rape)'],
        ['IPC Section 312', 'Causing miscarriage (illegal abortion); Section 313 — without woman\'s consent'],
        ['MTP Act 1971 (amended 2021)', 'Legal termination of pregnancy; ≤20 wks (now ≤24 wks for special categories) with single RMP opinion'],
        ['PCPNDT Act 1994', 'Prohibits sex determination; regulates pre-natal diagnostic techniques'],
        ['CrPC Section 174', 'Police inquest for sudden/unnatural deaths'],
        ['Indian Evidence Act (BSA 2023)', 'Section 27 — discovery of fact from accused; dying declaration under S.32; expert opinion'],
    ]
    story.append(make_table(acts_headers, acts_rows, styles))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph('Viscera Preservation', styles['SubSection']))
    visc_headers = ['Poison Suspected', 'Viscera Collected', 'Preservative Used']
    visc_rows = [
        ['General/unknown', 'Stomach + contents, intestine, liver (500g), kidney (1/2), brain, blood, urine', 'Saturated salt solution (NaCl)'],
        ['Alcohol', 'Blood, urine, vitreous humor', 'Sodium fluoride + oxalate (NOT formalin/spirit)'],
        ['CO poisoning', 'Blood (dark or cherry red)', 'Sealed vial; no preservative (air-tight)'],
        ['Metallic poisons (As, Pb, Hg)', 'Liver, kidney, bone, hair, nails, urine', 'Rectified spirit or saturated salt'],
        ['Volatile poisons', 'Stomach + contents, blood, lung', 'Sealed; NO preservative (volatile will evaporate)'],
        ['Pesticides (OP)', 'Stomach, liver, brain, blood', 'Saturated salt solution'],
    ]
    story.append(make_table(visc_headers, visc_rows, styles))
    story.append(PageBreak())

    # ─── CHAPTER 6: MCQs ──────────────────────────────────────────────────────
    story.append(chapter_header('CHAPTER 6', 'NEET PG HIGH-YIELD MCQs', styles))
    story.append(Spacer(1, 0.4*cm))

    mcqs = [
        ("A body is found with greenish discoloration of skin over the right iliac fossa, bloating, and skin slippage. The MOST likely time since death in summer (India) is:",
         ["6–12 hours", "24–48 hours", "3–5 days", "2–3 weeks"],
         2, "Green discoloration over cecum = 24–48 hrs; skin slippage = 3–5 days. Combined features indicate ~3–5 days."),
        ("Cadaveric spasm differs from rigor mortis in that it:",
         ["Involves all muscles simultaneously", "Occurs after a period of primary flaccidity", "Is instantaneous and involves muscles in use at time of death", "Can be broken by passive stretching and will return"],
         2, "Cadaveric spasm is instantaneous, no primary flaccidity, affects muscles in use — medico-legal significance: weapon in hand suggests suicide."),
        ("Rigor mortis is caused by:",
         ["Lactic acid accumulation alone", "Formation of insoluble actomyosin complex due to ATP depletion", "Calcium influx into muscle cells", "Cessation of glycolysis only"],
         1, "ATP depletion → actin + myosin form insoluble actomyosin; lactic acid is also produced but the mechanism is actomyosin complex."),
        ("A hanging death shows the ligature mark:",
         ["Horizontal, continuous around the neck", "Oblique, upward direction, discontinuous (gap at knot site)", "Deep, horizontal below thyroid cartilage", "Multiple parallel marks"],
         1, "Hanging ligature = oblique, upward (inverted V), discontinuous at knot site; above thyroid cartilage."),
        ("The 'diatom test' for drowning is BEST performed on:",
         ["Lung tissue", "Liver", "Bone marrow", "Brain"],
         2, "Bone marrow is most reliable — resistant to decomposition; diatoms found here only in antemortem drowning (not postmortem immersion)."),
        ("Entry gunshot wound is characterized by:",
         ["Larger size, everted margins", "Abrasion collar, smaller, punched-out appearance", "Soot only on exit side", "Absence of Fackler cavitation"],
         1, "Entry: smaller, round, abrasion collar (lacerating zone), punched-in margins. Exit: larger, irregular, everted, no abrasion collar."),
        ("Carbon monoxide poisoning is characterized by:",
         ["Yellow discoloration of skin", "Cherry-red postmortem lividity", "Early onset of rigor mortis", "Miosis and lacrimation"],
         1, "COHb → cherry-red color of blood and lividity. CO also delays/prolongs rigor mortis."),
        ("The antidote for organophosphate poisoning that reactivates acetylcholinesterase is:",
         ["Atropine", "Pralidoxime (2-PAM)", "Physostigmine", "Diazepam"],
         1, "Pralidoxime reactivates AChE. Atropine blocks muscarinic effects but does NOT reactivate AChE."),
        ("For suspected alcohol poisoning, vitreous humor is the BEST specimen because:",
         ["It contains more alcohol", "It is most resistant to postmortem diffusion and decomposition", "It yields more reliable Widmark factor", "It is easiest to collect"],
         1, "Vitreous humor is enclosed and isolated — least affected by postmortem decomposition or redistribution."),
        ("The MOST reliable criterion for age estimation above 45 years using Gustafson's method is:",
         ["Attrition (tooth wear)", "Root transparency", "Secondary dentin formation", "Cementum apposition"],
         1, "Root transparency (translucency from the apex) is the most reliable single indicator in Gustafson's method for elderly age estimation."),
    ]

    for i, (q, opts, ans_idx, exp) in enumerate(mcqs):
        elems = mcq_block(q, opts, ans_idx, exp, styles)
        bg = LIGHT_BLUE if i % 2 == 0 else LIGHT_GREEN
        q_data = [[e] for e in elems]
        # Flatten all elements into single table cell
        combined_text = f'<b>Q{i+1}. {q}</b><br/>'
        letters = ['A', 'B', 'C', 'D']
        for j, o in enumerate(opts):
            combined_text += f'{letters[j]}. {o}<br/>'
        combined_text += f'<font color="#166534"><b>✓ Ans: {letters[ans_idx]}. {opts[ans_idx]}</b> — {exp}</font>'
        t = Table([[Paragraph(combined_text, ParagraphStyle('mcqp', fontName='Helvetica', fontSize=9.5, textColor=DARK_TEXT, leading=14))]], colWidths=[17*cm])
        t.setStyle(TableStyle([
            ('BACKGROUND', (0,0), (-1,-1), bg),
            ('BOX', (0,0), (-1,-1), 1, MED_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),
        ]))
        story.append(t)
        story.append(Spacer(1, 0.2*cm))

    story.append(PageBreak())

    # ─── QUICK REFERENCE TABLES ───────────────────────────────────────────────
    story.append(chapter_header('QUICK REFERENCE', 'RAPID REVISION TABLES', styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Poisons — Quick Summary', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    pq_headers = ['Poison', 'Color of Blood/PM changes', 'Key Feature', 'Antidote']
    pq_rows = [
        ['CO', 'Cherry red lividity', 'COHb; delayed rigor; source = vehicle exhaust', '100% O₂; HBO'],
        ['Cyanide', 'Bright red blood; bitter almond smell', 'Histotoxic hypoxia; fastest acting', 'Hydroxocobalamin; nitrite-thiosulfate'],
        ['OP compounds', 'Miosis + bronchospasm', 'DUMBELS; reduced cholinesterase', 'Atropine + PAM'],
        ['Arsenic', 'Garlic smell; Mees lines; hyperkeratosis', 'Chronic: rain-drop pigmentation on skin', 'Dimercaprol (BAL); DMSA'],
        ['Lead', 'Burton\'s line (blue-black gums)', 'Basophilic stippling of RBCs', 'EDTA; DMSA; BAL'],
        ['Dhatura (Atropine)', 'Flushed, hot, dry skin; dilated pupils', '"Blind as bat, Red as beet, Mad as hatter, Hot as hare, Dry as bone"', 'Physostigmine'],
        ['Morphine/Opioids', 'Miosis; respiratory depression; coma', 'Pinpoint pupils + coma = CLASSIC triad', 'Naloxone'],
        ['Strychnine', 'Opisthotonos; tetanic convulsions', 'Spasms triggered by stimuli; risus sardonicus', 'Diazepam; muscle relaxants'],
        ['Methanol', 'Metabolic acidosis; blindness', 'Formic acid → retinal/optic nerve damage', 'Fomepizole; ethanol; folinic acid'],
    ]
    story.append(make_table(pq_headers, pq_rows, styles))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph('Eponyms in Forensic Medicine (NEET PG Favorites)', styles['SectionTitle']))
    story.append(HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE))
    story.append(Spacer(1, 0.2*cm))

    ep_headers = ['Eponym', 'What it means']
    ep_rows = [
        ['Tardieu\'s spots', 'Petechial hemorrhages in asphyxia (pericardium, conjunctiva, pleura)'],
        ['Nysten\'s law (Rule of 12)', 'Rigor mortis proceeds jaw → neck → trunk → upper limbs → lower limbs'],
        ['Winslow\'s/Magnus/Icard test', 'Old tests for signs of death (historical interest only)'],
        ['Gustafson\'s method', 'Dental age estimation by 6 criteria (attrition, periodontosis, secondary dentin, root resorption, cementum apposition, root transparency)'],
        ['Puppe\'s rule', 'In multiple gunshot wounds, each fracture stops at a previously formed fracture line — helps determine sequence'],
        ['Locard\'s exchange principle', 'Every contact leaves a trace — basis of forensic trace evidence'],
        ['Beneke\'s method', 'Weight of organs for age estimation'],
        ['Henssge nomogram', 'Temperature-based time of death estimation using ambient temp and body weight'],
        ['Haase\'s rule', 'Estimation of fetal age from crown-heel length (1–5 months: month²; 5–9 months: month × 5)'],
        ['Widmark formula', 'Blood alcohol calculation: C = A/(W × r); r = 0.68 (M), 0.55 (F)'],
    ]
    story.append(make_table(ep_headers, ep_rows, styles))
    story.append(Spacer(1, 0.5*cm))

    # Footer note
    story.append(info_box(
        '<b>Note:</b> This PDF is compiled from authoritative forensic medicine textbooks for NEET PG exam preparation. '
        'Sources: Essentials of Forensic Medicine & Toxicology 36th Ed. (2026), PC Dikshit Textbook, Parikh\'s Textbook. '
        'Always cross-reference with your institution\'s guidelines.',
        styles, bg=LIGHT_GRAY, border=GRAY
    ))

    doc.build(story)
    print(f"PDF generated: {OUTPUT_PATH}")
    return OUTPUT_PATH

if __name__ == '__main__':
    generate()
Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 generate_pdf.py 2>&1

Editing File

~/forensic-neet-pg/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/forensic-neet-pg/generate_pdf.py'
Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 generate_pdf.py 2>&1 && l…

Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 -c " import re with open…

Model call limits exceeded: run limit (20/20)

Wr is pdf

Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 -c " with open('generate_…

Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 -W ignore generate_pdf.py…

Writing File

~/forensic-neet-pg/gen.py

#!/usr/bin/env python3
"""High-Yield Forensic Medicine PDF — NEET PG"""

import requests, io
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib.colors import HexColor, white, black
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
                                 TableStyle, Image, PageBreak, HRFlowable, KeepTogether)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from PIL import Image as PILImage

DEEP_BLUE   = HexColor('#1a3a5c')
MED_BLUE    = HexColor('#2563EB')
LIGHT_BLUE  = HexColor('#EFF6FF')
ORANGE      = HexColor('#EA580C')
GREEN       = HexColor('#166534')
LIGHT_GREEN = HexColor('#F0FDF4')
RED         = HexColor('#DC2626')
LIGHT_RED   = HexColor('#FEF2F2')
YELLOW_BG   = HexColor('#FEFCE8')
YELLOW_BDR  = HexColor('#CA8A04')
GRAY        = HexColor('#6B7280')
LIGHT_GRAY  = HexColor('#F9FAFB')
DARK_TEXT   = HexColor('#1F2937')
PURPLE      = HexColor('#7C3AED')
PURPLE_BG   = HexColor('#F5F3FF')

OUT = "/home/daytona/workspace/forensic-neet-pg/Forensic_Medicine_HighYield_NEET_PG.pdf"

# ── Styles ────────────────────────────────────────────────────────────────────
def S():
    base = getSampleStyleSheet()
    def add(name, **kw):
        base.add(ParagraphStyle(name, **kw))
    # Override BodyText in place
    base['BodyText'].fontName = 'Helvetica'
    base['BodyText'].fontSize = 10
    base['BodyText'].textColor = DARK_TEXT
    base['BodyText'].alignment = TA_JUSTIFY
    base['BodyText'].spaceAfter = 4
    base['BodyText'].leading = 14

    add('CoverTitle',  fontName='Helvetica-Bold', fontSize=30, textColor=white, alignment=TA_CENTER, spaceAfter=8, leading=36)
    add('CoverSub',    fontName='Helvetica', fontSize=13, textColor=HexColor('#BFD7ED'), alignment=TA_CENTER, spaceAfter=6)
    add('CoverRef',    fontName='Helvetica-Oblique', fontSize=10, textColor=HexColor('#93C5FD'), alignment=TA_CENTER, spaceAfter=4)
    add('ChapTitle',   fontName='Helvetica-Bold', fontSize=17, textColor=white, alignment=TA_LEFT, spaceAfter=2)
    add('SecTitle',    fontName='Helvetica-Bold', fontSize=13, textColor=DEEP_BLUE, alignment=TA_LEFT, spaceAfter=4, spaceBefore=10)
    add('SubSec',      fontName='Helvetica-Bold', fontSize=11, textColor=MED_BLUE, alignment=TA_LEFT, spaceAfter=3, spaceBefore=6)
    add('Bullet',      fontName='Helvetica', fontSize=10, textColor=DARK_TEXT, alignment=TA_LEFT, spaceAfter=3, leading=14, leftIndent=15)
    add('Caption',     fontName='Helvetica-Oblique', fontSize=9, textColor=GRAY, alignment=TA_CENTER, spaceAfter=6)
    add('TH',          fontName='Helvetica-Bold', fontSize=9, textColor=white, alignment=TA_CENTER)
    add('TC',          fontName='Helvetica', fontSize=9, textColor=DARK_TEXT, alignment=TA_LEFT, leading=12)
    add('MCQStem',     fontName='Helvetica-Bold', fontSize=10, textColor=DEEP_BLUE, alignment=TA_LEFT, spaceAfter=2, leading=14)
    add('MCQOpt',      fontName='Helvetica', fontSize=10, textColor=DARK_TEXT, alignment=TA_LEFT, spaceAfter=1, leading=13, leftIndent=15)
    add('MCQAns',      fontName='Helvetica-Bold', fontSize=10, textColor=GREEN, alignment=TA_LEFT, spaceAfter=4, leading=13, leftIndent=15)
    add('Quote',       fontName='Helvetica-Oblique', fontSize=10, textColor=MED_BLUE, alignment=TA_CENTER, spaceAfter=8)
    add('KFtext',      fontName='Helvetica', fontSize=9.5, textColor=HexColor('#713f12'), leading=14)
    add('MNtext',      fontName='Helvetica', fontSize=9.5, textColor=DARK_TEXT, leading=14)
    add('MNhead',      fontName='Helvetica-Bold', fontSize=10, textColor=PURPLE)
    add('InfoTxt',     fontName='Helvetica', fontSize=10, textColor=DARK_TEXT, leading=14, alignment=TA_LEFT)
    return base

# ── Helper builders ────────────────────────────────────────────────────────────
def img_from_url(url, max_w=13*cm, max_h=8*cm):
    try:
        r = requests.get(url, timeout=15)
        r.raise_for_status()
        buf = io.BytesIO(r.content)
        pil = PILImage.open(buf)
        w, h = pil.size
        ratio = h / w
        iw = min(max_w, w)
        ih = iw * ratio
        if ih > max_h:
            ih = max_h
            iw = ih / ratio
        buf.seek(0)
        return Image(buf, width=iw, height=ih)
    except Exception as e:
        print(f"IMG FAIL {url}: {e}")
        return None

def box(text, s, bg=LIGHT_BLUE, border=MED_BLUE):
    t = Table([[Paragraph(text, s['InfoTxt'])]], colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), bg),
        ('BOX', (0,0), (-1,-1), 1.5, border),
        ('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 t

def kf_box(title, items, s):
    text = f'<b>⭐ {title}</b><br/>' + '<br/>'.join(f'• {i}' for i in items)
    t = Table([[Paragraph(text, s['KFtext'])]], colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), YELLOW_BG),
        ('BOX', (0,0), (-1,-1), 1.5, YELLOW_BDR),
        ('TOPPADDING', (0,0), (-1,-1), 7), ('BOTTOMPADDING', (0,0), (-1,-1), 7),
        ('LEFTPADDING', (0,0), (-1,-1), 10), ('RIGHTPADDING', (0,0), (-1,-1), 10),
    ]))
    return t

def mn_box(title, meaning, s):
    t = Table([
        [Paragraph(f'<b>MNEMONIC: {title}</b>', s['MNhead'])],
        [Paragraph(meaning, s['MNtext'])],
    ], colWidths=[17*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), PURPLE_BG),
        ('BOX', (0,0), (-1,-1), 1.5, PURPLE),
        ('TOPPADDING', (0,0), (-1,-1), 6), ('BOTTOMPADDING', (0,0), (-1,-1), 7),
        ('LEFTPADDING', (0,0), (-1,-1), 10), ('RIGHTPADDING', (0,0), (-1,-1), 10),
    ]))
    return t

def chap_hdr(num, title, s):
    t = Table([[Paragraph(f'<b>{num}</b>', s['ChapTitle']),
                Paragraph(title, s['CoverSub'])]], colWidths=[5*cm, 12*cm])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), DEEP_BLUE),
        ('TOPPADDING', (0,0), (-1,-1), 10), ('BOTTOMPADDING', (0,0), (-1,-1), 10),
        ('LEFTPADDING', (0,0), (-1,-1), 14), ('RIGHTPADDING', (0,0), (-1,-1), 10),
        ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
    ]))
    return t

def tbl(headers, rows, s, col_w=None):
    data = [[Paragraph(h, s['TH']) for h in headers]]
    for row in rows:
        data.append([Paragraph(str(c), s['TC']) for c in row])
    n = len(headers)
    cw = col_w or [17*cm/n]*n
    t = Table(data, colWidths=cw, repeatRows=1)
    t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), DEEP_BLUE),
        ('GRID', (0,0), (-1,-1), 0.5, HexColor('#D1D5DB')),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_BLUE]),
        ('TOPPADDING', (0,0), (-1,-1), 5), ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 6), ('RIGHTPADDING', (0,0), (-1,-1), 6),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    return t

def hr(s):
    return HRFlowable(width='100%', thickness=1.5, color=DEEP_BLUE)

def sec(txt, s):
    return [Paragraph(txt, s['SecTitle']), hr(s), Spacer(1, 0.2*cm)]

def subsec(txt, s):
    return [Paragraph(txt, s['SubSec'])]

def sp(n=0.3):
    return Spacer(1, n*cm)

# ── Main generator ─────────────────────────────────────────────────────────────
def generate():
    s = S()
    doc = SimpleDocTemplate(OUT, pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm, topMargin=2.2*cm, bottomMargin=2.2*cm,
        title="High-Yield Forensic Medicine — NEET PG", author="Orris Medical")
    story = []

    # COVER
    story.append(sp(1.2))
    story.append(Table([[Paragraph(
        '<b>FORENSIC MEDICINE</b><br/><font size="15">& Toxicology</font>',
        s['CoverTitle'])]],
        colWidths=[17*cm], style=[
            ('BACKGROUND', (0,0), (-1,-1), DEEP_BLUE),
            ('TOPPADDING', (0,0), (-1,-1), 18), ('BOTTOMPADDING', (0,0), (-1,-1), 6),
            ('LEFTPADDING', (0,0), (-1,-1), 20), ('RIGHTPADDING', (0,0), (-1,-1), 20),
        ]))
    story.append(Table([[Paragraph('NEET PG  ·  High-Yield Rapid Revision Notes', s['CoverSub'])]],
        colWidths=[17*cm], style=[
            ('BACKGROUND', (0,0), (-1,-1), DEEP_BLUE),
            ('BOTTOMPADDING', (0,0), (-1,-1), 14),
            ('LEFTPADDING', (0,0), (-1,-1), 20), ('RIGHTPADDING', (0,0), (-1,-1), 20),
        ]))
    story.append(sp(0.5))
    story.append(Table([[Paragraph(
        'Based on: Essentials of Forensic Medicine &amp; Toxicology 36th Ed. (2026)  |  PC Dikshit  |  Parikh\'s Textbook',
        s['CoverRef'])]], colWidths=[17*cm]))
    story.append(sp(0.6))

    topics = [
        ['📋 Forensic Thanatology', '🩸 Wounds & Injuries', '🔫 Firearm Injuries'],
        ['🪢 Asphyxia & Hanging', '☠️ Forensic Toxicology', '🧬 Identification'],
        ['⚖️ Medical Jurisprudence', '🔬 Viscera Preservation', '📝 NEET PG MCQs'],
    ]
    tdata = [[Paragraph(f'<b>{c}</b>', ParagraphStyle('tp2', fontName='Helvetica-Bold', fontSize=10,
               textColor=DEEP_BLUE, alignment=TA_CENTER)) for c in row] for row in topics]
    tt = Table(tdata, colWidths=[5.6*cm, 5.6*cm, 5.6*cm])
    tt.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), LIGHT_BLUE),
        ('BOX', (0,0), (-1,-1), 1, MED_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.5, HexColor('#BFDBFE')),
        ('TOPPADDING', (0,0), (-1,-1), 8), ('BOTTOMPADDING', (0,0), (-1,-1), 8),
        ('ALIGN', (0,0), (-1,-1), 'CENTER'),
    ]))
    story.append(tt)
    story.append(sp(0.5))
    story.append(box('🎯 <b>Focus Areas:</b> Postmortem changes · Rigor Mortis · Asphyxia/Hanging/Strangulation · '
                     'Firearm wounds · Poisons (OP, CO, Cyanide, Corrosives, Alcohol) · Age/Sex estimation · Medicolegal acts', s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 1 — POSTMORTEM CHANGES
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 1', 'SIGNS OF DEATH & POSTMORTEM CHANGES', s))
    story.append(sp(0.4))
    story += sec('Signs of Death — Classification', s)
    story.append(tbl(
        ['Stage', 'Timing', 'Key Changes'],
        [['Immediate (Somatic death)', 'At death', 'Loss of consciousness, cessation of respiration & circulation'],
         ['Early (Cellular death)', 'Minutes–hours', 'Pallor, eye changes, primary flaccidity, algor mortis, lividity, rigor mortis'],
         ['Late (Decomposition)', 'Days–weeks', 'Putrefaction, adipocere, mummification']], s))
    story.append(sp())
    story += subsec('Algor Mortis (Cooling of Body)', s)
    story.append(Paragraph(
        'Body cools after death by conduction and convection. Temperature does NOT fall for the first 0.5–1 hour '
        '(postmortem temperature plateau / isothermic phase). Thereafter cooling proceeds at 0.4–0.6°C/hr for 12–16 hours.',
        s['BodyText']))
    story.append(kf_box('Key Facts — Algor Mortis',
        ['Plateau: 0.5–1 hr post death (no cooling)',
         'Linear cooling: 0.4–0.6°C/hr for next 12–16 hrs',
         'Henssge nomogram uses ambient temp + body weight for accurate PMI',
         'Rectal temperature is the standard measurement site'], s))
    story.append(sp())

    # Cooling chart image
    img1 = img_from_url('https://cdn.orris.care/cdss_images/1e72a43bdeaf2fe2d491405f9a78063c76eb8132ecef4bb266ae083fc0c53b50.png')
    if img1:
        story.append(KeepTogether([img1,
            Paragraph('Fig 1.1: Chart showing major postmortem changes over time — Cooling → Lividity → Decomposition', s['Caption'])]))
    story.append(sp())

    story += subsec('Postmortem Lividity (Livor Mortis)', s)
    story.append(tbl(
        ['Feature', 'Detail'],
        [['Mechanism', 'Gravity-dependent settling of blood after circulation stops'],
         ['Colour', 'Pinkish-red/purplish-red; Cherry-red in CO & Cyanide poisoning; Pink in cold/hypothermia'],
         ['Onset', '30 min–2 hrs after death'],
         ['Fixation (unfixable)', 'After 6–8 hrs (may be earlier in Indian summer heat)'],
         ['Fully fixed', '12–18 hrs (cannot shift on repositioning or pressure)'],
         ['Blanching', 'Blanches on pressure initially; does NOT blanch after fixation'],
         ['NEET pearl', 'Fixed lividity at wrong body position → body was moved after 6–8 hrs']], s,
        col_w=[4.5*cm, 12.5*cm]))
    story.append(sp())

    story += subsec('Rigor Mortis', s)
    story.append(Paragraph(
        'Stiffening of muscles due to formation of insoluble <b>actomyosin complex</b> when ATP is depleted. '
        'Affects all voluntary and involuntary muscles including heart, smooth muscle, and even iris (rigor of iris).',
        s['BodyText']))
    story.append(tbl(
        ['Parameter', 'India (Tropical)', 'Temperate Countries'],
        [['Onset', '1–2 hrs after death', '3–6 hrs after death'],
         ['Fully established', '6 hrs (summer)', '12 hrs'],
         ['Duration', '24–48 hrs winter / 18–36 hrs summer', '2–3 days'],
         ['Order (Nysten\'s law)', 'Jaw → Neck → Trunk → Upper limbs → Lower limbs', 'Same'],
         ['Disappearance', 'Same order as appearance (jaw first to disappear)', 'Same']], s))
    story.append(sp(0.2))
    story.append(kf_box('Factors Affecting Rigor Mortis',
        ['EARLY onset + SHORT duration: Exhausting diseases (cholera, typhoid, TB, cancer), violent death (gunshot, electrocution), OP poisoning, hot climate',
         'DELAYED onset + LONG duration: Asphyxia, severe haemorrhage, apoplexy, cold climate, CO poisoning (delays disappearance)',
         'Cadaveric Spasm: Instantaneous rigor at moment of death — no primary flaccidity; medico-legal: weapon in hand = likely suicide',
         '"Rule of 12" (Nysten) NOT applicable in tropical/hot climates — exams often test this'], s))
    story.append(sp(0.2))
    story.append(mn_box('CITE — Causes of EARLY Rigor',
        'C = Cholera/Cancer  |  I = Infections (exhausting)  |  T = Trauma (violent death)  |  E = Electrocution / Extreme heat', s))
    story.append(sp())

    story += subsec('Rigor Mortis vs Cadaveric Spasm', s)
    story.append(tbl(
        ['Feature', 'Rigor Mortis', 'Cadaveric Spasm'],
        [['Onset', 'Gradual (1–6 hrs)', 'Instantaneous (at moment of death)'],
         ['Primary flaccidity', 'Present before rigor', 'Absent'],
         ['Muscles involved', 'All muscles', 'Only muscles in use at time of death'],
         ['Can be broken?', 'Yes (will NOT return)', 'Very difficult to break (extremely firm)'],
         ['Medico-legal use', 'Time since death', 'Weapon in hand → last act before death (suicide vs homicide)'],
         ['Mechanism', 'ATP depletion → actomyosin', 'Sudden nervous energy discharge']], s))
    story.append(sp())

    story += subsec('Putrefaction', s)
    story.append(Paragraph(
        'Decomposition by micro-organisms. <b>Starts in the cecum/colon</b> (most bacteria). '
        'Green discoloration appears first over right iliac fossa, then spreads. Rate doubled for every 10°C rise in temperature.',
        s['BodyText']))
    story.append(tbl(
        ['Sign', 'Timing (India — Summer)'],
        [['Green discoloration over right iliac fossa', '24–48 hrs'],
         ['Bloating / gas distension', '2–3 days'],
         ['Skin slippage & bullae', '3–5 days'],
         ['Liquefaction of soft tissues', '2–3 weeks'],
         ['Skeletonization', 'Months to years']], s, col_w=[9*cm, 8*cm]))
    story.append(sp())

    story += subsec('Adipocere & Mummification', s)
    story.append(tbl(
        ['Feature', 'Adipocere', 'Mummification'],
        [['Process', 'Saponification of body fat → waxy soap-like substance', 'Dehydration / desiccation of tissues'],
         ['Favoured by', 'Warm, moist, humid environment; burial in wet soil', 'Hot, dry, arid climate; desert burial'],
         ['Appearance', 'Waxy, yellowish-white, soft → hard', 'Brown, leathery, shrunken, hard'],
         ['Time', 'Weeks to months', 'Months'],
         ['Medico-legal', 'Preserves features & injuries; can identify person; may last decades', 'Preserves features; identification possible']], s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 2 — ASPHYXIA
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 2', 'ASPHYXIA, HANGING & STRANGULATION', s))
    story.append(sp(0.4))
    story += sec('Cardinal Signs of Asphyxia', s)
    story.append(box(
        '<b>C-P-C-F-L (All 5 must be known)</b><br/>'
        '1. <b>C</b>yanosis — bluish discoloration (face, lips, fingernails)<br/>'
        '2. <b>P</b>etechial haemorrhages (Tardieu\'s spots) — above level of compression<br/>'
        '3. <b>C</b>ongestion of internal organs — lungs dark/purple, viscera congested<br/>'
        '4. <b>F</b>luidity of blood — dark, fluid blood (increased CO₂)<br/>'
        '5. <b>L</b>eft heart chambers empty; right side filled with dark fluid blood', s, bg=LIGHT_BLUE, border=MED_BLUE))
    story.append(sp(0.3))
    story.append(Paragraph(
        '<b>Tardieu\'s Spots:</b> Petechial haemorrhages from raised venous pressure and capillary rupture. '
        'Found in: conjunctivae, sclerae, eyelids, face, scalp, visceral pleura, pericardium, thymus. '
        'In hanging: above ligature mark. In strangulation: above level of compression. '
        'Not pathognomonic — can occur in non-asphyxial deaths (e.g., sepsis, DIC).',
        s['BodyText']))
    story.append(sp())

    story += sec('Hanging', s)
    story.append(Paragraph(
        '"Hanging is always SUICIDAL unless proven otherwise." — Standard medico-legal principle',
        s['Quote']))
    story.append(tbl(
        ['Classification', 'Type A', 'Type B'],
        [['By degree of suspension', 'Complete — body fully suspended', 'Partial — toes/feet touch ground (head + chest weight is enough)'],
         ['By knot position', 'Typical — knot at occiput/nape of neck', 'Atypical — knot at side or front of neck']], s))
    story.append(sp(0.3))
    story += subsec('Cause of Death in Hanging (in order)', s)
    for item in [
        '1. Asphyxia — airway compressed (most common overall)',
        '2. Venous congestion — jugular veins compressed (only 2 kg force needed)',
        '3. Combined asphyxia + venous congestion — COMMONEST cause in practice',
        '4. Cerebral ischaemia — carotid arteries compressed (4–5 kg); vertebral arteries (20 kg)',
        '5. Reflex vagal inhibition — pressure on vagal sheath/carotid bodies',
        '6. Fracture/dislocation of cervical vertebrae (C1–C2) — judicial hanging only (free fall + jerk)',
    ]:
        story.append(Paragraph(item, s['Bullet']))
    story.append(sp(0.3))
    story.append(kf_box('Fatal Period & Recovery',
        ['Usual fatal period: 3–5 minutes',
         'Immediate death: cervical fracture or vagal inhibition',
         'Delayed deaths after rescue: aspiration pneumonia, pulmonary oedema, hypoxic encephalopathy, cerebral infarction',
         'Judicial hanging: fracture at C2 (Hangman\'s fracture) + atlanto-axial dislocation'], s))
    story.append(sp())

    story += sec('Hanging vs Strangulation — Ligature Mark Comparison', s)
    story.append(tbl(
        ['Feature', 'Hanging', 'Strangulation'],
        [['Direction', 'Oblique/upward (inverted V)', 'Horizontal/transverse'],
         ['Position on neck', 'Above thyroid cartilage', 'At or below thyroid cartilage'],
         ['Continuity', 'Discontinuous (gap at knot site)', 'Continuous (360° around neck)'],
         ['Depth', 'Shallow (partial) or deep (complete)', 'Deep, even, uniform'],
         ['Vital reaction', 'May be minimal', 'Well-marked ecchymosis, bruising'],
         ['Appearance', 'Dry, parchment-like, yellowish-brown', 'Well-defined, congested, bruised'],
         ['Nature (usually)', 'Suicidal', 'Homicidal'],
         ['Tardieu\'s spots', 'Above ligature, less prominent', 'Above ligature, very prominent']], s))
    story.append(sp())

    story += sec('Drowning', s)
    story.append(tbl(
        ['Type', 'Mechanism', 'Findings'],
        [['Wet drowning (80–90%)', 'Water enters lungs', 'Frothy fluid in airways, lungs pale/waterlogged, oedematous'],
         ['Dry drowning (10–20%)', 'Laryngospasm; no water in lungs', 'Lungs dry; typical asphyxial features; larynx in spasm'],
         ['Immersion syndrome', 'Sudden cardiac arrest on cold water contact (vagal inhibition)', 'Body in water; NO drowning signs; no water in lungs'],
         ['Secondary drowning', 'Delayed pulmonary oedema hours after near-drowning', 'ARDS; may be fatal 24–72 hrs later']], s))
    story.append(sp(0.3))
    story.append(kf_box('Diatom Test — Gold Standard for Drowning',
        ['Diatoms = siliceous (silica-walled) algae found in water',
         'Antemortem drowning: diatoms found in lung, liver, kidney, BONE MARROW (best site)',
         'Postmortem immersion: diatoms ONLY in lungs/airways, NOT in deeper organs',
         'Bone marrow most reliable — resistant to decomposition and contamination',
         'Important: match diatom species to water source to confirm drowning location'], s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 3 — WOUNDS
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 3', 'WOUNDS & MECHANICAL INJURIES', s))
    story.append(sp(0.4))
    story += sec('Classification of Mechanical Injuries', s)
    story.append(tbl(
        ['Wound Type', 'Causative Weapon', 'Defining Features'],
        [['Abrasion', 'Blunt/rough surface, friction', 'Superficial; epidermis only; heals without scar; preserves direction of force; patterned abrasion identifies weapon'],
         ['Contusion (Bruise)', 'Blunt force; no skin breach', 'Haemorrhage into tissues; color changes with age; no skin breach; may track'],
         ['Laceration', 'Blunt force / overstretching', 'Irregular, ragged edges; tissue bridges; contamination; NOT clean cut'],
         ['Incised wound', 'Sharp cutting (knife, razor, glass)', 'Clean edges; longer than deep; no tissue bridges; profuse bleeding; tailing at one end'],
         ['Stab/Puncture', 'Sharp pointed weapon', 'Depth > width/length; shape may reveal weapon; most dangerous (internal injury possible with small surface wound)'],
         ['Chop wound', 'Heavy sharp weapon (axe, hatchet)', 'Clean cut + contusion; bone may be split; wide gape']], s))
    story.append(sp())

    story += subsec('Age of Bruise — Colour Changes (NEET Favourite)', s)
    story.append(tbl(
        ['Colour', 'Approximate Age', 'Cause'],
        [['Red (erythema)', 'Fresh — immediately', 'Oxyhaemoglobin'],
         ['Blue / Purple', '1–3 days', 'Deoxyhaemoglobin'],
         ['Green', '4–5 days', 'Biliverdin (haem oxidation)'],
         ['Yellow', '5–7 days', 'Bilirubin'],
         ['Brown / Yellowish-brown', '7–14 days', 'Haemosiderin'],
         ['Disappears', '2–4 weeks', 'Complete reabsorption']], s))
    story.append(sp(0.3))
    story.append(box('⚠️ <b>NEET Pearl:</b> In courts, colour of bruise used to estimate time. Green = ~4–5 days = important for assault timing. '
                     'Bruises do NOT follow strict rules — only an approximation. Deep bruises may surface days later.',
                     s, bg=LIGHT_RED, border=RED))
    story.append(sp())

    story += sec('Firearm Wounds', s)
    story.append(tbl(
        ['Feature', 'Entry Wound', 'Exit Wound'],
        [['Size', 'Smaller (skin elasticity)', 'Larger (blow-out effect)'],
         ['Shape', 'Round/oval, punched-out, clean', 'Irregular, slit-like, stellate'],
         ['Margins', 'Inverted/punched in', 'Everted/blown out'],
         ['Abrasion collar', 'Present (ring/collar around wound)', 'Absent'],
         ['Soot/blackening', 'Present at close/contact range', 'Absent'],
         ['Tattooing/stippling', 'Intermediate range (15–60 cm)', 'Absent'],
         ['Burning/singeing', 'May be present (close range)', 'Absent'],
         ['Beveling of bone', 'Internal beveling (smaller inside)', 'External beveling (larger outside)'],
         ['Grease/wipe ring', 'Present (lubricant from bullet)', 'Absent']], s))
    story.append(sp(0.3))

    sg_img = img_from_url('https://cdn.orris.care/cdss_images/1f584c599e379f23aef818476636a25300cb15be4380b7a4f61102d9c96dc6ee.png')
    if sg_img:
        story.append(KeepTogether([sg_img,
            Paragraph('Fig 3.1: Tight-contact shotgun wound of chest — circular wound with muzzle imprint (upper right)', s['Caption'])]))
    story.append(sp(0.3))

    story += subsec('Range of Fire', s)
    story.append(tbl(
        ['Range', 'Characteristics'],
        [['Contact (hard/tight contact)', 'Muzzle imprint; star/cruciate laceration (gas tearing); soot inside wound track; minimal external soot'],
         ['Close range (<15 cm)', 'External blackening (soot), burning of skin/hair, tattooing, singeing'],
         ['Intermediate range (15–60 cm)', 'Tattooing/stippling only (powder grains embedded in skin); no blackening'],
         ['Distant range (>60 cm)', 'Abrasion collar only; no soot, no tattooing, no burning']], s, col_w=[5*cm, 12*cm]))
    story.append(sp(0.3))
    story.append(kf_box('Medico-Legal Classification of Firearm Deaths',
        ['Suicidal: Contact/close range; at temple, mouth, or chest; soot inside skull/wound; one wound',
         'Homicidal: Multiple wounds; any range; entry may be in back; no powder marks common',
         'Accidental: Any range; contextual; often single wound',
         'Puppe\'s Rule: Each new fracture (from subsequent shots) stops at an existing fracture line — determines sequence of shots'], s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 4 — TOXICOLOGY
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 4', 'FORENSIC TOXICOLOGY', s))
    story.append(sp(0.4))
    story += sec('Corrosive Poisons', s)
    story.append(tbl(
        ['Poison', 'Key Features', 'Antidote/Management'],
        [['Sulphuric Acid (H₂SO₄)', 'Black charred eschar; teeth eroded; hygroscopic (absorbs tissue water)', 'Dilution with water; milk; alkali (sodium bicarb); NO emetic'],
         ['Nitric Acid (HNO₃)', 'Yellow eschar (xanthoproteic reaction — yellow colour with proteins)', 'Alkali; dilution'],
         ['Hydrochloric Acid (HCl)', 'White eschar; HCl fumes; corrosive to metals', 'Alkali'],
         ['Carbolic Acid (Phenol)', 'White → brown eschar; peculiar smell (phenolic); cardiac arrest; nephrotoxic', 'Gastric lavage with castor oil / olive oil; NO alcohol (enhances absorption)'],
         ['Oxalic Acid', 'Colourless crystals; hypocalcaemia → tetany → cardiac arrest; green burns on lips/mouth; renal failure (calcium oxalate crystals in kidney)', 'Calcium gluconate IV; dilute lime water orally; generous IV fluids']], s))
    story.append(sp())

    story += sec('Organophosphate (OP) Poisoning', s)
    story.append(Paragraph(
        'Most common agricultural/suicidal poison in India. Inhibits acetylcholinesterase (AChE) → '
        'acetylcholine accumulates → overstimulation of muscarinic and nicotinic receptors.',
        s['BodyText']))
    story.append(mn_box('DUMBELS — Muscarinic Effects of OP',
        'D = Diarrhoea/Defecation  |  U = Urination  |  M = Miosis (small pupils)  |  '
        'B = Bradycardia + Bronchospasm + Bronchorhea  |  E = Emesis  |  L = Lacrimation  |  S = Salivation + Sweating', s))
    story.append(sp(0.2))
    story.append(mn_box('MTWTF — Nicotinic Effects of OP',
        'M = Muscle weakness  |  T = Twitching  |  W = Weakness progressing  |  T = Tremors  |  F = Fasciculations → Flaccid paralysis', s))
    story.append(sp(0.3))
    story.append(tbl(
        ['Feature', 'Detail'],
        [['Mechanism', 'Irreversible phosphorylation of serine at active site of AChE'],
         ['Lab diagnosis', 'RBC cholinesterase (most sensitive) and plasma cholinesterase levels (both reduced)'],
         ['Antidote 1 — Atropine', 'Blocks MUSCARINIC effects only; does NOT reactivate AChE; give high doses (2–4 mg IV; repeat every 5–10 min to dry secretions)'],
         ['Antidote 2 — Pralidoxime (2-PAM)', 'REACTIVATES AChE; must be given EARLY before "aging" occurs (within 24–48 hrs)'],
         ['Aging', 'Irreversible bond strengthening between OP and AChE; PAM ineffective after aging'],
         ['Intermediate syndrome', '24–96 hrs after apparent recovery; proximal muscle weakness, cranial nerve palsies, respiratory failure'],
         ['Rigor mortis', 'Appears EARLY in OP poisoning']], s, col_w=[5*cm, 12*cm]))
    story.append(sp())

    story += sec('Carbon Monoxide (CO) Poisoning', s)
    story.append(tbl(
        ['Feature', 'Detail'],
        [['Source', 'Incomplete combustion — car exhaust, coal gas, fires, generators'],
         ['Mechanism', 'CO + Hb → COHb (240× higher affinity than O₂); shifts ODC left; cellular hypoxia'],
         ['COHb % correlation', '<10%: no symptoms | 20–30%: headache, dyspnea | 40–50%: collapse | >60%: death'],
         ['Colour of blood/skin', 'Cherry red (COHb is bright cherry red) — PM lividity cherry red'],
         ['Rigor mortis', 'DELAYED onset; PROLONGED duration'],
         ['Treatment', '100% O₂ (reduces COHb half-life from 5 hrs → 60–90 min); Hyperbaric O₂ (severe: COHb >25%, CNS symptoms, pregnancy)'],
         ['Antidote', '100% O₂; Hyperbaric O₂']], s, col_w=[4.5*cm, 12.5*cm]))
    story.append(sp())

    story += sec('Cyanide Poisoning', s)
    story.append(tbl(
        ['Feature', 'Detail'],
        [['Mechanism', 'Inhibits cytochrome c oxidase (Complex IV of mitochondrial ETC) → histotoxic (cellular) hypoxia — cells cannot use O₂'],
         ['Sources', 'Hydrocyanic acid (HCN), KCN/NaCN, bitter almonds (amygdalin), cassava, cherry/apricot seeds'],
         ['Smell', 'Bitter almonds (HCN)'],
         ['Blood colour', 'Bright red — venous blood oxygenated (cannot use O₂); arteries and veins same colour'],
         ['Speed', 'Fastest-acting true cellular poison; death in minutes (HCN) to hours (KCN)'],
         ['Antidote', '1. Amyl nitrite (inhale) → MetHb binds CN⁻  2. Sodium nitrite IV  3. Sodium thiosulfate IV (CN → thiocyanate)  4. Hydroxocobalamin (Co binds CN; preferred modern antidote)']], s, col_w=[4.5*cm, 12.5*cm]))
    story.append(sp(0.3))
    story.append(box(
        '<b>CO vs Cyanide — Key Differences</b><br/>'
        '• Both → cherry/bright red blood and cherry-red lividity<br/>'
        '• CO → slow onset (hours); CN → rapid (minutes)<br/>'
        '• CO → COHb; CN → normal Hb but cells cannot use O₂<br/>'
        '• CO antidote: O₂ / hyperbaric O₂  |  CN antidote: Hydroxocobalamin + Nitrite-Thiosulfate',
        s, bg=LIGHT_RED, border=RED))
    story.append(sp())

    story += sec('Alcohol (Ethanol) — Medico-Legal Aspects', s)
    story.append(tbl(
        ['Blood Alcohol Concentration (BAC)', 'Clinical Effects'],
        [['<10 mg% (10 mg/100 mL)', 'No detectable effects'],
         ['10–50 mg%', 'Mild euphoria; reaction time slightly impaired'],
         ['50–100 mg%', 'Talkativeness; slight incoordination; impaired driving'],
         ['100–150 mg%', 'Slurred speech, incoordination, impaired judgment'],
         ['150–200 mg%', 'Staggering gait, emotional lability, nausea'],
         ['200–300 mg%', 'Stupor, vomiting, incontinence'],
         ['300–400 mg%', 'Deep coma, loss of pain response, respiratory depression'],
         ['>400–500 mg%', 'Respiratory failure, death']], s, col_w=[5*cm, 12*cm]))
    story.append(sp(0.3))
    story.append(kf_box('Legal & Forensic Alcohol Points',
        ['India Motor Vehicles Act: ≥30 mg/100 mL blood (or 150 µg/100 mL breath) = drunk driving offence',
         'Widmark formula: C = A ÷ (W × r)  where r = 0.68 (males), 0.55 (females)',
         'Best postmortem sample: Vitreous humour (most resistant to redistribution and decomposition)',
         'Denatured spirit = ethanol + methanol or pyridine (toxic adulterant)',
         'Methanol poisoning: formic acid → optic nerve damage → blindness; antidote: fomepizole / ethanol'], s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 5 — IDENTIFICATION
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 5', 'IDENTIFICATION & MEDICOLEGAL ASPECTS', s))
    story.append(sp(0.4))
    story += sec('Age Estimation', s)
    story.append(tbl(
        ['Stage', 'Method', 'Key Landmarks'],
        [['Fetus', 'Crown-heel length (Haase\'s rule)', 'Months 1–5: length = month² cm; months 5–9: length = month × 5 cm'],
         ['Infant (0–1 yr)', 'Ossification centres + teeth', 'Cuboid present at birth; Capitate+Hamate: 3–6 months; Lower central incisor erupts 6–8 months'],
         ['Child (1–15 yr)', 'Dental eruption + bone fusion', 'Deciduous set complete: 2.5 yr; Permanent (ex 3rd molar) complete: 12–13 yr'],
         ['Young adult (15–25 yr)', 'Epiphyseal fusion + wisdom tooth', '3rd molar eruption: 17–25 yr; Iliac crest fuses: 22–25 yr; Medial clavicle: 25–30 yr'],
         ['Adult (>45 yr)', 'Gustafson\'s dental method + degenerative changes', '6 criteria: Attrition, Periodontosis, Secondary dentin, Root resorption, Cementum apposition, Root Translucency (most reliable)']], s))
    story.append(sp(0.3))
    story.append(mn_box('Gustafson\'s 6 Criteria (APSRCT)',
        'A = Attrition  |  P = Periodontosis  |  S = Secondary dentin  |  R = Root resorption  |  C = Cementum apposition  |  T = Translucency of root (MOST reliable)', s))
    story.append(sp())

    story += sec('Sex Determination from Skeleton', s)
    story.append(tbl(
        ['Bone', 'Male', 'Female'],
        [['Skull', 'Heavy; prominent supra-orbital ridges; large mastoid; sloping forehead', 'Light; smooth; small mastoid; vertical forehead'],
         ['Pelvis (most reliable)', 'Android type; heart-shaped inlet; narrow', 'Gynecoid type; oval/round inlet; wide'],
         ['Greater sciatic notch', 'Narrow (<60°)', 'Wide (>60°) — most reliable single indicator'],
         ['Sub-pubic angle', 'Acute (<70°)', 'Obtuse (>90°)'],
         ['Stature', 'Taller; limbs longer', 'Shorter; more gracile'],
         ['Overall accuracy (pelvis)', '95–98%', 'Same']], s))
    story.append(sp())

    story += sec('Fingerprint Classification', s)
    story.append(Paragraph(
        '<b>Sir Francis Galton</b> established uniqueness of fingerprints. '
        '<b>Sir Edward Henry</b> developed the classification system used in India. '
        'Fingerprints are permanent (from ~12th week of fetal life) and unique (even in identical twins).',
        s['BodyText']))
    story.append(tbl(
        ['Pattern', 'Frequency', 'Deltas', 'Key Feature'],
        [['Loop', '65–70% (MOST COMMON)', 'One delta', 'Ulnar loop > Radial loop; open on one side'],
         ['Whorl', '25–30%', 'Two deltas', 'Circular/spiral; most complex'],
         ['Arch', '~5% (LEAST COMMON)', 'No delta', 'Simplest; wave pattern; ridges curve upward'],
         ['Composite', 'Rare', 'Variable', 'Combination patterns (tented arch, accidental, etc.)']], s))
    story.append(sp(0.3))
    story.append(mn_box('Locard\'s Exchange Principle',
        '"Every contact leaves a trace" — fundamental basis of forensic trace evidence. '
        'Coined by Dr Edmond Locard. Used in crime scene investigation.', s))
    story.append(sp())

    story += sec('Key Medicolegal Acts (India)', s)
    story.append(tbl(
        ['Act / Section', 'Key Provision'],
        [['IPC S.299/300', 'S.300 = Murder (4 conditions); S.299 = Culpable homicide not amounting to murder'],
         ['IPC S.304A', 'Death by negligence (not intentional) — medical negligence falls here'],
         ['IPC S.320', 'Grievous hurt (8 types): emasculation, permanent sight loss, hearing loss, limb loss, permanent disfiguration, bone fracture/dislocation, life-threatening illness for 20 days'],
         ['IPC S.375/376', 'Rape / sexual assault; minimum 7 years (up to life); gang rape minimum 10 years / life'],
         ['IPC S.312–316', 'Causing miscarriage; S.313 = without woman\'s consent; exceptions under MTP Act'],
         ['MTP Act 1971 (amended 2021)', 'Legal termination up to 20 weeks (single RMP); up to 24 weeks for special categories; >24 wks only if MBS board approves'],
         ['PCPNDT Act 1994', 'Prohibits sex determination of fetus; regulates pre-natal diagnostic techniques; penal consequences'],
         ['CrPC S.174 (now BNSS)', 'Police inquest for sudden/unnatural/suicidal/suspicious deaths'],
         ['IEA (BSA 2023) S.32', 'Dying declaration — admissible even without oath; magistrate preferred but not mandatory; corroboration not required']], s, col_w=[4.5*cm, 12.5*cm]))
    story.append(sp())

    story += sec('Viscera Preservation', s)
    story.append(tbl(
        ['Suspected Poison', 'Viscera Collected', 'Preservative'],
        [['General/unknown', 'Stomach + contents, intestine, liver (500 g), kidney (half), brain, blood, urine', 'Saturated common salt (NaCl) solution'],
         ['Alcohol', 'Blood, urine, vitreous humour', 'Sodium fluoride + potassium oxalate; NEVER formalin or spirit'],
         ['CO poisoning', 'Blood (cherry/dark red)', 'Air-tight sealed vial; NO preservative (CO escapes)'],
         ['Metallic poisons (As, Pb, Hg)', 'Liver, kidney, bone, hair, nails, urine', 'Rectified spirit or saturated salt solution'],
         ['Volatile poisons', 'Stomach + contents, blood, lung', 'Sealed container; NO preservative (volatile will evaporate)'],
         ['Organophosphates', 'Stomach + contents, liver, brain, blood', 'Saturated salt solution']], s))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # CH 6 — MCQs
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('CHAPTER 6', 'NEET PG HIGH-YIELD MCQs (with Explanations)', s))
    story.append(sp(0.4))

    mcqs = [
        ("A body is found with green discoloration over the right iliac fossa, bloating, and skin slippage. Most likely time since death in Indian summer?",
         ["6–12 hours","24–48 hours","3–5 days","2–3 weeks"], 2,
         "Green discoloration at 24–48 hrs; skin slippage at 3–5 days. Combined features = ~3–5 days."),
        ("Cadaveric spasm differs from rigor mortis in that it:",
         ["Involves all muscles simultaneously","Occurs after a period of primary flaccidity","Is instantaneous involving muscles active at time of death","Can be broken and will return"],
         2, "Cadaveric spasm is instantaneous, no primary flaccidity, affects muscles in use. Medico-legal: weapon in hand = likely suicide."),
        ("Rigor mortis is caused by:",
         ["Lactic acid accumulation alone","Formation of insoluble actomyosin complex due to ATP depletion","Calcium influx into muscle cells","Cessation of glycolysis only"],
         1, "ATP depletion → actin + myosin form insoluble actomyosin complex. Lactic acid is a by-product, not the direct cause."),
        ("Ligature mark in hanging is characteristically:",
         ["Horizontal, continuous, below thyroid cartilage","Oblique/upward, discontinuous (gap at knot site), above thyroid cartilage","Deep, transverse, uniform","Multiple parallel horizontal marks"],
         1, "Hanging mark: oblique, upward (inverted V shape), discontinuous at knot, above thyroid cartilage — key distinguishing features."),
        ("The diatom test for drowning is BEST performed on which tissue?",
         ["Lung tissue","Liver","Bone marrow","Brain"],
         2, "Bone marrow: most reliable — resistant to decomposition. In antemortem drowning, diatoms reach deeper organs. In postmortem immersion, only in lungs."),
        ("Entry gunshot wound is distinguished by:",
         ["Larger size, everted margins, no abrasion collar","Smaller size, abrasion collar, inverted margins","Soot deposit on exit side","Stellate appearance always"],
         1, "Entry wound: smaller, round/oval, punched-out, abrasion collar present, inverted margins. Exit: larger, irregular, everted, no collar."),
        ("CO poisoning causes characteristic postmortem lividity colour of:",
         ["Green","Cherry red","Yellow","Pale/white"],
         1, "COHb is cherry red in colour. Both CO and cyanide produce cherry-red lividity. CO also delays/prolongs rigor mortis."),
        ("The antidote that specifically REACTIVATES acetylcholinesterase in OP poisoning is:",
         ["Atropine","Pralidoxime (2-PAM)","Physostigmine","Diazepam"],
         1, "Pralidoxime reactivates AChE by breaking the OP–enzyme bond. Must be given early before 'aging'. Atropine blocks muscarinic receptors but does NOT reactivate AChE."),
        ("Best postmortem specimen for alcohol estimation (most resistant to decomposition):",
         ["Blood","Urine","Vitreous humour","Liver"],
         2, "Vitreous humour is anatomically enclosed; least affected by postmortem redistribution, contamination, and decomposition."),
        ("Most reliable single criterion in Gustafson's method of dental age estimation in elderly:",
         ["Attrition (tooth wear)","Root translucency (transparency from apex)","Secondary dentin formation","Cementum apposition"],
         1, "Root translucency is the MOST reliable indicator in Gustafson's method for age estimation >45 years. Translucency increases with age due to mineral deposition."),
        ("A stab wound is characteristically:",
         ["Longer than it is deep","Wider than deep","Deeper than it is long/wide — depth > length","Always triangular"],
         2, "Stab wound: depth > width/length (unlike incised wound where length > depth). Most forensically dangerous as internal injuries can be severe with small surface wound."),
        ("Tardieu's spots are found in which type of death?",
         ["Drowning only","Asphyxial deaths — above the level of compression","Firearm deaths","Poisoning by corrosives"],
         1, "Tardieu's spots = petechial haemorrhages from raised venous pressure. Found in asphyxial deaths, above the level of compression/obstruction."),
    ]

    for i, (q, opts, ans_idx, exp) in enumerate(mcqs):
        bg = LIGHT_BLUE if i % 2 == 0 else LIGHT_GREEN
        letters = ['A','B','C','D']
        text = f'<b>Q{i+1}. {q}</b><br/>'
        for j, o in enumerate(opts):
            text += f'{letters[j]}. {o}<br/>'
        text += f'<font color="#166534"><b>✓ Answer: {letters[ans_idx]}. {opts[ans_idx]}</b></font><br/>'
        text += f'<font color="#374151"><i>Explanation: {exp}</i></font>'
        t = Table([[Paragraph(text, ParagraphStyle(f'mcq{i}', fontName='Helvetica', fontSize=9.5,
                               textColor=DARK_TEXT, leading=14))]], colWidths=[17*cm])
        t.setStyle(TableStyle([
            ('BACKGROUND', (0,0), (-1,-1), bg),
            ('BOX', (0,0), (-1,-1), 1, MED_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),
        ]))
        story.append(t)
        story.append(sp(0.2))

    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════
    # QUICK REFERENCE
    # ═══════════════════════════════════════════════════════════════
    story.append(chap_hdr('QUICK REFERENCE', 'RAPID REVISION — EPONYMS & POISONS', s))
    story.append(sp(0.4))
    story += sec('Poisons — High-Yield Summary Table', s)
    story.append(tbl(
        ['Poison', 'Colour of Blood/PM', 'Key Distinguishing Feature', 'Antidote'],
        [['CO', 'Cherry red lividity', 'COHb; delayed/prolonged rigor; vehicle exhaust source', '100% O₂; Hyperbaric O₂'],
         ['Cyanide', 'Bright red blood; bitter almond smell', 'Histotoxic hypoxia; fastest-acting; arteries + veins same colour', 'Hydroxocobalamin; Nitrite + Thiosulfate'],
         ['Organophosphate', 'Normal colour', 'Miosis+DUMBELS+bronchospasm; reduced ChE; early rigor', 'Atropine + Pralidoxime (2-PAM)'],
         ['Arsenic', 'Normal', 'Garlic smell; Mees\' lines; rain-drop skin pigmentation (chronic)', 'BAL (Dimercaprol); DMSA'],
         ['Lead', 'Normal', 'Burton\'s blue gum line; basophilic stippling of RBCs; wrist/foot drop', 'EDTA; DMSA; BAL'],
         ['Dhatura (Atropine group)', 'Flushed face', '"Blind bat, Red beet, Mad hatter, Hot hare, Dry bone" — anticholinergic', 'Physostigmine'],
         ['Morphine/Opioids', 'Normal (cyanosis late)', 'Pinpoint pupils + coma + respiratory depression = CLASSIC triad', 'Naloxone (Narcan)'],
         ['Strychnine', 'Dark', 'Opisthotonos; risus sardonicus; tetanic spasms triggered by stimuli', 'Diazepam; muscle relaxants'],
         ['Methanol', 'Normal', 'Metabolic acidosis with high anion gap; optic nerve damage → blindness', 'Fomepizole; Ethanol; Folinic acid']], s))
    story.append(sp())

    story += sec('Forensic Eponyms — NEET PG Favourites', s)
    story.append(tbl(
        ['Eponym', 'Definition'],
        [['Tardieu\'s spots', 'Petechial haemorrhages in asphyxia (pericardium, visceral pleura, conjunctiva)'],
         ['Nysten\'s law', 'Rigor mortis sequence: Jaw → Neck → Trunk → Upper → Lower limbs'],
         ['Gustafson\'s method', 'Dental age estimation using 6 criteria (APSRCT); root translucency = most reliable'],
         ['Puppe\'s rule', 'Each new gunshot fracture stops at a pre-existing fracture line; determines firing sequence'],
         ['Locard\'s principle', '"Every contact leaves a trace" — basis of trace evidence in forensics'],
         ['Haase\'s rule', 'Fetal age: 1–5 months = month² cm; 5–10 months = month × 5 cm (crown-heel length)'],
         ['Widmark formula', 'BAC = A ÷ (W × r); r = 0.68 (males), 0.55 (females)'],
         ['Henssge nomogram', 'Accurate PMI from rectal temperature using ambient temp + body weight correction'],
         ['Galton (fingerprints)', 'Established uniqueness and permanence of fingerprints'],
         ['Henry (fingerprints)', 'Developed fingerprint classification system (loops, whorls, arches) used in India'],
         ['Winslow / Magnus / Icard test', 'Historical tests for signs of death — now of academic interest only'],
         ['Café coronary', 'Sudden death from choking on food bolus (large chunk of meat) — asphyxial mechanism']], s, col_w=[5*cm, 12*cm]))
    story.append(sp(0.5))
    story.append(box(
        '<b>Source:</b> This PDF is compiled from The Essentials of Forensic Medicine &amp; Toxicology 36th Ed. (2026), '
        'PC Dikshit\'s Textbook of Forensic Medicine &amp; Toxicology, and Parikh\'s Textbook of Medical Jurisprudence. '
        'For exam use only — always verify with updated guidelines.',
        s, bg=LIGHT_GRAY, border=GRAY))

    doc.build(story)
    print(f"✅ PDF generated: {OUT}")

if __name__ == '__main__':
    generate()
Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 gen.py 2>&1

Running Command

python3 -c "from reportlab.lib.styles import getSampleStyleSheet; s=getSampleSty…

Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 -c " with open('gen.py','…

Running Command

cd /home/daytona/workspace/forensic-neet-pg && sed -i "s/add('Bullet',/add('Bull…

Running Command

cd /home/daytona/workspace/forensic-neet-pg && python3 -c " data = open('gen.py'…

Your PDF is ready! Here is the download link:

Forensic Medicine HighYield NEET PG

PDF Document · PDF


What's inside (1.3 MB, ~20 pages):
ChapterTopics Covered
1 - Postmortem ChangesAlgor/Livor/Rigor mortis, Cadaveric spasm, Putrefaction, Adipocere, Mummification + cooling chart image
2 - AsphyxiaCardinal signs, Tardieu's spots, Hanging vs Strangulation (ligature mark table), Drowning types, Diatom test
3 - WoundsAbrasion/Contusion/Laceration/Incised/Stab classification, Age of bruise colour table, Firearm entry vs exit wound, Range of fire + shotgun image
4 - ToxicologyCorrosives, OP poisoning (DUMBELS mnemonic), CO, Cyanide, Alcohol (legal limits, Widmark formula), Viscera preservation
5 - IdentificationAge estimation (Haase/Gustafson's method), Sex from skeleton, Fingerprint classification, Medicolegal acts (MTP, PCPNDT, IPC)
6 - 12 MCQsNEET PG-style with answers + explanations
Quick ReferencePoison summary table, All forensic eponyms
Sources: Essentials of Forensic Medicine & Toxicology 36th Ed. (2026), PC Dikshit, Parikh's Textbook.
This is a shared conversation. Sign in to Orris to start your own chat.