diff --git a/extract.py b/extract.py index e76137f..14c3192 100644 --- a/extract.py +++ b/extract.py @@ -192,10 +192,17 @@ def render_page_png( def toc_sections(doc: fitz.Document, level: int) -> list[dict]: - """Return unique page boundaries from the PDF outline at one level.""" + """Return unique page boundaries and ancestor titles from the PDF outline.""" sections: list[dict] = [] seen_pages: set[int] = set() + parents: list[str] = [] for toc_level, title, page in doc.get_toc(): + title = title.strip() + if not title: + continue + parents = parents[: toc_level - 1] + ancestors = parents.copy() + parents.append(title) if toc_level != level or not title.strip(): continue page = max(1, min(page, doc.page_count)) @@ -205,12 +212,15 @@ def toc_sections(doc: fitz.Document, level: int) -> list[dict]: if page in seen_pages: continue seen_pages.add(page) - sections.append({"title": title.strip(), "start_page": page}) + sections.append( + {"title": title, "parents": ancestors, "start_page": page} + ) if sections: return sections # Keep PDFs without bookmarks usable. This fallback only recognizes # numbered headings and is deliberately limited to one boundary per page. + heading_path: list[str] = [] for page_index, page in enumerate(doc, start=1): text = clean_page_text(page.get_text()) if is_toc_page(text): @@ -220,14 +230,23 @@ def toc_sections(doc: fitz.Document, level: int) -> list[dict]: if not match: continue number, title = match.groups() - if number.count(".") + 1 != level: + heading_level = number.count(".") + 1 + heading_path = heading_path[: heading_level - 1] + heading_path.append(title.strip()) + if heading_level != level: continue title = title.strip() if not title or not title[0].isalpha() or TOC_LINE.search(title): continue if page_index not in seen_pages: seen_pages.add(page_index) - sections.append({"title": title, "start_page": page_index}) + sections.append( + { + "title": title, + "parents": heading_path[:-1], + "start_page": page_index, + } + ) break return sections @@ -322,7 +341,7 @@ def convert( { "num": f"{index:02d}", "title": entry["title"], - "slug": f"{index:02d}-{slugify(entry['title'])}", + "slug": f"{index:02d}-{slugify(' - '.join((*entry['parents'], entry['title'])))}", "pages": pages, } )