Make PDF of Larynx Anatomy with some anatomical images and make it interesting by learning point of view. According MBBS fourth year student.
larynx anatomy StatPearls laryngeal cartilages muscles innervation blood supply
larynx anatomy
larynx anatomy sagittal section showing cartilages vocal folds epiglottis
intrinsic muscles of larynx posterior cricoarytenoid laryngeal nerve anatomy diagram
~/
~/
mkdir -p "/tmp/workspace/larynx-anatomy-pdf/src" "/tmp/workspace/larynx-anatomy-pdf/assets" "/tmp/workspace/larynx-anatomy-pdf/output" && python - <<'PY' import requests, os imgs={ 'larynx_relation.png':'https://cdn.orris.care/cdss_images/439d7b432d8c5a1e318f166541a0b8069a7052737cfef721299bfcf2acb21cbb.png', 'cricoid.png':'https://cdn.orris.care/cdss_images/b9e263da6d29714b524cf9ad05037a2896a99821203e71de9e57568174f9b3b8.png', 'innervation.png':'https://cdn.orris.care/cdss_images/df48b894e180c38237a36132e1a6a45e04e536cd4ba11a75e1e384de1628e7fd.png', 'veins.png':'https://cdn.orris.care/cdss_images/eb5c951d04ac207a6371e51a9e7214c0e7c429718455ed00c4e81b09ae361ebc.png' } for f,u in imgs.items(): r=requests.get(u,timeout=30); r.raise_for_status(); open('/tmp/workspace/larynx-anatomy-pdf/assets/'+f,'wb').write(r.content) print(f,len(r.content)) PY
~/larynx-anatomy-pdf/src/create_larynx_pdf.py
from reportlab.lib import colors
from reportlab.lib.colors import HexColor
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle,
Image, KeepTogether, Flowable)
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
from reportlab.lib.utils import ImageReader
from reportlab.pdfgen import canvas
import os
from PIL import Image as PILImage
ROOT='/tmp/workspace/larynx-anatomy-pdf'
ASSETS=os.path.join(ROOT,'assets')
OUT=os.path.join(ROOT,'output','Larynx_Anatomy_MBBS_4th_Year.pdf')
# Palette
NAVY=HexColor('#12304A'); BLUE=HexColor('#2378A5'); TEAL=HexColor('#008C95')
GOLD=HexColor('#F2B84B'); PALE=HexColor('#EAF4F7'); INK=HexColor('#1F2933')
RED=HexColor('#BB3E3E'); GREEN=HexColor('#2F855A'); GREY=HexColor('#61707D')
styles=getSampleStyleSheet()
styles.add(ParagraphStyle(name='CoverTitle', parent=styles['Title'], fontName='Helvetica-Bold', fontSize=32, leading=38, textColor=colors.white, alignment=TA_LEFT, spaceAfter=12))
styles.add(ParagraphStyle(name='CoverSub', parent=styles['Normal'], fontName='Helvetica', fontSize=14, leading=20, textColor=HexColor('#D9EDF5')))
styles.add(ParagraphStyle(name='H1x', parent=styles['Heading1'], fontName='Helvetica-Bold', fontSize=21, leading=25, textColor=NAVY, spaceBefore=2, spaceAfter=10))
styles.add(ParagraphStyle(name='H2x', parent=styles['Heading2'], fontName='Helvetica-Bold', fontSize=13, leading=17, textColor=BLUE, spaceBefore=7, spaceAfter=5))
styles.add(ParagraphStyle(name='Bodyx', parent=styles['BodyText'], fontName='Helvetica', fontSize=9.4, leading=13.2, textColor=INK, spaceAfter=6))
styles.add(ParagraphStyle(name='Small', parent=styles['BodyText'], fontName='Helvetica', fontSize=7.5, leading=10, textColor=GREY))
styles.add(ParagraphStyle(name='Key', parent=styles['BodyText'], fontName='Helvetica-Bold', fontSize=9.2, leading=13, textColor=NAVY))
styles.add(ParagraphStyle(name='Caption', parent=styles['BodyText'], fontName='Helvetica-Oblique', fontSize=7.5, leading=9.5, textColor=GREY, alignment=TA_CENTER))
styles.add(ParagraphStyle(name='TableHead', parent=styles['BodyText'], fontName='Helvetica-Bold', fontSize=8.2, leading=10, textColor=colors.white))
styles.add(ParagraphStyle(name='TableCell', parent=styles['BodyText'], fontName='Helvetica', fontSize=7.9, leading=10, textColor=INK))
class PageNumberCanvas(canvas.Canvas):
def __init__(self,*args,**kwargs):
canvas.Canvas.__init__(self,*args,**kwargs); self.pages=[]
def showPage(self): self.pages.append(dict(self.__dict__)); self._startPage()
def save(self):
n=len(self.pages)
for state in self.pages:
self.__dict__.update(state)
if self._pageNumber>1:
self.setStrokeColor(HexColor('#C7D7DE')); self.line(1.5*cm,1.25*cm,19.5*cm,1.25*cm)
self.setFont('Helvetica',7.5); self.setFillColor(GREY)
self.drawString(1.5*cm,0.78*cm,'Larynx Anatomy | MBBS Fourth Year')
self.drawRightString(19.5*cm,0.78*cm,f'{self._pageNumber-1} / {n-1}')
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
class Callout(Flowable):
def __init__(self,title,body,accent=TEAL,width=17.8*cm):
Flowable.__init__(self); self.title=title; self.body=body; self.accent=accent; self.width=width; self.height=0
def wrap(self,availW,availH):
self.width=min(self.width,availW)
self.p1=Paragraph(self.title,styles['Key']); self.p2=Paragraph(self.body,styles['Bodyx'])
_,h1=self.p1.wrap(self.width-18,self.height); _,h2=self.p2.wrap(self.width-18,self.height)
self.height=h1+h2+15; return self.width,self.height
def draw(self):
self.canv.setFillColor(PALE); self.canv.roundRect(0,0,self.width,self.height,5,fill=1,stroke=0)
self.canv.setFillColor(self.accent); self.canv.roundRect(0,0,5,self.height,5,fill=1,stroke=0)
self.p1.drawOn(self.canv,11,self.height-9-self.p1.height)
self.p2.drawOn(self.canv,11,7)
def P(txt,style='Bodyx'): return Paragraph(txt,styles[style])
def bullet(txt): return Paragraph('• '+txt,styles['Bodyx'])
def img(name,maxw,maxh):
path=os.path.join(ASSETS,name); im=PILImage.open(path); w,h=im.size
scale=min(maxw/w,maxh/h); return Image(path,width=w*scale,height=h*scale)
def box_table(rows,widths,head=True):
data=[]
for ri,row in enumerate(rows): data.append([P(str(v),'TableHead' if head and ri==0 else 'TableCell') for v in row])
t=Table(data,colWidths=widths,repeatRows=1 if head else 0,hAlign='LEFT')
ts=[('VALIGN',(0,0),(-1,-1),'TOP'),('GRID',(0,0),(-1,-1),0.3,HexColor('#B9CDD5')),('LEFTPADDING',(0,0),(-1,-1),6),('RIGHTPADDING',(0,0),(-1,-1),6),('TOPPADDING',(0,0),(-1,-1),5),('BOTTOMPADDING',(0,0),(-1,-1),5)]
if head: ts += [('BACKGROUND',(0,0),(-1,0),NAVY),('BACKGROUND',(0,1),(-1,-1),colors.white)]
t.setStyle(TableStyle(ts)); return t
def draw_larynx_schematic(c, x,y,w,h):
# y bottom, compact frontal cartoon
c.saveState(); c.setStrokeColor(NAVY); c.setLineWidth(2)
# thyroid shield
c.setFillColor(HexColor('#82BDD7')); p=c.beginPath(); p.moveTo(x+w*.18,y+h*.80); p.lineTo(x+w*.50,y+h*.63); p.lineTo(x+w*.82,y+h*.80); p.lineTo(x+w*.72,y+h*.24); p.lineTo(x+w*.50,y+h*.10); p.lineTo(x+w*.28,y+h*.24); p.close(); c.drawPath(p,fill=1,stroke=1)
# cricoid
c.setFillColor(HexColor('#62A8C8')); c.roundRect(x+w*.31,y+h*.02,w*.38,h*.15,5,fill=1,stroke=1)
# epiglottis
c.setFillColor(HexColor('#9ACB82')); c.setLineWidth(1.5); c.ellipse(x+w*.42,y+h*.76,x+w*.58,y+h*1.02,fill=1,stroke=1)
# arytenoids
c.setFillColor(HexColor('#E2A95F')); c.circle(x+w*.41,y+h*.44,w*.055,fill=1,stroke=1); c.circle(x+w*.59,y+h*.44,w*.055,fill=1,stroke=1)
# vocal folds
c.setStrokeColor(colors.white); c.setLineWidth(3); c.line(x+w*.43,y+h*.41,x+w*.57,y+h*.41)
# labels
c.setFont('Helvetica-Bold',7); c.setFillColor(NAVY)
labels=[('Epiglottis',.65,.96,.82,.96),('Thyroid cartilage',.83,.56,.99,.56),('Arytenoid',.64,.46,.81,.47),('Cricoid',.70,.10,.91,.10)]
for text,sx,sy,ex,ey in labels:
c.setStrokeColor(GREY); c.setLineWidth(.5); c.line(x+w*sx,y+h*sy,x+w*ex,y+h*ey); c.drawString(x+w*ex+2,y+h*ey-2,text)
c.restoreState()
class Schematic(Flowable):
def __init__(self,w=8*cm,h=8.6*cm): Flowable.__init__(self); self.width=w; self.height=h
def wrap(self,aw,ah): return self.width,self.height
def draw(self): draw_larynx_schematic(self.canv,0,0,self.width,self.height)
def section_title(story,num,title,sub=None):
story.append(P(f'<font color="#008C95">{num:02d}</font> {title}','H1x'))
if sub: story.append(P(sub,'Bodyx'))
doc=SimpleDocTemplate(OUT,pagesize=A4,rightMargin=1.5*cm,leftMargin=1.5*cm,topMargin=1.45*cm,bottomMargin=1.65*cm)
story=[]
# Cover
story.append(Spacer(1,1.0*cm))
story.append(P('THE','CoverSub'))
story.append(P('LARYNX','CoverTitle'))
story.append(P('Anatomy through form, function and clinical correlations','CoverSub'))
story.append(Spacer(1,.3*cm))
story.append(Schematic(10*cm,10*cm))
story.append(Spacer(1,.2*cm))
story.append(P('<font color="#F2B84B"><b>MBBS Fourth Year</b></font> | Exam-focused visual learning notes','CoverSub'))
story.append(PageBreak())
# Roadmap
section_title(story,1,'Orientation and roadmap','Know the larynx as an airway valve, a sound generator and a landmark-rich surgical region.')
story.append(Callout('One-line definition','The larynx is a hollow musculoligamentous structure with a cartilaginous framework. It connects the pharynx to the trachea, protects the lower airway and produces voice.'))
story.append(Spacer(1,.25*cm))
story.append(img('larynx_relation.png',17*cm,7.0*cm)); story.append(P('Figure: relationship of the larynx to the pharynx and trachea. Source: Gray’s Anatomy for Students, Fig. 8.212.','Caption'))
story.append(Spacer(1,.2*cm))
story.append(box_table([['At a glance','High-yield statement'],['Extent','Approximately C3-C6 in adults; suspended from hyoid, continuous inferiorly with trachea.'],['Three jobs','Airway protection during swallowing, phonation, and maintenance of a patent airway.'],['Framework','3 unpaired cartilages + 3 paired cartilages + membranes/ligaments + intrinsic muscles.'],['Exam anchor','The <b>cricoid</b> is the only complete cartilaginous ring around the airway.']], [4.0*cm,13.6*cm]))
story.append(PageBreak())
# framework
section_title(story,2,'Cartilaginous framework','Learn the skeleton first. Muscles and membranes only make sense once the joints and landmarks are fixed.')
story.append(box_table([['Unpaired cartilage','Key anatomy and function'],['Thyroid','Largest. Two laminae meet anteriorly at the laryngeal prominence. Superior and inferior horns articulate/attach posteriorly.'],['Cricoid','Signet-ring shaped: narrow anterior arch and broad posterior lamina. Articulates with thyroid and arytenoids.'],['Epiglottis','Leaf-shaped elastic cartilage. Its stalk attaches to thyroid cartilage; helps guard the laryngeal inlet.'],['Paired: arytenoid','Pyramidal cartilages on upper border of cricoid lamina. Vocal and muscular processes are functional handles for vocal folds.'],['Paired: corniculate & cuneiform','Small supportive cartilages in aryepiglottic folds.']], [4.2*cm,13.4*cm]))
story.append(Spacer(1,.2*cm))
t=Table([[img('cricoid.png',8.3*cm,6*cm),P('<b>Why cricoid matters</b><br/>Its complete ring distinguishes it from tracheal rings. The posterior lamina provides attachment for posterior cricoarytenoid muscles.<br/><br/><b>Palpation trail</b><br/>Hyoid → thyroid notch/prominence → cricothyroid membrane → cricoid → tracheal rings.','Bodyx')]],colWidths=[9*cm,8.6*cm]); t.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'MIDDLE'),('BACKGROUND',(1,0),(1,0),PALE),('LEFTPADDING',(1,0),(1,0),12),('RIGHTPADDING',(1,0),(1,0),12)])); story.append(t)
story.append(P('Figure: cricoid cartilage, anterior and posterior views. Source: Gray’s Anatomy for Students, Fig. 8.213.','Caption'))
story.append(PageBreak())
# cavity
section_title(story,3,'Cavity and folds','Map the cavity from inlet to trachea, then locate the folds that define the voice box.')
story.append(box_table([['Region','Boundaries / contents','Why it matters'],['Laryngeal inlet','Anterior: epiglottis; lateral: aryepiglottic folds; posterior: interarytenoid region','Entry to airway; closes in swallowing.'],['Vestibule','Inlet to vestibular (false) folds','Supraglottic compartment.'],['Ventricle','Recess between vestibular and vocal folds','Contains laryngeal saccule.'],['Glottis','True vocal folds plus rima glottidis','Primary phonatory and airflow-control region.'],['Infraglottic cavity','Below vocal folds to lower border of cricoid','Continues as trachea.']], [3.0*cm,9.2*cm,5.4*cm]))
story.append(Spacer(1,.25*cm))
story.append(Callout('Fold distinction','<b>Vestibular folds</b> are superior and mainly protective. <b>Vocal folds</b> are inferior and contain vocal ligament and vocalis. The opening between true vocal folds is the <b>rima glottidis</b>.',GOLD))
story.append(Spacer(1,.18*cm))
story.append(P('<b>Remember:</b> “False above, true below.” The true folds set pitch and voice quality by changing length, tension and effective mass.','Bodyx'))
story.append(PageBreak())
# membranes
section_title(story,4,'Membranes, ligaments and joints','A few attachments repeatedly appear in anatomy, airway procedures and viva questions.')
story.append(box_table([['Structure','Attachment / relevance'],['Thyrohyoid membrane','Between hyoid and superior border of thyroid cartilage. Internal laryngeal nerve and superior laryngeal artery pierce it.'],['Cricothyroid membrane (median ligament)','Between thyroid and cricoid. Relatively avascular midline access for emergency cricothyrotomy.'],['Quadrangular membrane','Free inferior border forms vestibular ligament, supporting vestibular folds.'],['Conus elasticus','Free superior edge forms vocal ligament. Extends between cricoid and thyroid/arytenoid framework.'],['Cricothyroid joint','Permits thyroid tilt relative to cricoid: lengthens and tenses vocal folds.'],['Cricoarytenoid joint','Arytenoid rotation/gliding changes rima glottidis: abduction and adduction.']], [5.0*cm,12.6*cm]))
story.append(Spacer(1,.3*cm)); story.append(Callout('Procedure landmark','Cricothyrotomy is performed through the cricothyroid membrane, inferior to the thyroid cartilage and superior to the cricoid cartilage. Do not confuse this with a definitive tracheostomy.',RED))
story.append(PageBreak())
# muscles
section_title(story,5,'Intrinsic muscles: movement becomes voice','Approach muscles by action. Start with one life-saving fact: posterior cricoarytenoid opens the airway.')
story.append(box_table([['Muscle','Main action','Memory hook'],['Posterior cricoarytenoid','<b>Abducts</b> vocal folds','Only abductor: “PCA = Patent Cord Airway.”'],['Lateral cricoarytenoid','Adducts vocal folds','Closes intermembranous rima.'],['Transverse & oblique arytenoids','Adduct arytenoids','Close posterior rima.'],['Cricothyroid','Tenses and lengthens vocal folds','Raises pitch.'],['Thyroarytenoid / vocalis','Relaxes; fine-tunes tension','Adjusts tone and quality.'],['Aryepiglottic / thyroepiglottic','Narrow / widen laryngeal inlet','Sphincteric inlet control.']], [4.6*cm,7.0*cm,6.0*cm]))
story.append(Spacer(1,.25*cm))
story.append(Callout('Function loop','<b>Quiet inspiration:</b> PCA abducts folds. <b>Phonation:</b> adductors approximate folds while expiratory airflow vibrates them. <b>High pitch:</b> cricothyroid increases tension. <b>Swallowing:</b> inlet is protected through elevation and sphincteric closure.'))
story.append(PageBreak())
# nerve
section_title(story,6,'Nerve supply: the most tested pattern','All laryngeal motor and sensory innervation comes through branches of vagus nerve (CN X).')
story.append(img('innervation.png',15.6*cm,9.0*cm)); story.append(P('Figure: laryngeal innervation. Source: Gray’s Anatomy for Students, Fig. 8.231.','Caption'))
story.append(Spacer(1,.15*cm))
story.append(box_table([['Nerve','Motor','Sensory','Lesion clue'],['Internal laryngeal nerve (internal branch of SLN)','None','Above vocal folds','Reduced supraglottic sensation and impaired cough reflex.'],['External laryngeal nerve (external branch of SLN)','Cricothyroid only','None','Weak high-pitched voice; trouble projecting voice.'],['Recurrent laryngeal nerve (continues as inferior laryngeal)','All other intrinsic muscles','Below vocal folds','Hoarseness; bilateral injury can compromise airway.']], [4.8*cm,4.3*cm,3.9*cm,5.0*cm]))
story.append(Spacer(1,.2*cm)); story.append(Callout('The golden rule','<b>“All intrinsic muscles except cricothyroid are recurrent laryngeal.”</b> Sensory supply divides at the vocal folds: internal laryngeal above, recurrent laryngeal below.',GOLD))
story.append(PageBreak())
# vessels lymph
section_title(story,7,'Vessels and lymphatics','Use the vocal folds as a watershed line for lymphatic drainage and laryngeal cancer spread.')
story.append(box_table([['System','High-yield anatomy'],['Arterial supply','Superior laryngeal artery from superior thyroid artery enters with internal laryngeal nerve via thyrohyoid membrane. Inferior laryngeal artery derives from inferior thyroid artery.'],['Venous drainage','Superior laryngeal veins → superior thyroid vein → internal jugular vein. Inferior laryngeal veins → inferior thyroid veins → left brachiocephalic vein.'],['Lymph above vocal folds','Follows superior laryngeal vessels to upper deep cervical nodes near carotid bifurcation.'],['Lymph below vocal folds','To lower deep cervical/paratracheal nodes associated with inferior thyroid vessels, cricothyroid region and upper trachea.'],['Clinical contrast','True vocal cords have relatively sparse lymphatic drainage, so early glottic lesions may present with hoarseness before nodal disease.']], [4.5*cm,13.1*cm]))
story.append(Spacer(1,.2*cm)); story.append(img('veins.png',13.5*cm,5.8*cm)); story.append(P('Figure: venous drainage of larynx. Source: Gray’s Anatomy for Students, Fig. 8.230.','Caption'))
story.append(PageBreak())
# clinical
section_title(story,8,'Clinical anatomy that earns marks','Relate every anatomical fact to the symptom or procedure it explains.')
story.append(box_table([['Scenario','Anatomical explanation'],['Unilateral RLN palsy','One vocal fold remains paramedian. Patient has hoarseness and a breathy voice due to poor glottic closure.'],['Bilateral RLN palsy','Loss of abduction, particularly PCA action, risks a narrowed airway and stridor.'],['External laryngeal nerve injury','Cricothyroid palsy reduces ability to tense folds: weak, fatigable voice and loss of high pitch.'],['Aspiration risk','Loss of internal laryngeal sensation weakens protective cough and laryngeal reflexes.'],['Emergency airway','Cricothyroid membrane provides rapid anterior access below thyroid cartilage.'],['Thyroid surgery','RLN is at risk close to inferior thyroid artery and posteromedial thyroid lobe.']], [5.0*cm,12.6*cm]))
story.append(Spacer(1,.25*cm)); story.append(Callout('Viva prompt','<b>Question:</b> Name the only abductor of the vocal folds. <b>Answer:</b> Posterior cricoarytenoid, supplied by the recurrent laryngeal nerve.',RED))
story.append(PageBreak())
# recall
section_title(story,9,'Rapid revision and self-test','Use active recall before looking at the answers.')
story.append(P('<b>Five 30-second prompts</b>','H2x'))
for q in ['Name the three unpaired laryngeal cartilages.','Which cartilage is a complete airway ring?','What forms the true vocal fold?','Which nerve supplies cricothyroid?','What is the sensory division at the level of vocal folds?']:
story.append(bullet(q))
story.append(Spacer(1,.2*cm)); story.append(P('<b>Answers</b>','H2x'))
story.append(P('1. Thyroid, cricoid and epiglottis. 2. Cricoid. 3. Vocal ligament with vocalis muscle, covered by mucosa. 4. External branch of superior laryngeal nerve. 5. Internal laryngeal nerve above; recurrent/inferior laryngeal nerve below.','Bodyx'))
story.append(Spacer(1,.18*cm)); story.append(P('<b>Mnemonic board</b>','H2x'))
story.append(box_table([['Mnemonic','Meaning'],['PCA = “Posterior Cord Airway”','Posterior cricoarytenoid is the sole vocal-fold abductor.'],['“CT is the exception”','Cricothyroid is the only intrinsic laryngeal muscle not supplied by RLN.'],['“Above = internal; below = recurrent”','Sensory supply relative to true vocal folds.'],['“Cricoid completes the circle”','Only complete cartilaginous ring around the airway.']], [6.2*cm,11.4*cm]))
story.append(Spacer(1,.3*cm)); story.append(Callout('Last-minute hierarchy','Framework → cavity/folds → joints and membranes → muscle action → nerve rule → clinical lesion. If you can tell this story, you can answer most short notes and viva questions.'))
story.append(PageBreak())
# Sources
section_title(story,10,'Sources and image credits','Prepared as study material, not a substitute for dissection, clinical teaching or local examination guidance.')
story.append(P('<b>Primary anatomy source</b>','H2x'))
story.append(P('Drake RL, Vogl W, Mitchell AWM. <i>Gray’s Anatomy for Students</i>. Larynx chapter, pp. 1192-1208 in the indexed text. Used for the laryngeal framework, relationships, vessels, lymphatics and innervation.','Bodyx'))
story.append(P('<b>Supplementary current reference</b>','H2x'))
story.append(P('StatPearls. Anatomy, Head and Neck: Cervical, Respiratory, Larynx, and Cricoarytenoid; Anatomy, Head and Neck: Laryngeal Nerves. NCBI Bookshelf. Accessed October 2026.','Bodyx'))
story.append(P('<b>Image credits</b>','H2x'))
story.append(P('All embedded anatomical images are textbook figures from <i>Gray’s Anatomy for Students</i>, delivered through the medical textbook library: Fig. 8.212 (relationships), Fig. 8.213 (cricoid cartilage), Fig. 8.230 (venous drainage), Fig. 8.231 (innervation). The cover schematic is an original educational diagram created for this PDF.','Bodyx'))
story.append(Spacer(1,.3*cm)); story.append(Callout('Evidence note','A recent systematic review highlights that recurrent laryngeal nerve anatomy has clinically relevant variations. For routine undergraduate anatomy, learn the standard courses first: right RLN loops under right subclavian artery; left RLN loops under the arch of aorta.'))
doc.build(story,canvasmaker=PageNumberCanvas)
print(OUT)
python "/tmp/workspace/larynx-anatomy-pdf/src/create_larynx_pdf.py" && ls -lh "/tmp/workspace/larynx-anatomy-pdf/output/Larynx_Anatomy_MBBS_4th_Year.pdf"
python - <<'PY' from pypdf import PdfReader p='/tmp/workspace/larynx-anatomy-pdf/output/Larynx_Anatomy_MBBS_4th_Year.pdf' r=PdfReader(p) print('pages',len(r.pages)) for i,page in enumerate(r.pages): t=page.extract_text() or '' print(i+1,len(t),repr(t[:110])) PY
Larynx Anatomy MBBS 4th Year
PDF Document · PDF