dsh-book-reader 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +58 -68
- package/README.zh-CN.md +91 -0
- package/SKILL.md +22 -4
- package/cordis.patch.yml +5 -3
- package/lib/client.js +1140 -209
- package/lib/core/README.md +47 -22
- package/lib/core/pdf.js +564 -35
- package/lib/index.js +550 -20
- package/package.json +12 -3
package/lib/core/README.md
CHANGED
|
@@ -38,8 +38,9 @@ export async function getInfo(path)
|
|
|
38
38
|
// outline: [{ title, pdf, printed, depth }] }
|
|
39
39
|
|
|
40
40
|
export async function getPageText(path, pdfPage) // -> string
|
|
41
|
-
export async function searchPdf(path, query, opts) // opts: {ignoreCase=true, limit=200, contextChars=80}
|
|
42
|
-
// -> { hits: [{ pdf, printed, indexOnPage, snippet }], pages: <
|
|
41
|
+
export async function searchPdf(path, query, opts) // opts: {ignoreCase=true, limit=200, contextChars=80, pdfPages?}
|
|
42
|
+
// -> { hits: [{ pdf, printed, indexOnPage, snippet }], pages: <pages with hits>,
|
|
43
|
+
// mode: 'phrase' | 'compact' | 'terms' }
|
|
43
44
|
export async function closePdf(path) // optional cache eviction -> boolean
|
|
44
45
|
|
|
45
46
|
// lib/core/render.js
|
|
@@ -91,26 +92,48 @@ accepted). Invalid pages throw a `RangeError` with
|
|
|
91
92
|
|
|
92
93
|
### `searchPdf(path, query, opts)`
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
Normalized text search (not a regex engine). The query is cleaned first: trimmed,
|
|
96
|
+
a whole-query wrapper pair of `"…"`, `'…'`, `“…”`, `‘…’`, `` `…` `` or `$…$` is
|
|
97
|
+
stripped, and whitespace is collapsed. Both the query and each page's text are
|
|
98
|
+
then NFKC-normalized per code point (math alphanumerics and ligatures fold to
|
|
99
|
+
ASCII), soft hyphens are removed, and all dash variants are deleted (with a
|
|
100
|
+
following space when present), so `com- plex` matches `complex` and
|
|
101
|
+
`finite-dimensional` matches `finite dimensional`.
|
|
102
|
+
|
|
103
|
+
Matching is tried over the whole document in order; the first pass with any hit
|
|
104
|
+
decides the result and `mode`:
|
|
105
|
+
|
|
106
|
+
1. `phrase` — literal match on the normalized text; `ignoreCase` defaults to
|
|
107
|
+
`true`.
|
|
108
|
+
2. `compact` — only when no phrase matched: spaces are removed from both sides,
|
|
109
|
+
so `L(V)` finds `ℒ (𝑉)` and `T∈L(V)` finds `𝑆 , 𝑇 ∈ ℒ (𝑉)`.
|
|
110
|
+
3. `terms` — only when neither matched: every letter/digit token of the query
|
|
111
|
+
(length ≥2, at most 8) must occur somewhere on the page (AND); at most one
|
|
112
|
+
hit per page.
|
|
113
|
+
|
|
114
|
+
Hits rank **body pages first** (numeric `printed`) in page/offset order, then
|
|
115
|
+
front matter (roman labels) in order, with at most 3 front-matter hits while the
|
|
116
|
+
overall `limit` is applied. `pages` counts every matched page in the active pass
|
|
117
|
+
before ranking/limit. `indexOnPage` is a UTF-16 offset into the original
|
|
118
|
+
`getPageText()` text, and snippets are cut from that original text (surrogate
|
|
119
|
+
pairs preserved).
|
|
98
120
|
|
|
99
|
-
* `hits` are ordered by physical page, then position on the page.
|
|
100
|
-
* `printed` is the printed label for that page (`273` for PDF page 293), or a
|
|
101
|
-
front-matter string / `null`.
|
|
102
|
-
* `indexOnPage` is a UTF-16 offset into the **same normalised text** that
|
|
103
|
-
`getPageText()` returns for that page.
|
|
104
121
|
* `snippet` is at most about `2 * contextChars + query.length` characters, with
|
|
105
122
|
leading/trailing `…` when the hit is mid-page.
|
|
106
|
-
* `limit` caps the
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
* `limit` caps the returned hits (default 200); the scan continues to the last
|
|
124
|
+
page so `pages` is accurate.
|
|
125
|
+
* `pdfPages` (optional) is an array of 1-based **physical** pages to scan; the
|
|
126
|
+
tool maps printed page ranges to it. `pages` then counts matches inside that
|
|
127
|
+
range only. Out-of-document pages throw a `RangeError` with
|
|
128
|
+
`code = 'PDF_PAGE_OUT_OF_RANGE'`.
|
|
110
129
|
* `contextChars` defaults to 80.
|
|
130
|
+
* The analysis above is one `await` per page, so the event loop stays responsive.
|
|
131
|
+
A 410-page phrase search takes roughly 0.8–1.1 s; a query that also runs the
|
|
132
|
+
fallbacks stays around the same order.
|
|
111
133
|
|
|
112
|
-
An empty
|
|
113
|
-
throw
|
|
134
|
+
An empty query after cleaning (including quotes or `---`) throws `TypeError`;
|
|
135
|
+
queries longer than 1000 characters throw as well. Invalid
|
|
136
|
+
`limit`/`contextChars`/`pdfPages` throw `RangeError`.
|
|
114
137
|
|
|
115
138
|
### `closePdf(path)`
|
|
116
139
|
|
|
@@ -193,7 +216,7 @@ particular ranged from ~0.67 s to ~1.18 s across runs).
|
|
|
193
216
|
| First PDF call if only `pdf.js` was imported and neither canvas nor pdf.js was loaded yet | **~156 ms** |
|
|
194
217
|
| Warm `getInfo` | **~0.05 ms** median |
|
|
195
218
|
| `getPageText(344)` | **~28-50 ms** |
|
|
196
|
-
| `searchPdf` over all 410 pages (`"singular value decomposition"`) | **~0
|
|
219
|
+
| `searchPdf` over all 410 pages (`"singular value decomposition"`) | **~1.0 s** median; 0.83–1.1 s observed (phrase pass) |
|
|
197
220
|
| `renderPageToPng(344, scale 2)` | **~26 ms** median warm (22-43 ms) |
|
|
198
221
|
| `renderPagesToPng([344,345,346], scale 2)` | **~66 ms** median (63-77 ms) |
|
|
199
222
|
| Cropped render (`0,0,1,0.55`, scale 2) | **~18-27 ms** |
|
|
@@ -247,10 +270,12 @@ in one session. Four cached books plus a long render session should stay in the
|
|
|
247
270
|
7. **Scale and crop are validated, not clamped.** `scale` outside 1..3 throws;
|
|
248
271
|
crop fractions outside the page throw. Validate in the tool schema too so
|
|
249
272
|
the model gets a useful message.
|
|
250
|
-
8. **Search is
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
273
|
+
8. **Search is normalized text matching, not regex.** Quotes/`$…$` are stripped,
|
|
274
|
+
NFKC folds math alphanumerics and ligatures, dashes are dropped (so line-break
|
|
275
|
+
hyphenation matches), then a space-insensitive pass and a keyword-AND pass are
|
|
276
|
+
tried only when the literal phrase has no hits (`mode` reports which one won).
|
|
277
|
+
Body pages rank before front matter and front-matter hits are capped at 3, so
|
|
278
|
+
`pages` can exceed `hits.length`. Expect ~1 s for a 410-page book.
|
|
254
279
|
9. **Page labels come from the PDF, not from visible text.** Some books have
|
|
255
280
|
duplicate/odd roman labels (LADR's PDF page 4 is labelled `ii` by pdf.js).
|
|
256
281
|
Only numeric labels drive `printedOffset`; numeric deltas are taken by mode.
|