Create BDC sallubays of anatomy upper limb joint and lower limb joint Nots
| Feature | Details |
|---|---|
| Type | Synovial, multiaxial ball-and-socket joint |
| Bones | Head of humerus + glenoid cavity of scapula |
| Capsule | Loose; attached to glenoid labrum & anatomical neck of humerus |
| Feature | Details |
|---|---|
| Type | Compound synovial joint (3 articulations, 1 joint cavity) |
| Bones | Humerus + Radius + Ulna |
| Feature | Details |
|---|---|
| Type | Synovial, condyloid (ellipsoid) joint |
| Bones | Distal radius + articular disc + proximal carpal row (scaphoid, lunate, triquetrum) |
| Note | Ulna does NOT directly articulate - separated by articular disc (TFCC) |
| Feature | Details |
|---|---|
| Type | Synovial, multiaxial ball-and-socket joint |
| Bones | Head of femur + acetabulum of pelvic bone |
| Design | Stability > Mobility (opposite of shoulder) |
| Ligament | Origin | Insertion | Function |
|---|---|---|---|
| Iliofemoral (Y-lig. of Bigelow) | Anterior inferior iliac spine | Intertrochanteric line | Resists hyperextension; strongest ligament in body |
| Pubofemoral | Pubic part of acetabulum | Anterior intertrochanteric line | Limits abduction + extension |
| Ischiofemoral | Posterior acetabulum | Greater trochanter | Limits medial rotation |
| Feature | Details |
|---|---|
| Type | Largest synovial joint in the body; modified hinge joint |
| Bones | Femur + Tibia + Patella |
| Articulations | 2: tibiofemoral (weight-bearing) + patellofemoral |
| Feature | Medial Meniscus | Lateral Meniscus |
|---|---|---|
| Shape | C-shaped (larger) | O-shaped (smaller, more circular) |
| Attachment | Firmly attached to tibial collateral ligament + joint capsule | NOT attached to fibular collateral ligament; more mobile |
| Injury risk | More commonly torn (less mobile) | Less commonly torn |
| Ligament | Location | Function |
|---|---|---|
| Tibial (medial) collateral ligament | Medial femoral epicondyle → tibia | Resists valgus stress |
| Fibular (lateral) collateral ligament | Lateral femoral epicondyle → head of fibula | Resists varus stress |
| Anterior cruciate ligament (ACL) | Anterior tibial intercondylar area → posterior lateral femoral condyle | Prevents anterior tibial displacement; taut in extension |
| Posterior cruciate ligament (PCL) | Posterior tibial intercondylar area → anterior medial femoral condyle | Prevents posterior tibial displacement; taut in flexion; stronger than ACL |
| Feature | Details |
|---|---|
| Type | Synovial hinge joint |
| Bones | Distal tibia + distal fibula (mortise) + talus |
| Mortise | Formed by medial malleolus (tibia) + lateral malleolus (fibula) + inferior tibial surface |
| Ligament | Side | Parts |
|---|---|---|
| Deltoid ligament (medial) | Medial | 4 parts: tibionavicular, anterior/posterior tibiotalar, tibiocalcaneal; very strong |
| Lateral ligament | Lateral | 3 parts: Anterior talofibular (ATFL - most commonly injured in ankle sprains), Calcaneofibular (CFL), Posterior talofibular (PTFL) |
| Joint | Type | Key Movements | Most Common Clinical Problem |
|---|---|---|---|
| Shoulder | Ball & socket | All (most mobile) | Anterior dislocation |
| Elbow | Compound hinge/pivot | Flex/ext + pro/sup | Supracondylar fracture (children) |
| Wrist (radiocarpal) | Condyloid | Flex/ext + deviation | Colles' fracture |
| Hip | Ball & socket | All (most stable) | Neck of femur fracture → AVN |
| Knee | Modified hinge | Flex/ext + rotation | ACL tear, meniscal tear |
| Ankle | Hinge | Plantarflex/dorsiflex | Lateral ligament sprain (ATFL) |
| Joint | Nerves |
|---|---|
| Shoulder | Axillary, suprascapular, lateral pectoral |
| Elbow | Radial (main), musculocutaneous, median, ulnar |
| Wrist | Anterior & posterior interosseous, radial, ulnar |
| Joint | Nerves |
|---|---|
| Hip | Femoral, obturator, sciatic, superior gluteal |
| Knee | Femoral, obturator, sciatic (common peroneal + tibial) |
| Ankle | Deep peroneal, tibial |
Create a detailed PDF summary of these joint notes with diagrams
mkdir -p /tmp/workspace/joint-notes-pdf && ls /tmp/workspace/
~/joint-notes-pdf/generate_pdf.py
"""
BDC Anatomy Notes: Upper Limb & Lower Limb Joints
Detailed PDF with diagrams using ReportLab
"""
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.lib.units import inch, mm
from reportlab.lib.pagesizes import A4
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.graphics.shapes import Drawing, Rect, Circle, Line, String, Polygon, Ellipse, Group
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
import math
# ─── Colour Palette ────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a6b")
MED_BLUE = colors.HexColor("#2563a8")
LIGHT_BLUE = colors.HexColor("#dce8f7")
ACCENT = colors.HexColor("#e05c1a")
GOLD = colors.HexColor("#c89a2a")
LIGHT_GOLD = colors.HexColor("#fff8e1")
GREEN = colors.HexColor("#1a7a4a")
LIGHT_GREEN = colors.HexColor("#e6f4ec")
RED = colors.HexColor("#c0392b")
LIGHT_RED = colors.HexColor("#fdecea")
PURPLE = colors.HexColor("#5b2d8e")
LIGHT_PURPLE= colors.HexColor("#ede0f7")
GRAY_LIGHT = colors.HexColor("#f4f6f8")
GRAY_MED = colors.HexColor("#b0bec5")
DARK = colors.HexColor("#222222")
WHITE = colors.white
PAGE_W, PAGE_H = A4
MARGIN = 18 * mm
# ─── Styles ────────────────────────────────────────────────────────────────────
def make_styles():
base = getSampleStyleSheet()
s = {}
s['cover_title'] = ParagraphStyle('cover_title',
fontName='Helvetica-Bold', fontSize=28, textColor=WHITE,
alignment=TA_CENTER, leading=34, spaceAfter=8)
s['cover_sub'] = ParagraphStyle('cover_sub',
fontName='Helvetica', fontSize=13, textColor=colors.HexColor("#cde3f7"),
alignment=TA_CENTER, leading=18, spaceAfter=4)
s['cover_badge'] = ParagraphStyle('cover_badge',
fontName='Helvetica-Bold', fontSize=11, textColor=GOLD,
alignment=TA_CENTER, leading=16)
s['part_heading'] = ParagraphStyle('part_heading',
fontName='Helvetica-Bold', fontSize=18, textColor=WHITE,
alignment=TA_CENTER, leading=22, spaceBefore=6, spaceAfter=6)
s['joint_title'] = ParagraphStyle('joint_title',
fontName='Helvetica-Bold', fontSize=14, textColor=WHITE,
leading=18, spaceBefore=4, spaceAfter=2)
s['section_head'] = ParagraphStyle('section_head',
fontName='Helvetica-Bold', fontSize=11, textColor=DARK_BLUE,
leading=14, spaceBefore=8, spaceAfter=3,
borderPad=3)
s['body'] = ParagraphStyle('body',
fontName='Helvetica', fontSize=9, textColor=DARK,
leading=13, spaceBefore=1, spaceAfter=1)
s['bullet'] = ParagraphStyle('bullet',
fontName='Helvetica', fontSize=9, textColor=DARK,
leading=12, leftIndent=14, bulletIndent=4,
spaceBefore=1, spaceAfter=1)
s['clinical'] = ParagraphStyle('clinical',
fontName='Helvetica', fontSize=9, textColor=RED,
leading=12, leftIndent=14, bulletIndent=4,
spaceBefore=1, spaceAfter=1)
s['caption'] = ParagraphStyle('caption',
fontName='Helvetica-Oblique', fontSize=8, textColor=GRAY_MED,
alignment=TA_CENTER, leading=11, spaceAfter=4)
s['tbl_hdr'] = ParagraphStyle('tbl_hdr',
fontName='Helvetica-Bold', fontSize=8.5, textColor=WHITE,
alignment=TA_CENTER, leading=11)
s['tbl_cell'] = ParagraphStyle('tbl_cell',
fontName='Helvetica', fontSize=8.5, textColor=DARK,
leading=11)
s['tbl_cell_bold'] = ParagraphStyle('tbl_cell_bold',
fontName='Helvetica-Bold', fontSize=8.5, textColor=DARK_BLUE,
leading=11)
s['footer'] = ParagraphStyle('footer',
fontName='Helvetica', fontSize=7.5, textColor=GRAY_MED,
alignment=TA_CENTER)
return s
S = make_styles()
# ─── Utility Flowables ─────────────────────────────────────────────────────────
class ColorBox(Flowable):
"""A filled rounded rectangle used as a section header background."""
def __init__(self, w, h, fill_color, radius=6):
Flowable.__init__(self)
self.width = w; self.height = h
self.fill_color = fill_color; self.radius = radius
def draw(self):
self.canv.setFillColor(self.fill_color)
self.canv.roundRect(0, 0, self.width, self.height,
self.radius, fill=1, stroke=0)
class JointHeaderBlock(Flowable):
"""Coloured header banner for each joint."""
def __init__(self, number, title, joint_type, color, width):
Flowable.__init__(self)
self.number = number; self.title = title
self.joint_type = joint_type; self.color = color
self.width = width; self.height = 38
def draw(self):
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
# Number circle
c.setFillColor(WHITE)
c.circle(20, self.height/2, 13, fill=1, stroke=0)
c.setFillColor(self.color)
c.setFont('Helvetica-Bold', 12)
c.drawCentredString(20, self.height/2 - 4, self.number)
# Title
c.setFillColor(WHITE)
c.setFont('Helvetica-Bold', 13)
c.drawString(42, self.height/2 + 3, self.title)
# Type badge
c.setFont('Helvetica', 8)
c.setFillColor(colors.HexColor("#ffffffaa"))
badge_text = f" {self.joint_type} "
c.drawString(42, self.height/2 - 11, badge_text)
class DiagramFlowable(Flowable):
"""Base class - all joint diagrams inherit from this."""
def __init__(self, width, height):
Flowable.__init__(self)
self.width = width
self.height = height
def _label(self, c, x, y, text, fontsize=7, color=DARK, bold=False):
c.setFillColor(color)
c.setFont('Helvetica-Bold' if bold else 'Helvetica', fontsize)
c.drawCentredString(x, y, text)
def _label_left(self, c, x, y, text, fontsize=7, color=DARK):
c.setFillColor(color)
c.setFont('Helvetica', fontsize)
c.drawRightString(x, y, text)
def _label_right(self, c, x, y, text, fontsize=7, color=DARK):
c.setFillColor(color)
c.setFont('Helvetica', fontsize)
c.drawString(x, y, text)
def _arrow_line(self, c, x1, y1, x2, y2, color=GRAY_MED, width=0.7):
c.setStrokeColor(color)
c.setLineWidth(width)
c.line(x1, y1, x2, y2)
class ShoulderDiagram(DiagramFlowable):
def draw(self):
c = self.canv
cx, cy = self.width/2, self.height/2 + 5
# Background
c.setFillColor(LIGHT_BLUE)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
# Scapula glenoid (socket)
c.setFillColor(colors.HexColor("#b0bec5"))
c.setStrokeColor(DARK_BLUE)
c.setLineWidth(1.5)
c.ellipse(cx-52, cy-18, cx-28, cy+18, fill=1, stroke=1)
# Glenoid labrum (ring)
c.setFillColor(colors.HexColor("#e0c070"))
c.setStrokeColor(GOLD)
c.setLineWidth(1.2)
c.ellipse(cx-56, cy-22, cx-24, cy+22, fill=0, stroke=1)
# Humerus head (ball)
c.setFillColor(colors.HexColor("#d0d8e8"))
c.setStrokeColor(DARK_BLUE)
c.setLineWidth(1.5)
c.circle(cx+5, cy, 26, fill=1, stroke=1)
# Humerus shaft
c.setFillColor(colors.HexColor("#d0d8e8"))
c.setStrokeColor(DARK_BLUE)
c.setLineWidth(1.5)
c.rect(cx+14, cy-42, 18, 42, fill=1, stroke=1)
# Joint capsule outline
c.setStrokeColor(MED_BLUE)
c.setLineWidth(1.2)
c.setDash([3,2])
c.ellipse(cx-30, cy-30, cx+38, cy+30, fill=0, stroke=1)
c.setDash([])
# Acromion / coracoid process (simplified)
c.setFillColor(colors.HexColor("#90a4ae"))
c.setStrokeColor(DARK_BLUE)
c.setLineWidth(1)
# Acromion - flat bone above
pts = [cx-40, cy+38, cx+10, cy+38, cx+18, cy+28, cx-32, cy+28]
c.polygon(pts, fill=1, stroke=1)
# Coracohumeral ligament line
c.setStrokeColor(GREEN)
c.setLineWidth(1.4)
c.line(cx-20, cy+28, cx+5, cy+20)
# Rotator cuff arc
c.setStrokeColor(ACCENT)
c.setLineWidth(2)
c.arc(cx-18, cy-30, cx+36, cy+36, 30, 120)
# Labels
self._label(c, cx-40, cy+42, "Acromion", 7, DARK_BLUE, True)
self._label(c, cx-40, cy-26, "Glenoid\nLabrum", 6.5, GOLD)
self._label(c, cx+5, cy+6, "Humeral\nHead", 6.5, DARK_BLUE)
self._label(c, cx+5, cy-48, "Humerus\nShaft", 6.5, DARK_BLUE)
self._label(c, cx-40, cy+3, "Glenoid\nFossa", 6.5, colors.HexColor("#546e7a"))
self._label(c, cx+28, cy+24, "Rotator\nCuff", 6.5, ACCENT)
self._label(c, cx-5, cy+22, "Coracohumeral\nLig.", 6, GREEN)
# Title
self._label(c, self.width/2, 8, "Shoulder (Glenohumeral) Joint - Schematic", 7.5, DARK_BLUE, True)
class ElbowDiagram(DiagramFlowable):
def draw(self):
c = self.canv
cx, cy = self.width/2, self.height/2 + 8
# Background
c.setFillColor(LIGHT_GREEN)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
bone_color = colors.HexColor("#d0d8e8")
bone_stroke = DARK_BLUE
# Humerus (upper bone)
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-18, cy+10, 36, 48, fill=1, stroke=1)
# Medial epicondyle
c.ellipse(cx-28, cy+8, cx-14, cy+22, fill=1, stroke=1)
# Lateral epicondyle
c.ellipse(cx+14, cy+8, cx+28, cy+22, fill=1, stroke=1)
# Trochlea
c.setFillColor(colors.HexColor("#a8c0d6"))
c.ellipse(cx-15, cy+5, cx+3, cy+18, fill=1, stroke=1)
# Capitulum
c.setFillColor(colors.HexColor("#a8c0d6"))
c.ellipse(cx+3, cy+5, cx+18, cy+18, fill=1, stroke=1)
# Ulna
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-20, cy-55, 20, 58, fill=1, stroke=1)
# Olecranon
c.ellipse(cx-22, cy, cx-2, cy+16, fill=1, stroke=1)
# Coronoid process
c.polygon([cx-20, cy+4, cx-8, cy+4, cx-8, cy-2, cx-20, cy-2], fill=1, stroke=1)
# Radius
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
# Radial head circle
c.circle(cx+10, cy+2, 10, fill=1, stroke=1)
c.rect(cx+3, cy-55, 14, 55, fill=1, stroke=1)
# Annular ligament
c.setFillColor(colors.HexColor("#e0c07080"))
c.setStrokeColor(GOLD)
c.setLineWidth(2)
c.ellipse(cx+1, cy-7, cx+19, cy+11, fill=0, stroke=1)
# MCL line
c.setStrokeColor(RED); c.setLineWidth(2)
c.line(cx-24, cy+14, cx-14, cy-4)
# LCL line
c.setStrokeColor(MED_BLUE); c.setLineWidth(2)
c.line(cx+22, cy+14, cx+18, cy-4)
# Labels
self._label(c, cx, cy+58, "Humerus", 7, DARK_BLUE, True)
self._label(c, cx-22, cy+24, "Medial\nEpicondyle", 6, DARK_BLUE)
self._label(c, cx+22, cy+24, "Lateral\nEpicondyle", 6, DARK_BLUE)
self._label(c, cx-10, cy+10, "Trochlea", 6, colors.HexColor("#546e7a"))
self._label(c, cx+10, cy+10, "Capitulum", 6, colors.HexColor("#546e7a"))
self._label(c, cx-28, cy-10, "Olecranon", 6.5, DARK_BLUE)
self._label_right(c, cx+42, cy+2, "Radial Head", 6.5, DARK_BLUE)
self._label_right(c, cx+42, cy-10, "Annular Lig.", 6.5, GOLD)
self._label_left(c, cx-32, cy-10, "MCL", 6.5, RED)
self._label_right(c, cx+38, cy-18, "LCL", 6.5, MED_BLUE)
self._label(c, self.width/2, 8, "Elbow Joint - Schematic (Anterior View)", 7.5, DARK_BLUE, True)
class HipDiagram(DiagramFlowable):
def draw(self):
c = self.canv
cx, cy = self.width/2, self.height/2 + 8
# Background
c.setFillColor(LIGHT_PURPLE)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
bone_color = colors.HexColor("#d0d8e8")
bone_stroke = DARK_BLUE
# Acetabulum cup
c.setFillColor(colors.HexColor("#b0bec5"))
c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.circle(cx-10, cy, 36, fill=1, stroke=1)
# Acetabular labrum
c.setFillColor(colors.HexColor("#e0c07080"))
c.setStrokeColor(GOLD); c.setLineWidth(2)
c.circle(cx-10, cy, 40, fill=0, stroke=1)
# Femoral head (ball)
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.8)
c.circle(cx-10, cy, 30, fill=1, stroke=1)
# Fovea
c.setFillColor(colors.HexColor("#90a4ae"))
c.circle(cx-18, cy+5, 4, fill=1, stroke=0)
# Femoral neck
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.polygon([cx+18, cy+10, cx+24, cy-8, cx+56, cy-32, cx+62, cy-10,
cx+30, cy+20], fill=1, stroke=1)
# Femoral shaft
c.rect(cx+48, cy-32, 22, -60, fill=1, stroke=1)
# Greater trochanter
c.ellipse(cx+52, cy-28, cx+74, cy-8, fill=1, stroke=1)
# Ligaments
# Iliofemoral (Y-ligament)
c.setStrokeColor(ACCENT); c.setLineWidth(2.5)
c.line(cx-10, cy-32, cx+18, cy+10)
c.line(cx-10, cy-38, cx+20, cy-2)
# Ligamentum teres
c.setStrokeColor(GREEN); c.setLineWidth(1.8)
c.setDash([3,2])
c.line(cx-18, cy+5, cx-10, cy+20)
c.setDash([])
# Labels
self._label(c, cx-10, cy+2, "Femoral\nHead", 7, DARK_BLUE, True)
self._label(c, cx-10, cy-46, "Acetabulum", 7, DARK_BLUE, True)
self._label(c, cx-42, cy, "Acetabular\nLabrum", 6.5, GOLD)
self._label(c, cx-20, cy+10, "Fovea", 6, colors.HexColor("#546e7a"))
self._label(c, cx+68, cy-38, "Greater\nTrochanter", 6.5, DARK_BLUE)
self._label_right(c, cx-46, cy+10, "Iliofemoral\nLig. (Y-lig.)", 6.5, ACCENT)
self._label_right(c, cx-46, cy-8, "Lig. of\nFemoral Head", 6.5, GREEN)
self._label(c, self.width/2, 8, "Hip Joint - Schematic (Anterior View)", 7.5, DARK_BLUE, True)
class KneeDiagram(DiagramFlowable):
def draw(self):
c = self.canv
cx, cy = self.width/2, self.height/2 + 5
c.setFillColor(LIGHT_GOLD)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
bone_color = colors.HexColor("#d0d8e8")
bone_stroke = DARK_BLUE
cart_color = colors.HexColor("#a8d8b0")
# Femur condyles
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-22, cy+10, 44, 55, fill=1, stroke=1)
# Medial condyle
c.ellipse(cx-30, cy, cx-4, cy+22, fill=1, stroke=1)
# Lateral condyle
c.ellipse(cx+4, cy, cx+30, cy+22, fill=1, stroke=1)
# Tibia plateau
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-28, cy-12, 56, 14, fill=1, stroke=1)
c.rect(cx-22, cy-55, 44, 44, fill=1, stroke=1)
# Cartilage on tibia
c.setFillColor(cart_color); c.setStrokeColor(GREEN); c.setLineWidth(0.8)
c.ellipse(cx-30, cy-8, cx-2, cy+4, fill=1, stroke=1)
c.ellipse(cx+2, cy-8, cx+30, cy+4, fill=1, stroke=1)
# Medial meniscus (C-shape)
c.setFillColor(colors.HexColor("#e0c070")); c.setStrokeColor(GOLD); c.setLineWidth(1.2)
c.arc(cx-28, cy-6, cx-6, cy+6, 30, 300)
# Lateral meniscus (more circular)
c.arc(cx+6, cy-6, cx+26, cy+6, 30, 330)
# Patella
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.ellipse(cx-12, cy+26, cx+12, cy+50, fill=1, stroke=1)
self._label(c, cx, cy+38, "Patella", 6.5, DARK_BLUE, True)
# ACL
c.setStrokeColor(ACCENT); c.setLineWidth(2)
c.line(cx-4, cy-2, cx+12, cy+14)
# PCL
c.setStrokeColor(MED_BLUE); c.setLineWidth(2)
c.line(cx+4, cy-2, cx-12, cy+14)
# MCL
c.setStrokeColor(RED); c.setLineWidth(2)
c.line(cx-30, cy+14, cx-30, cy-8)
# LCL
c.setStrokeColor(GREEN); c.setLineWidth(2)
c.line(cx+30, cy+14, cx+30, cy-8)
# Labels
self._label(c, cx, cy+66, "Femur", 7, DARK_BLUE, True)
self._label(c, cx, cy-48, "Tibia", 7, DARK_BLUE, True)
self._label(c, cx-18, cy+8, "Medial\nCondyle", 6, DARK_BLUE)
self._label(c, cx+18, cy+8, "Lateral\nCondyle", 6, DARK_BLUE)
self._label_left(c, cx-38, cy+8, "MCL", 6.5, RED)
self._label_right(c, cx+38, cy+8, "LCL", 6.5, GREEN)
self._label(c, cx-4, cy+6, "ACL", 6, ACCENT)
self._label(c, cx+6, cy+6, "PCL", 6, MED_BLUE)
self._label(c, cx-14, cy-4, "Med.\nMeniscus", 5.5, GOLD)
self._label(c, cx+14, cy-4, "Lat.\nMeniscus", 5.5, GOLD)
self._label(c, self.width/2, 8, "Knee Joint - Schematic (Anterior View)", 7.5, DARK_BLUE, True)
class AnkleDiagram(DiagramFlowable):
def draw(self):
c = self.canv
cx, cy = self.width/2, self.height/2 + 5
c.setFillColor(LIGHT_RED)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
bone_color = colors.HexColor("#d0d8e8")
bone_stroke = DARK_BLUE
# Tibia
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-20, cy+10, 38, 55, fill=1, stroke=1)
# Medial malleolus
c.ellipse(cx-28, cy-5, cx-12, cy+16, fill=1, stroke=1)
# Fibula
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx+22, cy+10, 14, 55, fill=1, stroke=1)
# Lateral malleolus
c.ellipse(cx+14, cy-10, cx+36, cy+12, fill=1, stroke=1)
# Talus
c.setFillColor(colors.HexColor("#b0bec5")); c.setStrokeColor(bone_stroke); c.setLineWidth(1.5)
c.rect(cx-22, cy-25, 54, 22, fill=1, stroke=1)
# Calcaneus
c.setFillColor(bone_color); c.setStrokeColor(bone_stroke); c.setLineWidth(1.2)
c.ellipse(cx-16, cy-52, cx+32, cy-22, fill=1, stroke=1)
# Deltoid ligament (medial, fan shape)
c.setStrokeColor(RED); c.setLineWidth(1.8)
for angle in [-20, 0, 20, 40]:
rad = math.radians(angle + 270)
c.line(cx-20, cy+2,
cx-20 + 22*math.cos(rad),
cy+2 + 22*math.sin(rad))
# ATFL
c.setStrokeColor(ACCENT); c.setLineWidth(2)
c.line(cx+25, cy+2, cx+8, cy-10)
# CFL
c.setStrokeColor(PURPLE); c.setLineWidth(2)
c.line(cx+25, cy, cx+14, cy-36)
# Labels
self._label(c, cx, cy+60, "Tibia", 7, DARK_BLUE, True)
self._label(c, cx+29, cy+58, "Fibula", 7, DARK_BLUE, True)
self._label(c, cx, cy-14, "Talus", 7, DARK_BLUE, True)
self._label(c, cx+8, cy-40, "Calcaneus", 7, DARK_BLUE, True)
self._label(c, cx-28, cy+12, "Med.\nMalleolus", 6, DARK_BLUE)
self._label(c, cx+28, cy+16, "Lat.\nMalleolus", 6, DARK_BLUE)
self._label_left(c, cx-36, cy-6, "Deltoid\nLig.", 6.5, RED)
self._label_right(c, cx+46, cy-4, "ATFL", 6.5, ACCENT)
self._label_right(c, cx+46, cy-22, "CFL", 6.5, PURPLE)
self._label(c, self.width/2, 8, "Ankle (Talocrural) Joint - Schematic", 7.5, DARK_BLUE, True)
# ─── Helper functions ──────────────────────────────────────────────────────────
def section_header(text, color=MED_BLUE):
return Paragraph(f'<font color="white"><b> {text} </b></font>',
ParagraphStyle('sh', fontName='Helvetica-Bold',
fontSize=10, textColor=WHITE,
backColor=color, leading=16,
spaceBefore=10, spaceAfter=4,
borderPad=5, leftIndent=0))
def sub_header(text):
return Paragraph(f'<b><font color="#1a3a6b">{text}</font></b>', S['section_head'])
def body(text):
return Paragraph(text, S['body'])
def bullet(text, color=DARK):
return Paragraph(f"• {text}", S['bullet'])
def clinical_note(text):
return Paragraph(f"⚠ {text}", S['clinical'])
def space(h=4):
return Spacer(1, h)
def hr(color=GRAY_MED):
return HRFlowable(width="100%", thickness=0.5, color=color, spaceAfter=4, spaceBefore=4)
def make_table(data, col_widths=None, hdr_color=DARK_BLUE, alt_color=LIGHT_BLUE):
"""Creates a styled table. data[0] = headers."""
table_data = []
# Header row
hdr = [Paragraph(str(c), S['tbl_hdr']) for c in data[0]]
table_data.append(hdr)
for i, row in enumerate(data[1:]):
cells = []
for j, cell in enumerate(row):
style = S['tbl_cell_bold'] if j == 0 else S['tbl_cell']
cells.append(Paragraph(str(cell), style))
table_data.append(cells)
avail = PAGE_W - 2*MARGIN
if col_widths is None:
n = len(data[0])
col_widths = [avail/n] * n
t = Table(table_data, colWidths=col_widths, repeatRows=1)
style_cmds = [
('BACKGROUND', (0,0), (-1,0), hdr_color),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, alt_color]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_MED),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('LINEBELOW', (0,0), (-1,0), 1.5, hdr_color),
]
t.setStyle(TableStyle(style_cmds))
return t
def joint_header(num, title, jtype, color):
return JointHeaderBlock(str(num), title, jtype, color, PAGE_W - 2*MARGIN)
# ─── Cover Page ────────────────────────────────────────────────────────────────
def cover_page():
elems = []
class CoverBg(Flowable):
def __init__(self, w, h):
Flowable.__init__(self); self.width=w; self.height=h
def draw(self):
c = self.canv
# Gradient-like background using two rects
c.setFillColor(DARK_BLUE); c.rect(0, 0, self.width, self.height, fill=1, stroke=0)
c.setFillColor(colors.HexColor("#0d2550"))
c.rect(0, 0, self.width, self.height*0.45, fill=1, stroke=0)
# Decorative circles
c.setFillColor(colors.HexColor("#ffffff08"))
c.circle(self.width*0.85, self.height*0.8, 120, fill=1, stroke=0)
c.circle(self.width*0.1, self.height*0.2, 80, fill=1, stroke=0)
c.circle(self.width*0.5, self.height*0.5, 200, fill=0, stroke=1)
c.setStrokeColor(colors.HexColor("#ffffff10")); c.setLineWidth(0.5)
c.circle(self.width*0.5, self.height*0.5, 240, fill=0, stroke=1)
# Gold accent bar
c.setFillColor(GOLD)
c.rect(MARGIN, self.height*0.52, self.width - 2*MARGIN, 3, fill=1, stroke=0)
elems.append(CoverBg(PAGE_W, PAGE_H))
# Cover content as a table positioned over background
cover_content = [
Spacer(1, PAGE_H * 0.22),
Paragraph("BDC ANATOMY NOTES", ParagraphStyle('ct',
fontName='Helvetica-Bold', fontSize=11, textColor=GOLD,
alignment=TA_CENTER, leading=16, letterSpacing=3)),
Spacer(1, 10),
Paragraph("Upper Limb & Lower Limb<br/>Joints", ParagraphStyle('ctitle',
fontName='Helvetica-Bold', fontSize=32, textColor=WHITE,
alignment=TA_CENTER, leading=38)),
Spacer(1, 18),
Paragraph("Comprehensive Study Notes with Diagrams", ParagraphStyle('csub',
fontName='Helvetica', fontSize=13, textColor=colors.HexColor("#cde3f7"),
alignment=TA_CENTER, leading=18)),
Spacer(1, 30),
Paragraph("─────────────────────────────────────────────────", ParagraphStyle('div',
fontName='Helvetica', fontSize=9, textColor=colors.HexColor("#4a7abd"),
alignment=TA_CENTER)),
Spacer(1, 18),
Paragraph(
"Shoulder • Elbow • Wrist • Hand<br/>Hip • Knee • Ankle • Foot",
ParagraphStyle('joints', fontName='Helvetica', fontSize=12,
textColor=colors.HexColor("#b0d0f0"), alignment=TA_CENTER, leading=20)
),
Spacer(1, 30),
# Info badges
Table([[
Paragraph("8 Joints<br/>Covered", ParagraphStyle('badge', fontName='Helvetica-Bold',
fontSize=9, textColor=DARK_BLUE, alignment=TA_CENTER, backColor=GOLD,
borderPad=6, leading=13)),
Paragraph("Full Clinical<br/>Correlations", ParagraphStyle('badge2', fontName='Helvetica-Bold',
fontSize=9, textColor=WHITE, alignment=TA_CENTER, backColor=MED_BLUE,
borderPad=6, leading=13)),
Paragraph("Ligament<br/>Tables", ParagraphStyle('badge3', fontName='Helvetica-Bold',
fontSize=9, textColor=WHITE, alignment=TA_CENTER, backColor=GREEN,
borderPad=6, leading=13)),
Paragraph("Nerve & Blood<br/>Supply", ParagraphStyle('badge4', fontName='Helvetica-Bold',
fontSize=9, textColor=WHITE, alignment=TA_CENTER, backColor=ACCENT,
borderPad=6, leading=13)),
]], colWidths=[80, 88, 80, 88], hAlign='CENTER'),
Spacer(1, 40),
Paragraph("Source: Gray's Anatomy for Students", ParagraphStyle('src',
fontName='Helvetica-Oblique', fontSize=9, textColor=colors.HexColor("#7a9abf"),
alignment=TA_CENTER)),
]
elems.extend(cover_content)
return elems
# ─── Build document ────────────────────────────────────────────────────────────
def build():
path = "/tmp/workspace/joint-notes-pdf/BDC_Joint_Notes.pdf"
doc = SimpleDocTemplate(path, pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN+5)
avail_w = PAGE_W - 2*MARGIN
elems = []
# ── Cover ──
elems += cover_page()
elems.append(PageBreak())
# ══════════════════════════════════════════════════════
# PART 1: UPPER LIMB JOINTS
# ══════════════════════════════════════════════════════
class PartBanner(Flowable):
def __init__(self, title, subtitle, color, w):
Flowable.__init__(self); self.title=title; self.subtitle=subtitle
self.color=color; self.width=w; self.height=52
def draw(self):
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, self.width, self.height, 10, fill=1, stroke=0)
c.setFillColor(colors.HexColor("#ffffff15"))
c.circle(self.width-30, self.height/2, 40, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont('Helvetica-Bold', 16)
c.drawString(14, self.height/2+6, self.title)
c.setFont('Helvetica', 10)
c.setFillColor(colors.HexColor("#ffffffcc"))
c.drawString(14, self.height/2-10, self.subtitle)
elems.append(PartBanner("PART 1: UPPER LIMB JOINTS",
"Shoulder • Elbow • Wrist • Hand", DARK_BLUE, avail_w))
elems.append(space(10))
# ── 1. SHOULDER JOINT ──────────────────────────────────────────────────────
elems.append(joint_header(1, "SHOULDER (GLENOHUMERAL) JOINT", "Synovial Ball & Socket", MED_BLUE))
elems.append(space(6))
# Two-column layout: diagram left, key facts right
diag = ShoulderDiagram(avail_w * 0.48, 160)
facts_data = [
["Feature", "Details"],
["Type", "Synovial multiaxial ball & socket"],
["Bones", "Head of humerus + glenoid cavity"],
["Stability", "Primarily muscular (rotator cuff)"],
["Mobility", "Most mobile joint in the body"],
["Capsule", "Loose; allows wide ROM"],
]
facts_tbl = make_table(facts_data, col_widths=[avail_w*0.48*0.38, avail_w*0.48*0.62])
combo = Table([[diag, facts_tbl]], colWidths=[avail_w*0.48, avail_w*0.52])
combo.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),4)]))
elems.append(combo)
elems.append(space(4))
elems.append(section_header("ROTATOR CUFF (SITS)", GREEN))
rotator_data = [
["Muscle", "Origin", "Insertion", "Action"],
["Supraspinatus", "Supraspinous fossa", "Greater tubercle (sup.)", "Abduction (initiates 0-15°)"],
["Infraspinatus", "Infraspinous fossa", "Greater tubercle (mid.)", "Lateral rotation"],
["Teres minor", "Lateral border scapula", "Greater tubercle (inf.)", "Lateral rotation"],
["Subscapularis", "Subscapular fossa", "Lesser tubercle", "Medial rotation"],
]
elems.append(make_table(rotator_data, col_widths=[avail_w*0.2, avail_w*0.27, avail_w*0.27, avail_w*0.26],
hdr_color=GREEN))
elems.append(space(4))
elems.append(section_header("LIGAMENTS", MED_BLUE))
lig_data = [
["Ligament", "Attachment", "Function"],
["Glenohumeral (sup/mid/inf)", "Glenoid rim → lesser tubercle/neck humerus", "Anterior capsule reinforcement"],
["Coracohumeral", "Coracoid process → greater tubercle", "Limits inferior dislocation & ext rotation"],
["Transverse humeral", "Bridges bicipital groove", "Retains biceps tendon (long head)"],
["Coracoacromial", "Coracoid → acromion (NOT part of joint)", "Arch; prevents superior dislocation"],
]
elems.append(make_table(lig_data, col_widths=[avail_w*0.30, avail_w*0.38, avail_w*0.32]))
elems.append(space(4))
elems.append(section_header("MOVEMENTS & NERVE/BLOOD SUPPLY", MED_BLUE))
elems.append(body("<b>Movements:</b> Flexion, Extension, Abduction, Adduction, Medial rotation, Lateral rotation, Circumduction"))
elems.append(body("<b>Nerve supply:</b> Axillary nerve (C5,C6) | Suprascapular nerve (C5,C6) | Lateral pectoral nerve"))
elems.append(body("<b>Blood supply:</b> Anterior & posterior circumflex humeral arteries (from axillary artery)"))
elems.append(space(4))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Anterior dislocation</b> (95%): arm abducted + externally rotated. Risks: axillary nerve injury → deltoid paralysis & loss of sensation regimental badge area"))
elems.append(clinical_note("<b>Bankart lesion:</b> avulsion of anteroinferior glenoid labrum in recurrent dislocations"))
elems.append(clinical_note("<b>Hill-Sachs lesion:</b> impaction fracture posterosuperior humeral head (from repeated dislocations)"))
elems.append(clinical_note("<b>Rotator cuff tear:</b> supraspinatus most commonly torn; tested by painful arc (60-120°) and empty can test"))
elems.append(clinical_note("<b>Frozen shoulder (adhesive capsulitis):</b> painful progressive stiffness; capsule contracted"))
elems.append(PageBreak())
# ── 2. ELBOW JOINT ──────────────────────────────────────────────────────────
elems.append(joint_header(2, "ELBOW JOINT", "Compound Synovial (Hinge + Pivot)", colors.HexColor("#1a7a4a")))
elems.append(space(6))
diag2 = ElbowDiagram(avail_w * 0.46, 170)
artic_data = [
["Articulation", "Bones", "Type", "Movement"],
["Humeroulnar", "Trochlea + Trochlear notch of ulna", "Hinge", "Flexion / Extension"],
["Humeroradial", "Capitulum + Head of radius", "Hinge + Pivot", "Flexion / Extension"],
["Proximal radioulnar", "Head of radius + Radial notch of ulna", "Pivot", "Pronation / Supination"],
]
artic_tbl = make_table(artic_data, col_widths=[avail_w*0.52*0.22, avail_w*0.52*0.38,
avail_w*0.52*0.18, avail_w*0.52*0.22])
combo2 = Table([[diag2, artic_tbl]], colWidths=[avail_w*0.46, avail_w*0.54])
combo2.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),4)]))
elems.append(combo2)
elems.append(space(4))
elems.append(section_header("LIGAMENTS", colors.HexColor("#1a7a4a")))
elib_data = [
["Ligament", "Origin", "Insertion", "Resists"],
["Medial (ulnar) collateral", "Medial epicondyle of humerus", "Coronoid process + olecranon (ulna)", "Valgus stress"],
["Lateral (radial) collateral", "Lateral epicondyle of humerus", "Annular ligament + ulna", "Varus stress"],
["Annular ligament of radius", "Anterior radial notch of ulna", "Posterior radial notch of ulna", "Radial head dislocation"],
]
elems.append(make_table(elib_data, col_widths=[avail_w*0.28, avail_w*0.26, avail_w*0.26, avail_w*0.20],
hdr_color=colors.HexColor("#1a7a4a"), alt_color=LIGHT_GREEN))
elems.append(space(4))
elems.append(section_header("NERVE & BLOOD SUPPLY", colors.HexColor("#1a7a4a")))
elems.append(body("<b>Nerve supply:</b> Radial nerve (main) | Musculocutaneous | Median | Ulnar"))
elems.append(body("<b>Blood supply:</b> Anastomotic network - brachial, profunda brachii, radial, ulnar arteries"))
elems.append(space(4))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Supracondylar fracture</b> (children): distal fragment displaced posteriorly by triceps → brachial artery kinking → Volkmann's ischaemic contracture (anterior compartment)"))
elems.append(clinical_note("<b>Fat pad sign</b>: elevated posterior fat pad on X-ray = joint effusion = occult fracture (especially radial head in adults)"))
elems.append(clinical_note("<b>Nursemaid's elbow</b> (pulled elbow, <5 years): radial head subluxation under annular ligament; treated by supination + flexion"))
elems.append(clinical_note("<b>Olecranon fracture</b>: avulsion by triceps or direct blow; gap in extensor mechanism"))
elems.append(clinical_note("<b>Carrying angle</b>: ~170° valgus (females > males); increased = cubitus valgus → tardy ulnar nerve palsy"))
elems.append(clinical_note("<b>Medial epicondyle fracture</b>: commonest fracture around elbow in children; ulnar nerve at risk"))
elems.append(PageBreak())
# ── 3. WRIST JOINT ─────────────────────────────────────────────────────────
elems.append(joint_header(3, "WRIST (RADIOCARPAL) JOINT", "Synovial Condyloid (Ellipsoid)", colors.HexColor("#5b2d8e")))
elems.append(space(6))
wrist_overview = [
["Feature", "Details"],
["Type", "Synovial condyloid (ellipsoid)"],
["Proximal surface", "Distal radius + articular disc (TFCC)"],
["Distal surface", "Scaphoid + Lunate + Triquetrum (proximal row)"],
["Note on ulna", "Does NOT directly articulate; separated by TFCC"],
["Movements", "Flex, Ext, Radial deviation, Ulnar deviation, Circumduction"],
]
elems.append(make_table(wrist_overview, col_widths=[avail_w*0.35, avail_w*0.65],
hdr_color=PURPLE, alt_color=LIGHT_PURPLE))
elems.append(space(5))
elems.append(section_header("LIGAMENTS OF WRIST", PURPLE))
wrist_lig = [
["Ligament", "Sides", "Details"],
["Radial collateral lig.", "Lateral (radial)", "Radial styloid → scaphoid & trapezium"],
["Ulnar collateral lig.", "Medial (ulnar)", "Ulnar styloid → triquetrum & pisiform"],
["Palmar radiocarpal lig.", "Anterior", "Radius → carpals; very strong; prevents hyperextension"],
["Dorsal radiocarpal lig.", "Posterior", "Radius → carpals; limits flexion"],
["TFCC (triangular fibrocartilage complex)", "Ulnar side", "Articular disc + ligaments; key stabiliser of distal radioulnar + wrist"],
]
elems.append(make_table(wrist_lig, col_widths=[avail_w*0.30, avail_w*0.18, avail_w*0.52],
hdr_color=PURPLE, alt_color=LIGHT_PURPLE))
elems.append(space(5))
elems.append(section_header("CARPAL BONES (Mnemonic)", PURPLE))
elems.append(body('<b>Proximal row:</b> Scaphoid — Lunate — Triquetrum — Pisiform'))
elems.append(body('<b>Distal row:</b> Trapezium — Trapezoid — Capitate — Hamate'))
elems.append(body('<b>Mnemonic:</b> <i>"Some Lovers Try Positions That They Cannot Handle"</i>'))
elems.append(space(5))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Colles' fracture</b>: distal radius, dorsal displacement → 'dinner fork deformity'. FOOSH in elderly women (osteoporosis). Radial nerve at risk."))
elems.append(clinical_note("<b>Smith's fracture</b>: reverse Colles' — volar displacement. Direct blow to dorsal wrist."))
elems.append(clinical_note("<b>Scaphoid fracture</b>: most commonly fractured carpal bone (FOOSH). Risk of AVN (blood supply enters distally). Anatomical snuffbox tenderness. May be occult on initial X-ray."))
elems.append(clinical_note("<b>TFCC injury</b>: ulnar-sided wrist pain, especially with rotation."))
elems.append(clinical_note("<b>Carpal tunnel syndrome</b>: compression of median nerve under flexor retinaculum → thenar wasting + 'ape hand'."))
elems.append(PageBreak())
# ── 4. HAND JOINTS ─────────────────────────────────────────────────────────
elems.append(joint_header(4, "JOINTS OF THE HAND", "CMC • MCP • IP Joints", colors.HexColor("#c89a2a")))
elems.append(space(6))
hand_data = [
["Joint", "Type", "Movements", "Key Features"],
["Thumb CMC (1st carpometacarpal)", "Saddle (sellar)", "Flex, Ext, Abd, Add, Opposition", "Most mobile CMC; thumb opposition unique to primates"],
["Fingers CMC (2nd-5th)", "Plane (gliding)", "Limited gliding", "2nd & 3rd almost immobile; 4th & 5th slight mobility"],
["MCP joints (knuckles)", "Condyloid", "Flex, Ext, Abd, Add", "Collateral ligs taut in flexion; palmar plates prevent hyperext"],
["PIP joints", "Hinge", "Flexion & Extension only", "Most important for grip; commonly injured"],
["DIP joints", "Hinge", "Flexion & Extension only", "Mallet finger = extensor tendon injury here"],
]
elems.append(make_table(hand_data, col_widths=[avail_w*0.28, avail_w*0.16, avail_w*0.24, avail_w*0.32],
hdr_color=GOLD, alt_color=LIGHT_GOLD))
elems.append(space(5))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Mallet finger:</b> rupture of extensor tendon at DIP → fixed flexion deformity of distal phalanx"))
elems.append(clinical_note("<b>Boutonniere deformity:</b> central slip of extensor tendon at PIP ruptures → PIP flexion + DIP hyperextension"))
elems.append(clinical_note("<b>Swan neck deformity:</b> PIP hyperextension + DIP flexion (rheumatoid arthritis)"))
elems.append(clinical_note("<b>Gamekeeper's thumb (skier's thumb):</b> UCL injury of thumb MCP; valgus stress"))
elems.append(clinical_note("<b>Trigger finger:</b> stenosing tenosynovitis of flexor tendon sheath; finger locks in flexion"))
elems.append(PageBreak())
# ══════════════════════════════════════════════════════
# PART 2: LOWER LIMB JOINTS
# ══════════════════════════════════════════════════════
elems.append(PartBanner("PART 2: LOWER LIMB JOINTS",
"Hip • Knee • Ankle • Foot", ACCENT, avail_w))
elems.append(space(10))
# ── 5. HIP JOINT ──────────────────────────────────────────────────────────
elems.append(joint_header(5, "HIP JOINT", "Synovial Ball & Socket — Stability > Mobility", ACCENT))
elems.append(space(6))
diag5 = HipDiagram(avail_w * 0.48, 175)
hip_basic = [
["Feature", "Details"],
["Type", "Synovial multiaxial ball & socket"],
["Bones", "Head of femur + acetabulum"],
["Design emphasis", "Stability & weight-bearing"],
["Socket depth", "Deepened by acetabular labrum"],
["Labrum function", "Increases articular surface, stabilises"],
["Acetabular fossa", "Non-articular; contains fat & lig. teres"],
]
hip_tbl = make_table(hip_basic, col_widths=[avail_w*0.52*0.40, avail_w*0.52*0.60],
hdr_color=ACCENT, alt_color=colors.HexColor("#fdecea"))
combo5 = Table([[diag5, hip_tbl]], colWidths=[avail_w*0.48, avail_w*0.52])
combo5.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),4)]))
elems.append(combo5)
elems.append(space(4))
elems.append(section_header("LIGAMENTS", ACCENT))
hip_lig = [
["Ligament", "Origin", "Insertion", "Function / Resists"],
["Iliofemoral (Y-lig. of Bigelow)", "Ant. inferior iliac spine", "Intertrochanteric line", "Resists hyperextension; STRONGEST lig. in body"],
["Pubofemoral", "Pubic part of acetabular rim", "Intertrochanteric line (ant.)", "Limits abduction & extension"],
["Ischiofemoral", "Posterior acetabular rim", "Greater trochanter (base)", "Limits medial rotation; winds capsule in extension"],
["Ligamentum teres (intracapsular)", "Acetabular fossa / transverse lig.", "Fovea of femoral head", "Minor stabiliser; carries small obturator artery branch"],
]
elems.append(make_table(hip_lig, col_widths=[avail_w*0.27, avail_w*0.22, avail_w*0.22, avail_w*0.29],
hdr_color=ACCENT, alt_color=LIGHT_RED))
elems.append(space(4))
elems.append(section_header("BLOOD SUPPLY TO FEMORAL HEAD (CRITICAL)", RED))
elems.append(body("<b>1. Medial circumflex femoral artery (MCFA)</b> — main supply; enters posteriorly via retinacular vessels"))
elems.append(body("<b>2. Lateral circumflex femoral artery (LCFA)</b> — minor contribution"))
elems.append(body("<b>3. Obturator artery</b> via ligamentum teres — minimal contribution (only in children)"))
elems.append(body("<b>Trochanteric anastomosis</b>: ring of vessels around femoral neck from MCFA + LCFA branches"))
elems.append(space(4))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Neck of femur fracture</b>: disrupts MCFA → avascular necrosis (AVN) of femoral head. Elderly females with osteoporosis. Intracapsular (Garden I-IV) vs extracapsular."))
elems.append(clinical_note("<b>Posterior dislocation</b> (most common, 90%): high-energy trauma (dashboard injury). Limb: flexed, adducted, internally rotated. Risk of sciatic nerve injury."))
elems.append(clinical_note("<b>Perthes disease</b>: idiopathic AVN femoral head in children 4-8 years (boys > girls). Self-limiting but may cause deformity."))
elems.append(clinical_note("<b>DDH (developmental dysplasia)</b>: shallow acetabulum; Ortolani & Barlow tests; treated with Pavlik harness."))
elems.append(clinical_note("<b>Trendelenburg sign (+)</b>: pelvis drops on unsupported side when standing on one leg = gluteus medius weakness (superior gluteal nerve injury / hip OA / coxa vara)."))
elems.append(clinical_note("<b>Thomas test</b>: fixed flexion deformity of hip; <b>FABER/FADIR</b> tests for hip pathology."))
elems.append(PageBreak())
# ── 6. KNEE JOINT ──────────────────────────────────────────────────────────
elems.append(joint_header(6, "KNEE JOINT", "Largest Synovial Joint — Modified Hinge", colors.HexColor("#1a3a6b")))
elems.append(space(6))
diag6 = KneeDiagram(avail_w * 0.46, 175)
knee_basic = [
["Feature", "Details"],
["Type", "Synovial modified hinge (condylar)"],
["Articulatons", "Tibiofemoral + Patellofemoral"],
["Size", "Largest joint in the body"],
["Patella role", "Sesamoid; increases mechanical advantage of quad"],
["Locking mech.", "Lateral tibial rotation (screw-home) in full ext."],
["Unlocking", "Popliteus muscle (medially rotates tibia)"],
]
knee_tbl = make_table(knee_basic, col_widths=[avail_w*0.54*0.36, avail_w*0.54*0.64],
hdr_color=DARK_BLUE, alt_color=LIGHT_BLUE)
combo6 = Table([[diag6, knee_tbl]], colWidths=[avail_w*0.46, avail_w*0.54])
combo6.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),4)]))
elems.append(combo6)
elems.append(space(4))
elems.append(section_header("MENISCI", DARK_BLUE))
menisci_data = [
["Feature", "Medial Meniscus", "Lateral Meniscus"],
["Shape", "C-shaped (larger, more open)", "O-shaped (smaller, more circular)"],
["Attachment to capsule", "Firmly attached to tibial collateral ligament & capsule", "NOT attached to fibular collateral ligament; loose"],
["Mobility", "Less mobile → more commonly torn", "More mobile → less commonly torn"],
["Transverse lig.", "Connected anteriorly by transverse ligament of knee", ""],
["Extra connection", "Coronary ligament to tibia", "Posterior horn: ligament of Wrisberg to medial fem. condyle"],
]
elems.append(make_table(menisci_data, col_widths=[avail_w*0.22, avail_w*0.39, avail_w*0.39],
hdr_color=DARK_BLUE, alt_color=LIGHT_BLUE))
elems.append(space(4))
elems.append(section_header("LIGAMENTS", DARK_BLUE))
knee_lig = [
["Ligament", "Attachment", "Taut When", "Resists / Function"],
["Tibial (MCL) collateral", "Med. fem. epicondyle → medial tibia", "Flexion & Extension", "Valgus stress; attached to medial meniscus"],
["Fibular (LCL) collateral", "Lat. fem. epicondyle → head of fibula", "Extension", "Varus stress; NOT attached to lateral meniscus"],
["ACL (ant. cruciate)", "Ant. tibial intercondylar area → post. lat. fem. condyle", "Extension", "Prevents ant. tibial displacement; prevents hyperextension"],
["PCL (post. cruciate)", "Post. tibial intercondylar → ant. med. fem. condyle", "Flexion", "Prevents post. tibial displacement; stronger than ACL"],
["Patellar ligament", "Apex of patella → tibial tuberosity", "—", "Continuation of quad tendon; transmits extensor force"],
]
elems.append(make_table(knee_lig, col_widths=[avail_w*0.22, avail_w*0.26, avail_w*0.18, avail_w*0.34],
hdr_color=DARK_BLUE, alt_color=LIGHT_BLUE))
elems.append(space(4))
elems.append(section_header("BURSAE", DARK_BLUE))
bursae_data = [
["Bursa", "Location", "Clinical Significance"],
["Prepatellar bursa", "Between patella and skin", "Housemaid's knee (kneeling); bursitis"],
["Superficial infrapatellar", "Between tibial tuberosity and skin", "Clergyman's knee (on all fours)"],
["Deep infrapatellar", "Between patellar ligament and tibia", "Clergyman's knee"],
["Semimembranosus bursa", "Posterior, between semimembranosus & med. head gastrocnemius", "Baker's cyst (communicates with joint in adults)"],
["Suprapatellar pouch", "Communicates with knee joint cavity", "Effusion visible / palpable above patella"],
]
elems.append(make_table(bursae_data, col_widths=[avail_w*0.28, avail_w*0.32, avail_w*0.40],
hdr_color=MED_BLUE, alt_color=LIGHT_BLUE))
elems.append(space(4))
elems.append(section_header("CLINICAL TESTS & CORRELATIONS", RED))
tests_data = [
["Test / Condition", "Technique", "Positive Result / Significance"],
["Lachman's test (ACL)", "Tibia pulled forward at 15-30° flexion", "Anterior tibial displacement = ACL tear (most sensitive)"],
["Anterior drawer test (ACL)", "Tibia pulled forward at 90° flexion", "Anterior displacement = ACL tear"],
["Posterior drawer test (PCL)", "Tibia pushed back at 90° flexion", "Posterior displacement = PCL tear"],
["McMurray's test", "Knee flex/ext with valgus + rotation", "Click = meniscal tear"],
["Valgus stress test (MCL)", "Valgus force at 30° flexion", "Pain/laxity = MCL tear"],
["Unhappy triad (O'Donoghue)", "Valgus force on flexed knee", "ACL + MCL + Medial meniscus torn"],
]
elems.append(make_table(tests_data, col_widths=[avail_w*0.27, avail_w*0.32, avail_w*0.41],
hdr_color=RED, alt_color=LIGHT_RED))
elems.append(PageBreak())
# ── 7. ANKLE JOINT ─────────────────────────────────────────────────────────
elems.append(joint_header(7, "ANKLE (TALOCRURAL) JOINT", "Synovial Hinge — Mortise & Tenon", PURPLE))
elems.append(space(6))
diag7 = AnkleDiagram(avail_w * 0.46, 165)
ankle_basic = [
["Feature", "Details"],
["Type", "Synovial hinge joint"],
["Bones", "Distal tibia + fibula (mortise) + talus"],
["Mortise", "Medial malleolus + lateral malleolus + tibial plafond"],
["Movements", "Dorsiflexion (tibialis ant.) & Plantarflexion (gastrocnemius/soleus)"],
["Most stable position", "Dorsiflexion (wide ant. talus fills mortise tightly)"],
["Most vulnerable position", "Plantarflexion (narrow post. talus = less stable)"],
]
ankle_tbl = make_table(ankle_basic, col_widths=[avail_w*0.54*0.36, avail_w*0.54*0.64],
hdr_color=PURPLE, alt_color=LIGHT_PURPLE)
combo7 = Table([[diag7, ankle_tbl]], colWidths=[avail_w*0.46, avail_w*0.54])
combo7.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),4)]))
elems.append(combo7)
elems.append(space(4))
elems.append(section_header("LIGAMENTS", PURPLE))
ankle_lig = [
["Ligament", "Side", "Parts", "Function"],
["Deltoid (medial) ligament", "Medial", "Tibionavicular, Ant. tibiotalar, Post. tibiotalar, Tibiocalcaneal", "Very strong; resists eversion; rarely torn alone"],
["ATFL (ant. talofibular lig.)", "Lateral", "Single band", "Resists plantarflexion + inversion; MOST COMMONLY TORN"],
["CFL (calcaneofibular lig.)", "Lateral", "Single band", "Resists inversion with foot neutral"],
["PTFL (post. talofibular lig.)", "Lateral", "Strongest lateral lig.", "Resists extreme dorsiflexion & internal rotation; rarely torn"],
]
elems.append(make_table(ankle_lig, col_widths=[avail_w*0.26, avail_w*0.10, avail_w*0.36, avail_w*0.28],
hdr_color=PURPLE, alt_color=LIGHT_PURPLE))
elems.append(space(4))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Lateral ankle sprain</b>: most common ankle injury; ATFL torn first (plantarflexion + inversion). Ottawa ankle rules guide X-ray decision."))
elems.append(clinical_note("<b>Pott's fracture</b>: bimalleolar (both malleoli) or trimalleolar (+ posterior tibial margin) fracture-dislocation"))
elems.append(clinical_note("<b>High ankle sprain</b>: syndesmosis injury (anterior tibiofibular ligament); often missed; mechanism = external rotation"))
elems.append(clinical_note("<b>Achilles tendon rupture</b>: Simmond's/Thompson test positive (squeeze calf → no plantarflexion)"))
elems.append(clinical_note("<b>Talus fractures</b>: risk of AVN (blood supply from three vessels entering at neck/sinus tarsi)"))
elems.append(PageBreak())
# ── 8. FOOT JOINTS ─────────────────────────────────────────────────────────
elems.append(joint_header(8, "SUBTALAR & FOOT JOINTS", "Inversion • Eversion • Arches", colors.HexColor("#1a7a4a")))
elems.append(space(6))
foot_data = [
["Joint", "Type", "Bones", "Main Movements", "Key Feature"],
["Subtalar (talocalcaneal)", "Plane synovial", "Talus + calcaneus", "Inversion & eversion", "3 facets; supported by interosseous talocalcaneal ligament"],
["Talonavicular", "Ball & socket (part of midtarsal)", "Talus + navicular", "Inversion, eversion, adduction", "With calcaneocuboid = transverse tarsal joint"],
["Calcaneocuboid", "Saddle (part of midtarsal)", "Calcaneus + cuboid", "Limited gliding", "Key component of lateral column of foot"],
["Tarsometatarsal (Lisfranc)", "Plane synovial", "Tarsals + metatarsal bases", "Minimal gliding", "Lisfranc ligament (2nd TMT); injury commonly missed"],
["MTP joints", "Condyloid synovial", "Metatarsals + proximal phalanges", "Flex, ext, abd, add", "1st MTP = hallux valgus (bunion) site"],
["IP joints (toe)", "Hinge synovial", "Phalanges", "Flexion, extension", "Hammer toe = PIP flexion deformity"],
]
elems.append(make_table(foot_data, col_widths=[avail_w*0.20, avail_w*0.16, avail_w*0.18, avail_w*0.16, avail_w*0.30],
hdr_color=colors.HexColor("#1a7a4a"), alt_color=LIGHT_GREEN))
elems.append(space(6))
elems.append(section_header("ARCHES OF THE FOOT", colors.HexColor("#1a7a4a")))
arches = [
["Arch", "Bones", "Keystone", "Main Support"],
["Medial longitudinal (higher)", "Calcaneus - talus - navicular - cuneiforms - 1st-3rd MT", "Talus", "Spring (plantar calcaneonavicular) ligament + tibialis posterior + FHL"],
["Lateral longitudinal (lower)", "Calcaneus - cuboid - 4th-5th MT", "Cuboid", "Long & short plantar ligaments + peroneus longus"],
["Transverse arch", "Cuboid + 3 cuneiforms + bases of MT", "2nd cuneiform", "Peroneus longus (crosses obliquely)"],
]
elems.append(make_table(arches, col_widths=[avail_w*0.24, avail_w*0.36, avail_w*0.12, avail_w*0.28],
hdr_color=colors.HexColor("#1a7a4a"), alt_color=LIGHT_GREEN))
elems.append(space(6))
elems.append(section_header("CLINICAL CORRELATIONS", RED))
elems.append(clinical_note("<b>Pes planus (flat foot)</b>: loss of medial longitudinal arch; spring ligament laxity; posterior tibial tendon dysfunction"))
elems.append(clinical_note("<b>Pes cavus (high arch)</b>: exaggerated medial arch; associated with Charcot-Marie-Tooth disease & Friedreich's ataxia"))
elems.append(clinical_note("<b>Hallux valgus (bunion)</b>: lateral deviation of great toe at 1st MTP; female predominance; ill-fitting shoes"))
elems.append(clinical_note("<b>Lisfranc injury</b>: disruption of tarsometatarsal joints; mechanism = axial load on plantarflexed foot; commonly missed on X-ray"))
elems.append(clinical_note("<b>Plantar fasciitis</b>: inflammation at calcaneal attachment of plantar fascia; heel pain worst in morning"))
elems.append(clinical_note("<b>Morton's neuroma</b>: perineural fibrosis of common digital nerve (3rd-4th interspace most common)"))
elems.append(PageBreak())
# ══════════════════════════════════════════════════════
# SUMMARY COMPARISON TABLE
# ══════════════════════════════════════════════════════
elems.append(PartBanner("MASTER SUMMARY TABLES", "Quick Reference for Revision", MED_BLUE, avail_w))
elems.append(space(10))
elems.append(sub_header("All Joints at a Glance"))
summary = [
["Joint", "Type", "Key Movements", "Key Nerve", "Common Problem"],
["Shoulder", "Ball & socket", "All (most mobile)", "Axillary (C5,C6)", "Anterior dislocation"],
["Elbow", "Compound hinge+pivot", "Flex/Ext + Pro/Sup", "Radial nerve", "Supracondylar # (child)"],
["Wrist", "Condyloid", "Flex/Ext + Deviation", "Ant. interosseous", "Colles' fracture"],
["Hand CMC/MCP/IP", "Various", "Flex/Ext, Opposition", "Median/Ulnar", "Mallet, Boutonniere"],
["Hip", "Ball & socket", "All (most stable)", "Femoral/Obturator/Sciatic", "NOF # → AVN"],
["Knee", "Modified hinge", "Flex/Ext + Rotation", "Femoral/Sciatic", "ACL + Meniscal tear"],
["Ankle", "Hinge", "Dorsiflex/Plantarflex", "Tibial + Deep peroneal", "Lateral sprain (ATFL)"],
["Foot", "Multiple types", "Inversion/Eversion", "Tibial + Peroneal", "Hallux valgus, Flat foot"],
]
elems.append(make_table(summary,
col_widths=[avail_w*0.14, avail_w*0.18, avail_w*0.20, avail_w*0.22, avail_w*0.26]))
elems.append(space(10))
elems.append(sub_header("Nerve Supply Summary"))
nerves = [
["Joint", "Nerve(s)", "Spinal Level", "Hilton's Law Note"],
["Shoulder", "Axillary, suprascapular, lat. pectoral", "C5, C6", "Muscles acting on + nerve crossing over joint"],
["Elbow", "Radial (main), musculocutaneous, median, ulnar", "C5-T1", "All main arm nerves contribute"],
["Wrist/Hand", "Radial, median, ulnar (ant. & post. interosseous)", "C6-T1", "Median = thumb/index/middle; Ulnar = ring/little"],
["Hip", "Femoral, obturator, sciatic, sup. gluteal", "L2-S1", "Knee pain may be referred from hip (obturator)"],
["Knee", "Femoral, obturator, common peroneal, tibial", "L2-S2", "Obturator → explains hip OA causing knee pain"],
["Ankle/Foot", "Deep peroneal (dorsum), tibial (sole), sural", "L4-S2", "Tibial nerve = intrinsic muscles of sole"],
]
elems.append(make_table(nerves,
col_widths=[avail_w*0.12, avail_w*0.30, avail_w*0.14, avail_w*0.44],
hdr_color=GREEN, alt_color=LIGHT_GREEN))
elems.append(space(10))
elems.append(sub_header("Blood Supply Summary"))
blood = [
["Joint", "Main Artery", "Source", "Clinical Note"],
["Shoulder", "Ant. & Post. circumflex humeral", "Axillary artery", "Surgical neck # → anterior circumflex at risk"],
["Elbow", "Anastomotic network", "Brachial + Radial + Ulnar", "Network = collateral circulation preserved in distal occlusion"],
["Wrist", "Dorsal + palmar carpal arches", "Radial + Ulnar", "Scaphoid fracture → AVN (distal to proximal supply)"],
["Hip", "Medial circumflex femoral (main)", "Profunda femoris", "NOF # disrupts MCFA → AVN femoral head"],
["Knee", "Genicular branches", "Femoral + Popliteal + Ant. tibial", "Rich anastomosis above and below joint"],
["Ankle/Foot", "Ant. tibial, post. tibial, peroneal", "Popliteal artery", "Talus AVN after fracture (poor supply at neck)"],
]
elems.append(make_table(blood,
col_widths=[avail_w*0.12, avail_w*0.25, avail_w*0.18, avail_w*0.45],
hdr_color=ACCENT, alt_color=LIGHT_RED))
elems.append(space(10))
# ── Footer note
elems.append(hr())
elems.append(Paragraph(
"BDC Anatomy Joint Notes | Source: Gray's Anatomy for Students | For educational purposes only",
S['footer']))
# ── Build PDF
doc.build(elems)
print(f"PDF created: {path}")
return path
if __name__ == "__main__":
build()
cd /tmp/workspace/joint-notes-pdf && python generate_pdf.py 2>&1
~/joint-notes-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/joint-notes-pdf/generate_pdf.py'
cd /tmp/workspace/joint-notes-pdf && python generate_pdf.py 2>&1
~/joint-notes-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/joint-notes-pdf/generate_pdf.py'
cd /tmp/workspace/joint-notes-pdf && python generate_pdf.py 2>&1
grep -n "c\.polygon\|c\.poly" /tmp/workspace/joint-notes-pdf/generate_pdf.py
~/joint-notes-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/joint-notes-pdf/generate_pdf.py'
~/joint-notes-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/joint-notes-pdf/generate_pdf.py'
~/joint-notes-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/joint-notes-pdf/generate_pdf.py'
cd /tmp/workspace/joint-notes-pdf && python generate_pdf.py 2>&1
ls -lh /tmp/workspace/joint-notes-pdf/BDC_Joint_Notes.pdf
BDC Joint Notes
PDF Document · PDF