Generate a pdf for the dental histology paper of 1st year bds for maxillary cental incisor showing all the labial,lingual,mesial,distal and insical aspect with diagrams
mkdir -p /home/daytona/workspace/maxillary-incisor-pdf && ls /home/daytona/works…
~/maxillary-incisor-pdf/generate_pdf.py
#!/usr/bin/env python3
"""
Dental Histology - Maxillary Central Incisor
1st Year BDS Study Paper
Generates a comprehensive PDF with all 5 aspects + diagrams
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm, mm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.graphics.shapes import (
Drawing, Path, Circle, Ellipse, Line, Rect, String, Polygon, Group
)
from reportlab.graphics import renderPDF
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.graphics.shapes import ArcPath
import math
# ─── Colours ──────────────────────────────────────────────────────────────────
IVORY = colors.HexColor('#FFFDE7')
ENAMEL_CLR = colors.HexColor('#E8F4FD')
DENTIN_CLR = colors.HexColor('#FFF3CD')
PULP_CLR = colors.HexColor('#FFB3BA')
CEMENTUM_CLR= colors.HexColor('#C8E6C9')
OUTLINE_CLR = colors.HexColor('#1A237E')
ANNOT_CLR = colors.HexColor('#D32F2F')
LABEL_CLR = colors.HexColor('#0D47A1')
BG_CLR = colors.HexColor('#F0F4FF')
TITLE_CLR = colors.HexColor('#1A237E')
SECTION_CLR = colors.HexColor('#283593')
SUBHEAD_CLR = colors.HexColor('#3949AB')
# ─── Page Setup ───────────────────────────────────────────────────────────────
PAGE_W, PAGE_H = A4
MARGIN = 2 * cm
doc = SimpleDocTemplate(
'/home/daytona/workspace/maxillary-incisor-pdf/Maxillary_Central_Incisor_BDS1.pdf',
pagesize=A4,
rightMargin=MARGIN, leftMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title='Dental Histology – Maxillary Central Incisor',
author='1st Year BDS – Dental Anatomy & Histology',
)
styles = getSampleStyleSheet()
title_style = ParagraphStyle('Title', parent=styles['Title'],
fontSize=22, textColor=TITLE_CLR, spaceAfter=4,
fontName='Helvetica-Bold', alignment=TA_CENTER)
subtitle_style = ParagraphStyle('Subtitle', parent=styles['Normal'],
fontSize=13, textColor=SECTION_CLR, spaceAfter=2,
fontName='Helvetica-Bold', alignment=TA_CENTER)
section_style = ParagraphStyle('Section', parent=styles['Heading1'],
fontSize=15, textColor=TITLE_CLR, spaceBefore=10, spaceAfter=4,
fontName='Helvetica-Bold',
borderPad=4, borderWidth=0,
backColor=colors.HexColor('#E8EAF6'),
leftIndent=-5, rightIndent=-5,
borderRadius=4)
subsection_style = ParagraphStyle('Subsection', parent=styles['Heading2'],
fontSize=12, textColor=SUBHEAD_CLR, spaceBefore=6, spaceAfter=3,
fontName='Helvetica-Bold')
body_style = ParagraphStyle('Body', parent=styles['Normal'],
fontSize=10, leading=15, spaceAfter=4,
fontName='Helvetica', alignment=TA_JUSTIFY,
leftIndent=5)
bullet_style = ParagraphStyle('Bullet', parent=styles['Normal'],
fontSize=10, leading=14, spaceAfter=2,
fontName='Helvetica', leftIndent=15, bulletIndent=5)
caption_style = ParagraphStyle('Caption', parent=styles['Normal'],
fontSize=9, textColor=SECTION_CLR, spaceAfter=6,
fontName='Helvetica-BoldOblique', alignment=TA_CENTER)
table_header_style = ParagraphStyle('TableHeader', parent=styles['Normal'],
fontSize=9, textColor=colors.white, fontName='Helvetica-Bold', alignment=TA_CENTER)
table_cell_style = ParagraphStyle('TableCell', parent=styles['Normal'],
fontSize=9, fontName='Helvetica', leading=12)
note_style = ParagraphStyle('Note', parent=styles['Normal'],
fontSize=9, textColor=colors.HexColor('#4A148C'),
fontName='Helvetica-Oblique', leftIndent=10,
backColor=colors.HexColor('#F3E5F5'),
borderPad=4, spaceAfter=6)
# ═══════════════════════════════════════════════════════════════════════════════
# DIAGRAM HELPERS
# ═══════════════════════════════════════════════════════════════════════════════
def add_label(d, x, y, text, anchor='middle', color=LABEL_CLR, size=7.5, bold=False):
font = 'Helvetica-Bold' if bold else 'Helvetica'
d.add(String(x, y, text, fontSize=size, fillColor=color,
textAnchor=anchor, fontName=font))
def add_arrow_label(d, lx, ly, tx, ty, text, color=ANNOT_CLR, size=7.5):
"""Arrow from label text to tooth surface."""
d.add(Line(lx, ly, tx, ty, strokeColor=color, strokeWidth=0.6,
strokeDashArray=[2, 1]))
d.add(Circle(tx, ty, 1.5, fillColor=color, strokeColor=color, strokeWidth=0))
anchor = 'end' if lx < tx else 'start'
d.add(String(lx, ly + 2, text, fontSize=size, fillColor=color,
textAnchor=anchor, fontName='Helvetica'))
def make_labial_diagram(w=220, h=290):
"""
Labial aspect of maxillary central incisor.
Crown: trapezoid-like shape wider at incisal edge.
Root: tapered cone.
"""
d = Drawing(w, h)
d.add(Rect(0, 0, w, h, fillColor=BG_CLR, strokeColor=None))
cx = w / 2
root_base_y = 20
cementoenamel_y = 105 # CEJ
incisal_y = 245
# ── Root ──
root_l = cx - 14
root_r = cx + 14
root_tip_x = cx
root_tip_y = root_base_y
root_path = Path(fillColor=CEMENTUM_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.4)
root_path.moveTo(root_l, cementoenamel_y)
root_path.curveTo(root_l - 2, cementoenamel_y - 30, root_tip_x - 6, root_tip_y + 20, root_tip_x, root_tip_y)
root_path.curveTo(root_tip_x + 6, root_tip_y + 20, root_r + 2, cementoenamel_y - 30, root_r, cementoenamel_y)
root_path.closePath()
d.add(root_path)
# ── Crown ──
crown_ml = cx - 27 # mesial labial at CEJ
crown_mr = cx + 20 # distal labial at CEJ (asymmetric)
crown_il = cx - 30 # mesial incisal
crown_ir = cx + 26 # distal incisal
crown_path = Path(fillColor=ENAMEL_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.6)
crown_path.moveTo(crown_ml, cementoenamel_y)
# mesial surface – nearly straight
crown_path.curveTo(crown_il + 2, cementoenamel_y + 30, crown_il, incisal_y - 20, crown_il, incisal_y)
# incisal edge
crown_path.lineTo(crown_ir, incisal_y)
# distal surface – slight convexity
crown_path.curveTo(crown_ir, incisal_y - 20, crown_mr + 4, cementoenamel_y + 30, crown_mr, cementoenamel_y)
crown_path.closePath()
d.add(crown_path)
# ── CEJ line ──
d.add(Line(crown_ml - 5, cementoenamel_y, crown_mr + 5, cementoenamel_y,
strokeColor=colors.HexColor('#6D4C41'), strokeWidth=1, strokeDashArray=[4,2]))
# ── Incisal edge ridge (highlight) ──
d.add(Line(crown_il + 3, incisal_y, crown_ir - 3, incisal_y,
strokeColor=colors.HexColor('#4FC3F7'), strokeWidth=2.5))
# ── Developmental grooves on labial surface ──
for gx in [cx - 8, cx + 8]:
d.add(Line(gx, incisal_y - 8, gx, incisal_y - 50,
strokeColor=colors.HexColor('#B0BEC5'), strokeWidth=0.8, strokeDashArray=[3, 3]))
# ── Pulp canal outline inside root ──
canal_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.8)
canal_path.moveTo(cx - 5, cementoenamel_y - 5)
canal_path.curveTo(cx - 4, cementoenamel_y - 25, cx - 2, root_tip_y + 30, cx, root_tip_y + 12)
canal_path.curveTo(cx + 2, root_tip_y + 30, cx + 4, cementoenamel_y - 25, cx + 5, cementoenamel_y - 5)
canal_path.closePath()
d.add(canal_path)
# ── Pulp chamber outline inside crown ──
pc_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.8)
pc_path.moveTo(cx - 9, cementoenamel_y + 5)
pc_path.curveTo(cx - 10, cementoenamel_y + 50, cx - 7, incisal_y - 25, cx - 5, incisal_y - 12)
pc_path.lineTo(cx + 5, incisal_y - 12)
pc_path.curveTo(cx + 7, incisal_y - 25, cx + 10, cementoenamel_y + 50, cx + 9, cementoenamel_y + 5)
pc_path.closePath()
d.add(pc_path)
# ── Pulp horn ──
d.add(Line(cx - 5, incisal_y - 12, cx - 10, incisal_y - 5,
strokeColor=PULP_CLR, strokeWidth=2))
d.add(Line(cx + 5, incisal_y - 12, cx + 10, incisal_y - 5,
strokeColor=PULP_CLR, strokeWidth=2))
# ── Annotations ──
# Enamel
add_arrow_label(d, 12, incisal_y - 40, crown_ml + 5, incisal_y - 60, 'Enamel')
# Dentin (inside crown)
add_arrow_label(d, 12, incisal_y - 80, crown_ml + 10, incisal_y - 100, 'Dentine')
# Pulp chamber
add_arrow_label(d, w - 15, incisal_y - 60, cx + 7, incisal_y - 40, 'Pulp Chamber', color=ANNOT_CLR)
# CEJ
add_arrow_label(d, w - 15, cementoenamel_y + 5, crown_mr + 2, cementoenamel_y, 'CEJ', color=colors.HexColor('#4E342E'))
# Cementum
add_arrow_label(d, w - 15, cementoenamel_y - 30, root_r - 2, cementoenamel_y - 25, 'Cementum')
# Root canal
add_arrow_label(d, w - 15, root_base_y + 30, cx + 4, root_base_y + 40, 'Root Canal')
# Mesial contact
add_label(d, crown_il + 4, incisal_y + 6, 'Mesial', size=7, color=colors.HexColor('#1B5E20'))
# Distal contact
add_label(d, crown_ir - 4, incisal_y + 6, 'Distal', size=7, color=colors.HexColor('#1B5E20'))
# Incisal edge
add_label(d, cx, incisal_y + 14, 'Incisal Edge', size=7.5, color=colors.HexColor('#006064'), bold=True)
# ── Dimension arrows (crown length) ──
d.add(Line(crown_ml - 20, cementoenamel_y, crown_ml - 20, incisal_y,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
d.add(Line(crown_ml - 23, cementoenamel_y, crown_ml - 17, cementoenamel_y,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
d.add(Line(crown_ml - 23, incisal_y, crown_ml - 17, incisal_y,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
add_label(d, crown_ml - 32, (cementoenamel_y + incisal_y) / 2, '~10.5mm',
size=6.5, color=colors.HexColor('#546E7A'), anchor='end')
# ── Title ──
add_label(d, cx, h - 12, 'LABIAL ASPECT', size=9, color=TITLE_CLR, bold=True)
return d
def make_lingual_diagram(w=220, h=290):
"""
Lingual aspect – narrower, shows cingulum, marginal ridges, lingual fossa.
"""
d = Drawing(w, h)
d.add(Rect(0, 0, w, h, fillColor=BG_CLR, strokeColor=None))
cx = w / 2
cementoenamel_y = 105
incisal_y = 245
root_base_y = 20
# ── Root ──
root_path = Path(fillColor=CEMENTUM_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.4)
root_path.moveTo(cx - 13, cementoenamel_y)
root_path.curveTo(cx - 12, cementoenamel_y - 28, cx - 5, root_base_y + 20, cx, root_base_y)
root_path.curveTo(cx + 5, root_base_y + 20, cx + 12, cementoenamel_y - 28, cx + 13, cementoenamel_y)
root_path.closePath()
d.add(root_path)
# ── Crown (lingual – narrower) ──
crown_path = Path(fillColor=ENAMEL_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.6)
crown_path.moveTo(cx - 23, cementoenamel_y) # mesial CEJ
# mesial margin
crown_path.curveTo(cx - 24, cementoenamel_y + 30, cx - 26, incisal_y - 20, cx - 26, incisal_y)
# incisal
crown_path.lineTo(cx + 22, incisal_y)
# distal margin
crown_path.curveTo(cx + 22, incisal_y - 20, cx + 20, cementoenamel_y + 30, cx + 19, cementoenamel_y)
crown_path.closePath()
d.add(crown_path)
# ── Cingulum (cervical bulge on lingual) ──
cingulum = Path(fillColor=colors.HexColor('#FFF9C4'), strokeColor=OUTLINE_CLR, strokeWidth=1.0)
cingulum.moveTo(cx - 14, cementoenamel_y + 4)
cingulum.curveTo(cx - 15, cementoenamel_y + 20, cx - 10, cementoenamel_y + 32, cx, cementoenamel_y + 32)
cingulum.curveTo(cx + 10, cementoenamel_y + 32, cx + 15, cementoenamel_y + 20, cx + 14, cementoenamel_y + 4)
cingulum.closePath()
d.add(cingulum)
# ── Mesial marginal ridge ──
d.add(Line(cx - 26, incisal_y - 15, cx - 15, cementoenamel_y + 55,
strokeColor=colors.HexColor('#8D6E63'), strokeWidth=1.2))
# ── Distal marginal ridge ──
d.add(Line(cx + 22, incisal_y - 15, cx + 12, cementoenamel_y + 55,
strokeColor=colors.HexColor('#8D6E63'), strokeWidth=1.2))
# ── Lingual fossa (ellipse) ──
fossa = Ellipse(cx, (cementoenamel_y + 40 + incisal_y - 20) / 2,
12, 28, fillColor=colors.HexColor('#DCEDC8'),
strokeColor=colors.HexColor('#558B2F'), strokeWidth=0.9)
d.add(fossa)
# ── CEJ line ──
d.add(Line(cx - 26, cementoenamel_y, cx + 22, cementoenamel_y,
strokeColor=colors.HexColor('#6D4C41'), strokeWidth=1, strokeDashArray=[4, 2]))
# ── Pulp horn outline ──
pc_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.7)
pc_path.moveTo(cx - 8, cementoenamel_y + 6)
pc_path.curveTo(cx - 9, cementoenamel_y + 45, cx - 5, incisal_y - 30, cx - 4, incisal_y - 15)
pc_path.lineTo(cx + 4, incisal_y - 15)
pc_path.curveTo(cx + 5, incisal_y - 30, cx + 9, cementoenamel_y + 45, cx + 8, cementoenamel_y + 6)
pc_path.closePath()
d.add(pc_path)
# ── Root canal ──
canal_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.7)
canal_path.moveTo(cx - 4, cementoenamel_y - 3)
canal_path.curveTo(cx - 3, cementoenamel_y - 22, cx - 2, root_base_y + 28, cx, root_base_y + 10)
canal_path.curveTo(cx + 2, root_base_y + 28, cx + 3, cementoenamel_y - 22, cx + 4, cementoenamel_y - 3)
canal_path.closePath()
d.add(canal_path)
# ── Annotations ──
add_arrow_label(d, 10, cementoenamel_y + 20, cx - 12, cementoenamel_y + 18, 'Cingulum', color=colors.HexColor('#F57F17'))
add_arrow_label(d, 10, cementoenamel_y + 60, cx - 18, cementoenamel_y + 55, 'Mesial\nMarginal Ridge', color=colors.HexColor('#4E342E'))
add_arrow_label(d, w - 12, cementoenamel_y + 60, cx + 15, cementoenamel_y + 55, 'Distal\nMarginal Ridge', color=colors.HexColor('#4E342E'))
add_arrow_label(d, w - 12, cementoenamel_y + 100, cx + 10, (cementoenamel_y + 80 + incisal_y - 20) / 2, 'Lingual\nFossa', color=colors.HexColor('#33691E'))
add_arrow_label(d, w - 12, cementoenamel_y - 25, cx + 11, cementoenamel_y - 20, 'Cementum')
add_arrow_label(d, 10, incisal_y - 50, cx - 5, incisal_y - 40, 'Pulp\nChamber', color=ANNOT_CLR)
add_label(d, cx, cementoenamel_y - 10, 'CEJ', size=7, color=colors.HexColor('#4E342E'), bold=True)
add_label(d, cx, h - 12, 'LINGUAL ASPECT', size=9, color=TITLE_CLR, bold=True)
return d
def make_mesial_diagram(w=220, h=290):
"""
Mesial aspect – shows triangular crown profile, contact area, and labio-lingual width.
"""
d = Drawing(w, h)
d.add(Rect(0, 0, w, h, fillColor=BG_CLR, strokeColor=None))
cx = w / 2
cementoenamel_y = 100
incisal_y = 240
root_base_y = 18
# ── Root (mesial view – oval/round cross section shown as tapered outline) ──
root_path = Path(fillColor=CEMENTUM_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.4)
root_path.moveTo(cx - 15, cementoenamel_y)
root_path.curveTo(cx - 14, cementoenamel_y - 28, cx - 5, root_base_y + 22, cx, root_base_y)
root_path.curveTo(cx + 5, root_base_y + 22, cx + 14, cementoenamel_y - 28, cx + 15, cementoenamel_y)
root_path.closePath()
d.add(root_path)
# ── Crown (triangular, labial side left, lingual side right) ──
lab_l = cx - 20 # labial surface
ling_r = cx + 18 # lingual surface
cej_y = cementoenamel_y
crown_path = Path(fillColor=ENAMEL_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.6)
crown_path.moveTo(lab_l, cej_y)
# labial profile – convex outward
crown_path.curveTo(lab_l - 5, cej_y + 40, lab_l - 6, incisal_y - 30, lab_l, incisal_y)
# incisal edge (tip)
crown_path.lineTo(lab_l + 10, incisal_y + 5) # slight labial inclination
# lingual profile – slightly concave (lingual fossa region)
crown_path.curveTo(ling_r + 4, incisal_y - 20, ling_r + 6, cementoenamel_y + 50, ling_r, cej_y)
crown_path.closePath()
d.add(crown_path)
# ── Cingulum bump on lingual ──
d.add(Ellipse(ling_r + 2, cej_y + 22, 5, 12,
fillColor=colors.HexColor('#FFF9C4'),
strokeColor=OUTLINE_CLR, strokeWidth=0.8))
# ── CEJ line ──
d.add(Line(lab_l - 2, cej_y, ling_r + 2, cej_y,
strokeColor=colors.HexColor('#6D4C41'), strokeWidth=1, strokeDashArray=[4, 2]))
# ── Contact area (a small highlighted oval near incisal 1/3) ──
contact_y = incisal_y - 35
contact_x = lab_l - 2
d.add(Ellipse(contact_x, contact_y, 4, 9,
fillColor=colors.HexColor('#FF8A65'),
strokeColor=colors.HexColor('#BF360C'), strokeWidth=1))
# ── Enamel + dentin differentiation ──
enamel_thick = 4
# enamel outline on labial
d.add(Line(lab_l, cej_y + 20, lab_l - enamel_thick, cej_y + 20,
strokeColor=ENAMEL_CLR, strokeWidth=2))
# ── Pulp chamber ──
pc_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.8)
pc_path.moveTo(cx - 7, cej_y + 5)
pc_path.curveTo(cx - 8, cej_y + 40, cx - 7, incisal_y - 28, cx - 5, incisal_y - 12)
pc_path.lineTo(cx + 4, incisal_y - 12)
pc_path.curveTo(cx + 6, incisal_y - 28, cx + 7, cej_y + 40, cx + 6, cej_y + 5)
pc_path.closePath()
d.add(pc_path)
# ── Root canal ──
canal_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.7)
canal_path.moveTo(cx - 4, cej_y - 2)
canal_path.curveTo(cx - 3, cej_y - 24, cx - 2, root_base_y + 28, cx, root_base_y + 10)
canal_path.curveTo(cx + 2, root_base_y + 28, cx + 3, cej_y - 24, cx + 4, cej_y - 2)
canal_path.closePath()
d.add(canal_path)
# ── Annotations ──
add_arrow_label(d, 12, incisal_y - 5, contact_x - 2, contact_y, 'Contact\nArea', color=colors.HexColor('#BF360C'))
add_arrow_label(d, 12, incisal_y - 50, lab_l - 3, incisal_y - 70, 'Labial\nSurface')
add_arrow_label(d, w - 12, incisal_y - 50, ling_r + 3, incisal_y - 70, 'Lingual\nSurface', color=colors.HexColor('#33691E'))
add_arrow_label(d, w - 12, cej_y + 20, ling_r + 2, cej_y + 20, 'Cingulum', color=colors.HexColor('#F57F17'))
add_arrow_label(d, 12, cej_y - 25, cx - 12, cej_y - 20, 'Root\n(Cementum)')
add_arrow_label(d, w - 12, cej_y - 15, cx + 3, cej_y - 8, 'CEJ', color=colors.HexColor('#4E342E'))
add_label(d, cx, h - 12, 'MESIAL ASPECT', size=9, color=TITLE_CLR, bold=True)
# ── Labio-lingual dimension ──
dim_y = cej_y - 40
d.add(Line(lab_l - 6, dim_y, ling_r + 2, dim_y,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
d.add(Line(lab_l - 6, dim_y - 3, lab_l - 6, dim_y + 3,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
d.add(Line(ling_r + 2, dim_y - 3, ling_r + 2, dim_y + 3,
strokeColor=colors.HexColor('#546E7A'), strokeWidth=0.7))
add_label(d, (lab_l + ling_r) / 2, dim_y + 6, '~7mm (L-L)',
size=6.5, color=colors.HexColor('#546E7A'))
return d
def make_distal_diagram(w=220, h=290):
"""
Distal aspect – similar to mesial but contact area is lower (middle 1/3)
and crown is slightly shorter on distal.
"""
d = Drawing(w, h)
d.add(Rect(0, 0, w, h, fillColor=BG_CLR, strokeColor=None))
cx = w / 2
cej_y = 100
incisal_y = 235 # slightly shorter crown
root_base_y = 18
lab_l = cx - 20
ling_r = cx + 18
# ── Root ──
root_path = Path(fillColor=CEMENTUM_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.4)
root_path.moveTo(cx - 14, cej_y)
root_path.curveTo(cx - 13, cej_y - 26, cx - 5, root_base_y + 22, cx, root_base_y)
root_path.curveTo(cx + 5, root_base_y + 22, cx + 13, cej_y - 26, cx + 14, cej_y)
root_path.closePath()
d.add(root_path)
# ── Crown ──
crown_path = Path(fillColor=ENAMEL_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.6)
crown_path.moveTo(lab_l, cej_y)
crown_path.curveTo(lab_l - 5, cej_y + 38, lab_l - 5, incisal_y - 28, lab_l, incisal_y)
crown_path.lineTo(lab_l + 10, incisal_y + 5)
crown_path.curveTo(ling_r + 5, incisal_y - 18, ling_r + 6, cej_y + 48, ling_r, cej_y)
crown_path.closePath()
d.add(crown_path)
# ── Cingulum ──
d.add(Ellipse(ling_r + 2, cej_y + 22, 5, 12,
fillColor=colors.HexColor('#FFF9C4'),
strokeColor=OUTLINE_CLR, strokeWidth=0.8))
# ── CEJ (distal CEJ curves slightly coronally) ──
d.add(Line(lab_l - 2, cej_y, ling_r + 2, cej_y,
strokeColor=colors.HexColor('#6D4C41'), strokeWidth=1, strokeDashArray=[4, 2]))
# ── Contact area – middle 1/3 on distal ──
contact_y = incisal_y - 70
contact_x = lab_l - 2
d.add(Ellipse(contact_x, contact_y, 4, 9,
fillColor=colors.HexColor('#FF8A65'),
strokeColor=colors.HexColor('#BF360C'), strokeWidth=1))
# ── Pulp ──
pc_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.8)
pc_path.moveTo(cx - 7, cej_y + 5)
pc_path.curveTo(cx - 8, cej_y + 38, cx - 7, incisal_y - 28, cx - 5, incisal_y - 12)
pc_path.lineTo(cx + 4, incisal_y - 12)
pc_path.curveTo(cx + 6, incisal_y - 28, cx + 7, cej_y + 38, cx + 6, cej_y + 5)
pc_path.closePath()
d.add(pc_path)
canal_path = Path(fillColor=PULP_CLR, strokeColor=ANNOT_CLR, strokeWidth=0.7)
canal_path.moveTo(cx - 4, cej_y - 2)
canal_path.curveTo(cx - 3, cej_y - 22, cx - 2, root_base_y + 28, cx, root_base_y + 10)
canal_path.curveTo(cx + 2, root_base_y + 28, cx + 3, cej_y - 22, cx + 4, cej_y - 2)
canal_path.closePath()
d.add(canal_path)
# ── Annotations ──
add_arrow_label(d, 12, incisal_y - 25, contact_x - 2, contact_y, 'Distal\nContact\n(Middle 1/3)', color=colors.HexColor('#BF360C'))
add_arrow_label(d, 12, incisal_y - 65, lab_l - 3, incisal_y - 80, 'Labial\nSurface')
add_arrow_label(d, w - 12, incisal_y - 65, ling_r + 3, incisal_y - 80, 'Lingual\nSurface', color=colors.HexColor('#33691E'))
add_arrow_label(d, w - 12, cej_y + 20, ling_r + 2, cej_y + 20, 'Cingulum', color=colors.HexColor('#F57F17'))
add_arrow_label(d, w - 12, cej_y - 15, cx + 3, cej_y - 8, 'CEJ', color=colors.HexColor('#4E342E'))
add_arrow_label(d, 12, cej_y - 20, cx - 12, cej_y - 15, 'Root\n(Cementum)')
add_label(d, cx, h - 12, 'DISTAL ASPECT', size=9, color=TITLE_CLR, bold=True)
return d
def make_incisal_diagram(w=220, h=220):
"""
Incisal / Occlusal aspect – looking from incisal edge down.
Shows trapezoid outline, labial convexity, cingulum, lingual fossa.
"""
d = Drawing(w, h)
d.add(Rect(0, 0, w, h, fillColor=BG_CLR, strokeColor=None))
cx = w / 2
cy = h / 2 + 5
# ── Overall outline: trapezoid with convex labial, concave lingual ──
lab_w = 54 # labial (wider)
ling_w = 38 # lingual (narrower)
depth = 50 # labio-lingual depth
lab_y = cy - depth / 2
ling_y = cy + depth / 2
# Crown outline
crown = Path(fillColor=ENAMEL_CLR, strokeColor=OUTLINE_CLR, strokeWidth=1.6)
crown.moveTo(cx - lab_w / 2, lab_y)
# labial edge – convex
crown.curveTo(cx - lab_w / 2 + 5, lab_y - 10, cx + lab_w / 2 - 5, lab_y - 10, cx + lab_w / 2, lab_y)
# distal side
crown.lineTo(cx + ling_w / 2, ling_y)
# lingual – slightly concave
crown.curveTo(cx + ling_w / 2 - 4, ling_y + 6, cx - ling_w / 2 + 4, ling_y + 6, cx - ling_w / 2, ling_y)
# mesial side
crown.lineTo(cx - lab_w / 2, lab_y)
crown.closePath()
d.add(crown)
# ── Enamel layer (inner offset outline shows dentin beneath) ──
en_thick = 6
dentin = Path(fillColor=DENTIN_CLR, strokeColor=colors.HexColor('#C8A86A'), strokeWidth=1)
dentin.moveTo(cx - lab_w / 2 + en_thick, lab_y + en_thick / 2)
dentin.curveTo(cx - lab_w / 2 + en_thick + 3, lab_y - 4,
cx + lab_w / 2 - en_thick - 3, lab_y - 4,
cx + lab_w / 2 - en_thick, lab_y + en_thick / 2)
dentin.lineTo(cx + ling_w / 2 - en_thick, ling_y - en_thick / 2)
dentin.curveTo(cx + ling_w / 2 - en_thick - 3, ling_y + 3,
cx - ling_w / 2 + en_thick + 3, ling_y + 3,
cx - ling_w / 2 + en_thick, ling_y - en_thick / 2)
dentin.closePath()
d.add(dentin)
# ── Pulp in cross section ──
d.add(Ellipse(cx, cy + 5, 10, 14, fillColor=PULP_CLR,
strokeColor=ANNOT_CLR, strokeWidth=0.9))
# ── Cingulum (raised oval on lingual) ──
d.add(Ellipse(cx, ling_y - 8, 12, 8,
fillColor=colors.HexColor('#FFF9C4'),
strokeColor=colors.HexColor('#F9A825'), strokeWidth=1))
# ── Lingual fossa (depression oval) ──
d.add(Ellipse(cx, cy + 6, 10, 14,
fillColor=colors.HexColor('#DCEDC8'),
strokeColor=colors.HexColor('#558B2F'), strokeWidth=0.8,
strokeDashArray=[2, 2]))
# ── Marginal ridges ──
d.add(Line(cx - lab_w / 2 + 4, lab_y + 2, cx - ling_w / 2 + 3, ling_y - 3,
strokeColor=colors.HexColor('#8D6E63'), strokeWidth=1.2))
d.add(Line(cx + lab_w / 2 - 4, lab_y + 2, cx + ling_w / 2 - 3, ling_y - 3,
strokeColor=colors.HexColor('#8D6E63'), strokeWidth=1.2))
# ── Incisal edge highlight ──
d.add(Line(cx - lab_w / 2 + 3, lab_y - 3, cx + lab_w / 2 - 3, lab_y - 3,
strokeColor=colors.HexColor('#4FC3F7'), strokeWidth=2.5))
# ── Axis labels ──
add_label(d, cx, lab_y - 18, 'LABIAL', size=8, color=LABEL_CLR, bold=True)
add_label(d, cx, ling_y + 20, 'LINGUAL', size=8, color=colors.HexColor('#2E7D32'), bold=True)
add_label(d, cx - lab_w / 2 - 18, cy, 'MESIAL', size=8, color=LABEL_CLR, bold=True)
add_label(d, cx + lab_w / 2 + 14, cy, 'DISTAL', size=8, color=LABEL_CLR, bold=True)
# ── Annotation arrows ──
add_arrow_label(d, 15, lab_y + 28, cx - lab_w / 2 + 3, lab_y + 5, 'Enamel\n(6 labels at rim)')
add_arrow_label(d, w - 15, lab_y + 28, cx + lab_w / 2 - 3, lab_y + 5, 'Incisal Edge')
add_arrow_label(d, 15, ling_y - 12, cx - ling_w / 2 + 4, ling_y - 6, 'Mesial\nMarginal Ridge', color=colors.HexColor('#4E342E'))
add_arrow_label(d, w - 15, ling_y - 12, cx + ling_w / 2 - 4, ling_y - 6, 'Distal\nMarginal Ridge', color=colors.HexColor('#4E342E'))
add_arrow_label(d, w - 15, cy + 5, cx + 12, cy + 5, 'Pulp Cross-\nSection', color=ANNOT_CLR)
add_arrow_label(d, 15, cy + 15, cx - 9, ling_y - 8, 'Cingulum', color=colors.HexColor('#F57F17'))
add_label(d, cx, h - 10, 'INCISAL ASPECT', size=9, color=TITLE_CLR, bold=True)
return d
# ═══════════════════════════════════════════════════════════════════════════════
# CONTENT BUILDER
# ═══════════════════════════════════════════════════════════════════════════════
def P(text, style=None):
return Paragraph(text, style or body_style)
def Bullet(text):
return Paragraph(f'<bullet>\u2022</bullet> {text}', bullet_style)
def H1(text):
return Paragraph(text, section_style)
def H2(text):
return Paragraph(text, subsection_style)
def HR():
return HRFlowable(width='100%', thickness=1.2, color=SECTION_CLR, spaceAfter=4)
def Note(text):
return Paragraph(f'<i>Note: {text}</i>', note_style)
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ═══════════════════════════════════════════════════════════════════════════════
story = []
# ────────────────────────── COVER ──────────────────────────────────────────────
story.append(Spacer(1, 0.5 * cm))
# College header table
header_data = [[
Paragraph('<b>DENTAL HISTOLOGY & ANATOMY</b><br/>'
'<font size="10">1st Year Bachelor of Dental Surgery (BDS)</font>',
ParagraphStyle('hdr', parent=styles['Normal'],
fontSize=14, textColor=colors.white,
fontName='Helvetica-Bold', alignment=TA_CENTER,
leading=20))
]]
header_tbl = Table(header_data, colWidths=[PAGE_W - 2 * MARGIN])
header_tbl.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, -1), TITLE_CLR),
('ROWPADDING', (0, 0), (-1, -1), 12),
('ROUNDEDCORNERS', [6]),
]))
story.append(header_tbl)
story.append(Spacer(1, 0.4 * cm))
story.append(Paragraph('MAXILLARY CENTRAL INCISOR', title_style))
story.append(Paragraph('Complete Five-Surface Analysis with Annotated Diagrams', subtitle_style))
story.append(Spacer(1, 0.2 * cm))
story.append(HR())
story.append(Spacer(1, 0.2 * cm))
# ─── Quick Reference Table ─────────────────────────────────────────────────────
story.append(H1('Quick Reference: Measurements & Key Features'))
story.append(Spacer(1, 0.2 * cm))
ref_data = [
[Paragraph('<b>Parameter</b>', table_header_style),
Paragraph('<b>Value</b>', table_header_style),
Paragraph('<b>Clinical Significance</b>', table_header_style)],
[P('Total tooth length'), P('22.5 mm (average)'), P('Varies 18-26 mm; reference for implant planning')],
[P('Crown length (cervico-incisal)'), P('10.5 mm'), P('Reference for crown-to-root ratio')],
[P('Root length'), P('12.0 mm'), P('Single straight root; apical 1/3 may curve')],
[P('Crown width (mesio-distal)'), P('8.5 mm (max)'), P('Wider than any other incisor; key aesthetic unit')],
[P('Crown thickness (labio-lingual)'), P('7.0 mm'), P('Thinner lingual due to cingulum')],
[P('Number of roots'), P('1'), P('Single, straight, conical root')],
[P('Number of root canals'), P('1 (rarely 2)'), P('Wide oval canal; tapers apically')],
[P('Pulp horns'), P('2 (mesial + distal)'), P('Mesial horn larger; important in pulp capping')],
[P('Eruption (permanent)'), P('7-8 years'), P('Replaced deciduous central incisor at age 6-7')],
[P('Root complete'), P('10 years'), P('Root apex closes ~3 years post-eruption')],
]
ref_table = Table(ref_data, colWidths=[5.5 * cm, 3.5 * cm, 8 * cm])
ref_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), TITLE_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(ref_table)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 1 – LABIAL ASPECT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('1. Labial Aspect'))
story.append(Spacer(1, 0.1 * cm))
# Diagram + text side by side
labial_dwg = make_labial_diagram(220, 290)
labial_text = [
[H2('General Shape'),
P('The labial surface is the most visible surface facing the lips. The crown outline '
'when viewed from the front is roughly trapezoidal - widest at the incisal edge '
'and narrower at the cervical margin.')],
[H2('Crown Outline'),
P('<b>Incisal edge:</b> Nearly straight in a newly erupted tooth; develops wear facets with age. '
'Mamelons (three lobes) are visible in newly erupted teeth and wear away quickly. '
'<b>Mesial outline:</b> Straighter, meets the incisal edge at an almost right angle. '
'<b>Distal outline:</b> More rounded, meets incisal edge at a rounded angle. '
'<b>Cervical outline (CEJ):</b> Curves toward the root apex (apically convex).')],
[H2('Surface Texture'),
P('Fine labial developmental grooves run vertically dividing the surface into three '
'lobes: mesio-labial, labio-incisal, and disto-labial. The labial surface is convex '
'in both directions (cervico-incisally and mesio-distally).')],
[H2('Contact Areas'),
P('<b>Mesial contact:</b> Located in the incisal third, very close to the incisal angle. '
'<b>Distal contact:</b> Located in the middle-incisal third junction, slightly lower than mesial.')],
[H2('CEJ on Labial'),
P('The cervico-enamel junction curves toward the incisal edge (coronally) on the mesial '
'side. On the labial view, the CEJ curvature is approximately 3.5 mm deep.')],
]
lab_detail_table = Table(
[[renderPDF.GraphicsFlowable(labial_dwg),
Table(labial_text, colWidths=[3.5 * cm, 9 * cm],
style=TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), 3)]))]],
colWidths=[6 * cm, PAGE_W - 2 * MARGIN - 6 * cm]
)
lab_detail_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
]))
story.append(lab_detail_table)
story.append(Spacer(1, 0.2 * cm))
story.append(Note('Key exam point: The mesial angle is sharper (more right-angled) '
'than the distal angle (more rounded). This distinguishes right from left.'))
story.append(HR())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 2 – LINGUAL ASPECT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('2. Lingual Aspect'))
story.append(Spacer(1, 0.1 * cm))
lingual_dwg = make_lingual_diagram(220, 290)
lingual_text = [
[H2('Cingulum'),
P('The <b>cingulum</b> is the most distinctive feature of the lingual surface. '
'It is a rounded enamel bulge at the cervical third of the lingual surface. '
'It represents the lingual lobe. It is well-developed in the maxillary central incisor.')],
[H2('Lingual Fossa'),
P('A shallow, smooth <b>concave depression</b> occupying the middle and incisal thirds '
'of the lingual surface between the marginal ridges. Bound cervically by the cingulum '
'and incisally by the lingual surface of the incisal edge.')],
[H2('Marginal Ridges'),
P('<b>Mesial marginal ridge:</b> Runs from the mesio-incisal angle to the cingulum. '
'Slightly more prominent than the distal marginal ridge. '
'<b>Distal marginal ridge:</b> Runs from the disto-incisal angle to the cingulum. '
'Both form the lateral borders of the lingual fossa.')],
[H2('CEJ on Lingual'),
P('The CEJ on the lingual side curves <b>less apically</b> (toward the root) than '
'the labial side. The curvature is approximately 2.5 mm. This is a general rule '
'for all teeth - labial CEJ curvature > lingual CEJ curvature.')],
[H2('Lingual Ridge'),
P('Some texts describe a faint <b>lingual ridge</b> running from the cingulum toward '
'the incisal edge, dividing the lingual fossa into mesial and distal fossae. '
'This is variable and may be absent.')],
]
ling_detail_table = Table(
[[renderPDF.GraphicsFlowable(lingual_dwg),
Table(lingual_text, colWidths=[3.5 * cm, 9 * cm],
style=TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), 3)]))]],
colWidths=[6 * cm, PAGE_W - 2 * MARGIN - 6 * cm]
)
ling_detail_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
]))
story.append(ling_detail_table)
story.append(Spacer(1, 0.2 * cm))
story.append(Note('Cingulum is the remnant of the lingual lobe - present in all maxillary anterior teeth. '
'Pronounced cingulum = higher caries risk in the lingual fossa pit.'))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 3 – MESIAL ASPECT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('3. Mesial Aspect'))
story.append(Spacer(1, 0.1 * cm))
mesial_dwg = make_mesial_diagram(220, 290)
mesial_text = [
[H2('Crown Profile'),
P('When viewed from the mesial side, the crown is roughly <b>triangular</b> with '
'the apex at the incisal edge and the base at the CEJ. '
'The labial profile is convex. The lingual profile is concave in the middle '
'(lingual fossa region) and convex at the cervical (cingulum).')],
[H2('Incisal Edge'),
P('The incisal edge is positioned <b>labial to the long axis</b> of the root. '
'This labial inclination is important for anterior guidance in occlusion.')],
[H2('Contact Area'),
P('The <b>mesial contact area</b> is located in the <b>incisal third</b> of the '
'mesial surface, very close to the mesio-incisal angle. This is the highest '
'contact area among all posterior and anterior teeth.')],
[H2('CEJ Curvature'),
P('The CEJ curves <b>incisally (coronally)</b> on the mesial surface to a depth of '
'approximately <b>3.5 mm</b>. This is the greatest CEJ curvature of any tooth in the '
'mouth and is a key identifying feature.')],
[H2('Root Outline'),
P('The root appears broad at the cervix and tapers smoothly to a rounded apex. '
'A <b>developmental depression</b> may be present on the mesial root surface. '
'Root is slightly concave on the mesial surface near the apex.')],
]
mes_detail_table = Table(
[[renderPDF.GraphicsFlowable(mesial_dwg),
Table(mesial_text, colWidths=[3.5 * cm, 9 * cm],
style=TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), 3)]))]],
colWidths=[6 * cm, PAGE_W - 2 * MARGIN - 6 * cm]
)
mes_detail_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
]))
story.append(mes_detail_table)
story.append(Spacer(1, 0.2 * cm))
story.append(Note('Mesial CEJ curvature of 3.5 mm is the MOST curvature of any tooth surface in the dentition. '
'Exam favourite for identification purposes.'))
story.append(HR())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 4 – DISTAL ASPECT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('4. Distal Aspect'))
story.append(Spacer(1, 0.1 * cm))
distal_dwg = make_distal_diagram(220, 290)
distal_text = [
[H2('Differences from Mesial'),
P('The distal aspect is similar to the mesial but has several distinguishing features: '
'(1) the crown appears slightly <b>shorter</b>; '
'(2) the incisal edge appears more rounded; '
'(3) the overall crown width is slightly less.')],
[H2('Contact Area'),
P('The <b>distal contact area</b> is in the <b>junction of incisal and middle thirds</b>, '
'positioned slightly <b>more cervically</b> than the mesial contact area. '
'This is a key difference used to identify right vs. left central incisor.')],
[H2('CEJ Curvature'),
P('The CEJ curvature on the distal is approximately <b>2.5 mm</b> - less than the mesial '
'(3.5 mm). This rule applies: mesial CEJ curvature > distal CEJ curvature for all anterior teeth.')],
[H2('Crown Profile'),
P('The labial surface is convex. The lingual profile shows the cingulum at the cervical '
'and a slight concavity corresponding to the lingual fossa in the middle third. '
'The outline is slightly more convex distally than mesially.')],
[H2('Root'),
P('The distal root surface is usually <b>convex</b> (less likely to have developmental '
'depression compared to mesial). Root tapers to a blunt, rounded apex that may '
'show a slight distal inclination in some individuals.')],
]
dis_detail_table = Table(
[[renderPDF.GraphicsFlowable(distal_dwg),
Table(distal_text, colWidths=[3.5 * cm, 9 * cm],
style=TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), 3)]))]],
colWidths=[6 * cm, PAGE_W - 2 * MARGIN - 6 * cm]
)
dis_detail_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
]))
story.append(dis_detail_table)
story.append(Spacer(1, 0.2 * cm))
story.append(Note('Key: Mesial contact = incisal 1/3; Distal contact = incisal-middle 1/3 junction. '
'Use this to determine side (right vs. left).'))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 5 – INCISAL ASPECT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('5. Incisal Aspect'))
story.append(Spacer(1, 0.1 * cm))
incisal_dwg = make_incisal_diagram(220, 220)
incisal_text = [
[H2('Overall Outline'),
P('Viewed from the incisal edge, the crown appears <b>trapezoid</b> with the wider '
'base labially and narrower portion lingually. It is widest at the incisal edge '
'and narrows as it goes toward the lingual.')],
[H2('Labial Surface'),
P('The labial surface appears <b>convex</b>. The incisal edge (visible as a line) '
'is nearly straight in a newly erupted tooth. The labio-incisal angle on the '
'mesial is sharper; on the distal it is more rounded.')],
[H2('Lingual Features'),
P('<b>Cingulum:</b> Appears as a raised rounded area at the lingual. '
'<b>Lingual fossa:</b> The concave region between the cingulum and incisal edge, '
'bounded by the mesial and distal marginal ridges. '
'<b>Marginal ridges</b> are visible on both sides.')],
[H2('Enamel Thickness'),
P('The cross-section reveals <b>enamel thickest at the incisal edge</b> (up to 2 mm) '
'and progressively thinning toward the cervical margin. The labial enamel is thicker '
'than the lingual enamel. The dentin core is central.')],
[H2('Mesio-Distal Asymmetry'),
P('From the incisal view, the <b>mesial half is slightly wider</b> than the distal half. '
'This mesio-distal asymmetry, combined with the angulation of the incisal '
'edge, helps identify right vs. left tooth. '
'The tooth tilts slightly to the distal when the mesial surface is viewed.')],
]
inc_detail_table = Table(
[[renderPDF.GraphicsFlowable(incisal_dwg),
Table(incisal_text, colWidths=[3.5 * cm, 9 * cm],
style=TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWPADDING', (0,0), (-1,-1), 3)]))]],
colWidths=[6 * cm, PAGE_W - 2 * MARGIN - 6 * cm]
)
inc_detail_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
]))
story.append(inc_detail_table)
story.append(Spacer(1, 0.2 * cm))
story.append(Note('The incisal view is critical for wax carving practicals. '
'Remember: labial side is wider and convex; lingual side narrower with cingulum.'))
story.append(HR())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 6 – PULP CAVITY
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('6. Pulp Cavity'))
story.append(Spacer(1, 0.1 * cm))
pulp_data = [
[Paragraph('<b>Region</b>', table_header_style),
Paragraph('<b>Description</b>', table_header_style)],
[P('<b>Pulp Chamber</b>'), P('Broad mesiodistally in the coronal third; two pulp horns (mesial > distal). '
'Tapers incisally toward the pulp horns and cervically toward the root canal.')],
[P('<b>Pulp Horns</b>'), P('Two horns pointing toward the mesio-incisal and disto-incisal corners. '
'Mesial horn is larger. Important clinically during cavity preparation to avoid pulp exposure.')],
[P('<b>Root Canal</b>'), P('Single canal, wide at cervical third, oval in cross section, tapers to a point near apex. '
'Average length of root canal = 12 mm. Rarely bifurcated.')],
[P('<b>Apical Foramen</b>'), P('Located at or near the anatomic apex. Varies in position - may be slightly '
'lateral to the geometric apex in some teeth.')],
[P('<b>Accessory Canals</b>'), P('Can be present especially in the apical third. Important for endodontic treatment success.')],
]
pulp_table = Table(pulp_data, colWidths=[4 * cm, 13 * cm])
pulp_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), TITLE_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(pulp_table)
story.append(Spacer(1, 0.3 * cm))
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 7 – HISTOLOGY OF DENTAL TISSUES
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('7. Histology of Dental Tissues'))
story.append(Spacer(1, 0.1 * cm))
histo_data = [
[Paragraph('<b>Tissue</b>', table_header_style),
Paragraph('<b>Composition</b>', table_header_style),
Paragraph('<b>Key Histological Features</b>', table_header_style)],
[P('<b>Enamel</b>'),
P('96% inorganic (HAP), 1% organic, 3% water'),
P('Enamel rods (prisms) run from DEJ to surface. '
'Hunter-Schreger bands (alternating rod direction). '
'Striae of Retzius (incremental lines). '
'Enamel spindles at DEJ. '
'Neonatal line (if present). No cells - cannot regenerate.')],
[P('<b>Dentine</b>'),
P('70% inorganic (HAP), 20% organic (collagen), 10% water'),
P('Dentinal tubules containing odontoblast processes (Tomes fibres). '
'Peritubular dentine (hypermineralised) around each tubule. '
'Intertubular dentine between tubules. '
'Incremental lines: Lines of von Ebner (daily), Owen\'s contour lines. '
'Interglobular dentine (unmineralised). '
'Dead tracts = empty dentinal tubules.')],
[P('<b>Cementum</b>'),
P('50% inorganic, 50% organic (collagen) + water'),
P('Acellular (primary) cementum: covers coronal 2/3 of root. '
'Cellular (secondary) cementum: covers apical 1/3. '
'Sharpey\'s fibres = embedded extrinsic fibres of PDL. '
'Incremental lines of Salter.')],
[P('<b>Pulp</b>'),
P('Soft connective tissue; cells, fibres, ground substance, blood vessels, nerves'),
P('Odontoblastic layer (peripheral). '
'Cell-free zone of Weil. '
'Cell-rich zone. '
'Pulp core: fibroblasts, macrophages, blood vessels (arterioles), '
'nerve fibres (A-delta, C fibres). '
'Dental pulp stem cells present.')],
]
histo_table = Table(histo_data, colWidths=[3 * cm, 5 * cm, 9 * cm])
histo_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), TITLE_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(histo_table)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 8 – RULES FOR IDENTIFICATION (LAWS OF ASHLEY HOWE)
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('8. Rules for Identification (Right vs. Left)'))
story.append(Spacer(1, 0.1 * cm))
story.append(P('<b>Laws of Ashley Howe</b> (applied to maxillary central incisor):'))
story.append(Spacer(1, 0.1 * cm))
rules_data = [
[Paragraph('<b>Rule</b>', table_header_style),
Paragraph('<b>Feature</b>', table_header_style),
Paragraph('<b>Application</b>', table_header_style)],
[P('1. Curvature of CEJ'), P('Mesial > Distal'), P('Place tooth so CEJ curves more on the side you call mesial')],
[P('2. Contact area location'), P('Mesial in incisal 1/3\nDistal in incisal-middle 1/3 junction'),
P('Mesial contact is closer to incisal edge; distal is slightly lower')],
[P('3. Labial surface outline'), P('Mesial angle = right angle\nDistal angle = rounded'),
P('The sharper corner faces the midline (mesial).')],
[P('4. Curvature of crown'),
P('More curvature on labial surface distally'),
P('The labial surface curves away more on the distal side; mesial is flatter.')],
[P('5. Root inclination'), P('Root tip inclines distally'),
P('When the labial surface faces you, the root tip points to the side away from midline = distal')],
]
rules_table = Table(rules_data, colWidths=[3.5 * cm, 5 * cm, 8.5 * cm])
rules_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), SECTION_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(rules_table)
story.append(Spacer(1, 0.3 * cm))
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 9 – DECIDUOUS vs. PERMANENT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('9. Deciduous vs. Permanent Maxillary Central Incisor'))
story.append(Spacer(1, 0.1 * cm))
dec_perm_data = [
[Paragraph('<b>Feature</b>', table_header_style),
Paragraph('<b>Deciduous</b>', table_header_style),
Paragraph('<b>Permanent</b>', table_header_style)],
[P('Total length'), P('~16 mm'), P('~22.5 mm')],
[P('Crown length'), P('~6 mm'), P('~10.5 mm')],
[P('Crown width (M-D)'), P('~6.5 mm'), P('~8.5 mm')],
[P('Enamel colour'), P('More opaque, chalky white'), P('More translucent, off-white')],
[P('Crown:Root ratio'), P('Crown appears larger relative to root'), P('Normal proportions')],
[P('Cervical constriction'), P('More pronounced'), P('Less pronounced')],
[P('Labial surface'), P('More convex'), P('Flatter, with developmental grooves')],
[P('Pulp chamber'), P('Relatively larger'), P('Proportionally smaller')],
[P('Root'), P('Narrow, tapers rapidly'), P('Broad, regular taper')],
[P('Eruption'), P('6-10 months (central incisor)'), P('7-8 years')],
]
dec_table = Table(dec_perm_data, colWidths=[5 * cm, 5.5 * cm, 6.5 * cm])
dec_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), SECTION_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(dec_table)
story.append(Spacer(1, 0.3 * cm))
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 10 – CLINICAL SIGNIFICANCE
# ═══════════════════════════════════════════════════════════════════════════════
story.append(H1('10. Clinical Significance'))
story.append(Spacer(1, 0.1 * cm))
clin_data = [
[Paragraph('<b>Clinical Aspect</b>', table_header_style),
Paragraph('<b>Relevance</b>', table_header_style)],
[P('<b>Aesthetic</b>'), P('Most visible tooth in the smile; cornerstone of smile design and cosmetic dentistry. '
'Golden proportion: width/height ratio ~75-80%.')],
[P('<b>Trauma</b>'), P('Most commonly injured tooth (crown fractures, avulsions, luxations) due to anterior position. '
'Immature apex after trauma requires special endodontic management (apexogenesis/apexification).')],
[P('<b>Endodontics</b>'), P('Straight single root canal - relatively straightforward RCT. '
'Access cavity is a rounded triangle/shovel shape from lingual. '
'Working length ~22.5 mm.')],
[P('<b>Restorations</b>'), P('Class III cavities (proximal without incisal angle) and Class IV (including incisal angle) '
'are common. Composite resin is preferred for aesthetics.')],
[P('<b>Orthodontics</b>'), P('Key landmark for occlusal analysis. Used to assess midline deviation, overjet, and overbite. '
'Most commonly bonded for orthodontic brackets.')],
[P('<b>Implantology</b>'), P('Single implant replacement for congenitally missing or extracted central incisor. '
'Root length and bone availability are key pre-implant assessment factors.')],
]
clin_table = Table(clin_data, colWidths=[4 * cm, 13 * cm])
clin_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), SECTION_CLR),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#EEF2FF')]),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#9FA8DA')),
('ROWPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
story.append(clin_table)
story.append(Spacer(1, 0.3 * cm))
story.append(HR())
# ═══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ═══════════════════════════════════════════════════════════════════════════════
footer_text = ('References: Wheeler\'s Dental Anatomy, Physiology and Occlusion | '
'Ash & Nelson Dental Anatomy | '
'Orban\'s Oral Histology and Embryology | '
'Bath-Balogh Illustrated Dental Embryology, Histology & Anatomy')
story.append(P(f'<font size="8" color="#546E7A"><i>{footer_text}</i></font>'))
# ─── Build ──────────────────────────────────────────────────────────────────────
def add_page_number(canvas, doc):
canvas.saveState()
canvas.setFont('Helvetica', 8)
canvas.setFillColor(colors.HexColor('#546E7A'))
canvas.drawCentredString(PAGE_W / 2, 1 * cm,
f'Maxillary Central Incisor - Dental Histology & Anatomy | 1st Year BDS | Page {doc.page}')
canvas.restoreState()
doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
print("PDF generated successfully!")
cd /home/daytona/workspace/maxillary-incisor-pdf && python generate_pdf.py 2>&1