fix(reader): optimize rendering performance, prevent browser freeze on large technical books
- extract_outline_entries: filter PDF bookmarks by depth and major structural headings, avoiding 550 micro-snippets - renderContinuousBook: load current chapter and seamlessly append next chapters on scroll (virtual continuous) instead of dumping all chapters into DOM synchronously - renderInteractiveSpans: use native HTML template fragment instead of new DOMParser per paragraph - event delegation: handle word clicks on readerContentBody instead of adding hundreds of thousands of individual DOM event listeners - consolidate existing CCNA book in DB into 68 clean chapters
This commit is contained in:
1 parent
46d6973365
commit
c4a6edee37
3 files changed
+70
-38
No files matched your search
+41
-8
@@ -513,15 +513,17 @@ CLI_PROMPT_RE = re.compile(
|
||||
|
||||
def extract_outline_entries(outline, reader) -> List[Tuple[str, int]]:
|
||||
"""
|
||||
Recursively extracts bookmarks / outline entries from PDF.
|
||||
Returns a list of (title, 0-indexed page_number).
|
||||
Recursively extracts bookmarks / outline entries from PDF respecting hierarchy depth.
|
||||
For books with deep trees (Parts -> Chapters -> Sections -> Subsections),
|
||||
prioritizes major structural headings and prevents fragmenting chapters into
|
||||
hundreds of tiny 1-page snippets.
|
||||
"""
|
||||
entries: List[Tuple[str, int]] = []
|
||||
raw_entries: List[Tuple[str, int, int]] = []
|
||||
|
||||
def _traverse(items):
|
||||
def _traverse(items, depth=0):
|
||||
for item in items:
|
||||
if isinstance(item, list):
|
||||
_traverse(item)
|
||||
_traverse(item, depth + 1)
|
||||
else:
|
||||
try:
|
||||
title = getattr(item, "title", None) or (item.get("/Title") if hasattr(item, "get") else None)
|
||||
@@ -529,13 +531,44 @@ def extract_outline_entries(outline, reader) -> List[Tuple[str, int]]:
|
||||
if title and page_num is not None:
|
||||
clean_title = str(title).strip()
|
||||
if clean_title:
|
||||
entries.append((clean_title, int(page_num)))
|
||||
raw_entries.append((clean_title, int(page_num), depth))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if outline:
|
||||
_traverse(outline)
|
||||
return entries
|
||||
_traverse(outline, 0)
|
||||
|
||||
if not raw_entries:
|
||||
return []
|
||||
|
||||
# If total entries is reasonable (<= 50), return all
|
||||
if len(raw_entries) <= 50:
|
||||
return [(t, p) for t, p, _ in raw_entries]
|
||||
|
||||
# Check depth distribution
|
||||
d0_entries = [e for e in raw_entries if e[2] == 0]
|
||||
d1_entries = [e for e in raw_entries if e[2] <= 1]
|
||||
|
||||
# If top-level (depth 0) already has >= 10 chapters, use depth 0
|
||||
if len(d0_entries) >= 10:
|
||||
return [(t, p) for t, p, _ in d0_entries]
|
||||
|
||||
# Otherwise, if depth <= 1 gives a good number of chapters (10 to 80), use depth <= 1
|
||||
if len(d1_entries) <= 80:
|
||||
return [(t, p) for t, p, _ in d1_entries]
|
||||
|
||||
# If still too many entries (> 80), filter by major chapter keywords
|
||||
major_re = re.compile(
|
||||
r"^(cover|title\s+page|copyright|contents|introduction|your\s+study\s+plan|"
|
||||
r"part\s+[0-9ivxlcdm]+|chapter\s+\d+|appendix\s+[a-z]|glossary|index)",
|
||||
re.IGNORECASE
|
||||
)
|
||||
major_entries = [e for e in d1_entries if major_re.match(e[0])]
|
||||
if len(major_entries) >= 10:
|
||||
return [(t, p) for t, p, _ in major_entries]
|
||||
|
||||
return [(t, p) for t, p, _ in d1_entries[:80]]
|
||||
|
||||
|
||||
|
||||
def clean_page_lines(raw_pages_text: List[str]) -> List[str]:
|
||||
|
||||
Reference in new issue
Block a user