feat(reader): in-book search for PDFs
PDFs have fully searchable text (pdf.js text layer) — the previous
reflowable-only gate existed only because foliate's generic search
needs DOM documents that PDF sections don't provide. This adds a PDF
pipeline alongside it:
- Fork d065495 exposes the pdf.js document proxy as book.pdf so the
host can drive text extraction directly.
- New web/src/reader/pdf-search.ts: extractPdfPages() pulls each
page's textContent with item geometry (progress-reported, cached
after first search). PDF text items often omit inter-word spaces
(gaps are positional), so pages are joined gap-aware — baseline
changes, hasEOL, or horizontal gaps past a font-size threshold
become spaces — recording a char→item map. searchPdfPages() does
case-insensitive matching over the joined text and maps each hit
back to the page-fraction rects of the items it spans, with
ellipsized pre/match/post excerpts. Pure functions, unit-sanity
checked (cross-item 'brave new' → two rects).
- runSearch branches: EPUB keeps foliate's DOM search; PDFs search
the extracted pages, group hits per page ('Page 12'), and render
on-page hit rectangles through the existing fraction-rect overlay
(addRectAnnotation) — which re-render automatically when pages
revisit, same as highlights. Clearing the query removes them.
- Results navigate by page index; the 🔍 button and '/' shortcut now
appear for PDFs too (comics remain without searchable text).
This commit is contained in:
@@ -246,7 +246,7 @@ templ ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) {
|
||||
<h1 class="text-base sm:text-lg font-semibold hidden sm:block sm:truncate">{ metadata.Title }</h1>
|
||||
<div class="flex items-center gap-1">
|
||||
<button @click="addBookmark()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Bookmark this position (b)">🏷️</button>
|
||||
<button x-show="!isFixedLayout" @click="toggleSearch()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Search (/)">🔍</button>
|
||||
<button x-show="!isFixedLayout || isPDF" @click="toggleSearch()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Search (/)">🔍</button>
|
||||
<button @click="toggleBookmarks()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Annotations">📝</button>
|
||||
<button @click="toggleSettings()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Settings (s)">Aa</button>
|
||||
</div>
|
||||
@@ -541,10 +541,10 @@ templ ReaderSearchDrawer() {
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary)" x-text="group.label"></div>
|
||||
<div class="space-y-1">
|
||||
<template x-for="item in group.items" :key="item.cfi">
|
||||
<template x-for="(item, si) in group.items" :key="gi + '-' + si">
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="goToSearchResult(item.cfi)"
|
||||
@click.prevent="goToSearchResult(item)"
|
||||
class="search-result"
|
||||
>
|
||||
<span x-text="item.pre"></span><mark x-text="item.match"></mark><span x-text="item.post"></span>
|
||||
|
||||
Reference in New Issue
Block a user