@embedpdf/plugin-search 3.0.0-next.13 → 3.0.0-next.15

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/search.plugin.ts"],"sourcesContent":["import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { type PointIn, type TextQuad, applyPoint, boundsOfRects, pageGeometry, textQuadBounds } from '@embedpdf/core-geometry';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport { EngineError, EngineErrorCode } from '@embedpdf/engine-core/runtime';\nimport type {\n PdfQuad,\n PdfRect,\n SearchMatch,\n SearchMode,\n SearchQuery,\n SearchSlice,\n} from '@embedpdf/engine-core/runtime';\n\nimport type {\n SearchAction,\n SearchCapability,\n SearchHit,\n SearchPluginConfig,\n SearchRevealOptions,\n SearchState,\n} from './types';\n\nconst EMPTY: SearchHit[] = [];\n\n/** The browser find-bar feel: hit at the top-middle, no zoom change. */\nconst DEFAULT_REVEAL: SearchRevealOptions = { anchor: { y: 0.35 }, behavior: 'smooth' };\n\n/** Local engines reject a denied scope before the worker; cloud maps 403. */\nconst isPermissionDenied = (err: unknown): boolean =>\n (err instanceof Error && err.name === 'PermissionDenied') ||\n EngineError.is(err, EngineErrorCode.Forbidden);\n\n/**\n * The search capability: a client-driven cursor loop over the engine's\n * budgeted slices. One generation counter arbitrates: a new `search()` (or\n * `clear()`, or a document mutation via `rerun()`) supersedes the running\n * loop, which aborts its in-flight slice and stops dispatching.\n *\n * Rect conversion happens HERE, once per slice: the engine's PDF-space\n * line rects → content space through the same page geometry the selection\n * plugin uses, so search highlights and selection highlights are pixel-twins.\n *\n * Stage integration is optional and additive: with a Stage present the scan\n * starts at the current page (viewport-first — the hits the user is looking\n * at arrive in the first slice) and `next()`/`prev()`/`goTo()` reveal the\n * hit's page. Without one, navigation still works; the host scrolls.\n */\nexport function createSearchCapability(\n ctx: PluginContext<SearchState, SearchAction>,\n config: SearchPluginConfig = {},\n): SearchCapability {\n let generation = 0;\n let inflight: { abort(reason?: unknown): void } | null = null;\n\n interface PageConverter {\n pageIndex: number;\n toQuad: (q: PdfQuad) => TextQuad;\n }\n\n function hitsFromSlice(slice: SearchSlice): SearchHit[] {\n // zoom = 1: content space is scale-free; the layer applies the page\n // transform at render time (the selection idiom).\n const converters = new Map<PageObjectNumber, PageConverter | null>();\n const converterFor = (pon: PageObjectNumber): PageConverter | null => {\n let conv = converters.get(pon);\n if (conv !== undefined) return conv;\n const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n if (!layout) {\n conv = null; // page vanished mid-search (delete); drop its hits\n } else {\n const { pdfToContent } = pageGeometry(\n { crop: layout.boxes.crop, rotation: layout.rotation, userUnit: layout.userUnit },\n 1,\n );\n // Engine quads carry frame-geometric slot semantics (p1..p4 =\n // upper-start, upper-end, lower-start, lower-end) — a trusted\n // producer, so the y-flip maps corners straight onto their names.\n const toPoint = (pt: { x: number; y: number }) =>\n applyPoint(pdfToContent as never, pt as PointIn<'pdf'>) as { x: number; y: number };\n conv = {\n pageIndex: layout.index,\n toQuad: (q) => ({\n upperStart: toPoint(q.p1),\n upperEnd: toPoint(q.p2),\n lowerStart: toPoint(q.p3),\n lowerEnd: toPoint(q.p4),\n }),\n };\n }\n converters.set(pon, conv);\n return conv;\n };\n\n const hits: SearchHit[] = [];\n for (const match of slice.matches) {\n const conv = converterFor(match.pageObjectNumber);\n if (!conv) continue;\n const segments = match.segments.map((segment) => {\n const quad = conv.toQuad(segment.quad);\n // `rect` is recomputed from the MAPPED quad so the pair can't drift.\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n });\n hits.push({\n pon: match.pageObjectNumber,\n pageIndex: conv.pageIndex,\n charStart: match.charStart,\n charCount: match.charCount,\n segments,\n ...(segments.length\n ? { bounds: boundsOfRects(segments.map((s) => s.rect)) ?? undefined }\n : {}),\n ...(match.snippet ? { snippet: match.snippet } : {}),\n });\n }\n return hits;\n }\n\n /** How a collect() consumer observes the loop — the session dispatches\n * into state, findAll accumulates locally. */\n interface CollectSink {\n /** Called before the first slice AND again on a stale-cursor restart —\n * reset any accumulation. */\n onStart: () => void;\n onSlice: (hits: SearchHit[], scanned: number, total: number) => void;\n /** Superseded / aborted — the loop stops without completing. */\n isCancelled: () => boolean;\n setInflight: (pending: { abort(reason?: unknown): void } | null) => void;\n }\n\n /**\n * THE MECHANISM — one cursor loop over the engine's budgeted slices,\n * shared by the session (`search`) and the session-free service\n * (`findAll`): permission fallback ('full' → 'rects' when snippets are\n * denied, unless the caller pinned a mode), restart-once on a stale\n * cursor, rect conversion per slice. Resolves true on exhaustion, false\n * when cancelled; throws on error.\n */\n async function collect(\n query: SearchQuery,\n opts: { startPage?: PageObjectNumber; mode?: SearchMode },\n sink: CollectSink,\n ): Promise<boolean> {\n const doc = ctx.doc;\n if (!doc) return false;\n\n sink.onStart();\n\n let mode: SearchMode = opts.mode ?? 'full';\n const modePinned = opts.mode !== undefined;\n let cursor: string | undefined;\n let restarted = false;\n for (;;) {\n let slice: SearchSlice;\n try {\n const request = {\n query,\n mode,\n ...(cursor !== undefined\n ? { cursor }\n : opts.startPage !== undefined\n ? { startPage: opts.startPage }\n : {}),\n };\n const pending = doc.search.query(request);\n sink.setInflight(pending);\n slice = await pending;\n } catch (err) {\n if (sink.isCancelled()) return false; // superseded — the abort was ours\n // Snippets denied (no doc.text.copy)? Degrade to rect-only matches\n // instead of failing the whole search — find still works.\n if (!modePinned && mode === 'full' && cursor === undefined && isPermissionDenied(err)) {\n mode = 'rects';\n continue;\n }\n // A stale cursor rejects with InvalidArg — the document changed\n // under the loop, or the engine's session was recycled. Position\n // is lost, the query isn't: restart once from scratch.\n if (cursor !== undefined && !restarted && EngineError.is(err, EngineErrorCode.InvalidArg)) {\n restarted = true;\n cursor = undefined;\n sink.onStart();\n continue;\n }\n throw err;\n }\n if (sink.isCancelled()) return false;\n sink.onSlice(hitsFromSlice(slice), slice.scannedPages, slice.totalPages);\n if (slice.nextCursor === null) {\n sink.setInflight(null);\n return true;\n }\n cursor = slice.nextCursor;\n }\n }\n\n /** THE SESSION — collect() with state as the sink, generation-guarded. */\n async function runSession(query: SearchQuery, startPage?: PageObjectNumber): Promise<void> {\n const gen = ++generation;\n inflight?.abort('superseded');\n inflight = null;\n\n if (!ctx.doc) {\n ctx.dispatch({ type: 'CLEAR' });\n return;\n }\n\n // Viewport-first: begin scanning where the user is looking.\n if (startPage === undefined) {\n const stage = ctx.tryGet(StageToken);\n if (stage) startPage = ctx.document()?.pages[stage.currentPage()]?.pageObjectNumber;\n }\n\n try {\n const complete = await collect(\n query,\n { startPage },\n {\n onStart: () => ctx.dispatch({ type: 'START', query }),\n onSlice: (hits, scanned, total) => ctx.dispatch({ type: 'APPEND', hits, scanned, total }),\n isCancelled: () => gen !== generation,\n setInflight: (pending) => {\n if (gen === generation) inflight = pending;\n },\n },\n );\n if (complete && gen === generation) ctx.dispatch({ type: 'COMPLETE' });\n } catch (err) {\n if (gen !== generation) return;\n ctx.dispatch({ type: 'ERROR', message: err instanceof Error ? err.message : String(err) });\n }\n }\n\n function goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null {\n const { hits } = ctx.getState();\n if (hits.length === 0) return null;\n const wrapped = ((index % hits.length) + hits.length) % hits.length;\n ctx.dispatch({ type: 'SET_ACTIVE', index: wrapped });\n const hit = hits[wrapped];\n // Positioned reveal: the HIT (not just its page) arrives at the anchor —\n // per-call override > plugin config > find-bar default. Zoom never changes.\n const arrival = { ...DEFAULT_REVEAL, ...config.reveal, ...reveal };\n ctx.tryGet(StageToken)?.reveal(hit.pageIndex, {\n rect: hit.bounds,\n anchor: arrival.anchor,\n behavior: arrival.behavior,\n });\n return hit;\n }\n\n const clear = () => {\n generation++;\n inflight?.abort('cleared');\n inflight = null;\n ctx.dispatch({ type: 'CLEAR' });\n };\n\n return {\n // The twin. Search is a pure request, so the VERBS carry no client gate\n // (the engine enforces; the ERROR state reports) — the twin exists for\n // affordance hiding. 'full' composes both capabilities because a snippet\n // reproduces document text; the session's own degradation means plain\n // `canSearch()` is exactly \"would `search()` produce hits\".\n canSearch: (mode) => {\n const sec = ctx.doc?.security;\n if (!sec) return false;\n return sec.allows('doc.text.search') && (mode !== 'full' || sec.allows('doc.text.copy'));\n },\n\n // An empty text is \"stop searching\", not a query — identical to clear().\n search: (query, exec) =>\n query.text.length === 0 ? clear() : void runSession(query, exec?.startPage),\n\n // Replay the stored query verbatim; the rescan starts viewport-first.\n rerun: () => {\n const { query, status } = ctx.getState();\n if (query && status !== 'idle') void runSession(query);\n },\n\n clear,\n\n next: () => goTo(ctx.getState().activeIndex + 1),\n prev: () => goTo(ctx.getState().activeIndex - 1),\n goTo,\n\n query: () => ctx.getState().query,\n\n findAll: async (query, opts = {}) => {\n if (!ctx.doc) return [];\n const all: SearchHit[] = [];\n const signal = opts.signal;\n // Independent lifecycle: its own abort handle, never the session's\n // generation counter — a findAll can neither supersede the user's\n // visible search nor be superseded by it.\n let pending: { abort(reason?: unknown): void } | null = null;\n const onAbort = () => pending?.abort(signal?.reason);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n await collect(\n query,\n { mode: opts.mode },\n {\n onStart: () => {\n all.length = 0;\n },\n onSlice: (hits) => all.push(...hits),\n isCancelled: () => signal?.aborted ?? false,\n setInflight: (p) => {\n pending = p;\n },\n },\n );\n if (signal?.aborted) throw signal.reason ?? new Error('findAll aborted');\n return all;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n },\n\n status: () => ctx.getState().status,\n hits: () => ctx.getState().hits,\n hitCount: () => ctx.getState().hits.length,\n activeIndex: () => ctx.getState().activeIndex,\n activeHit: () => {\n const { hits, activeIndex } = ctx.getState();\n return activeIndex >= 0 && activeIndex < hits.length ? hits[activeIndex] : null;\n },\n hitsForPage: (pon) => {\n const { hits, hitsByPage } = ctx.getState();\n const indices = hitsByPage[pon];\n return indices?.length ? indices.map((i) => hits[i]) : EMPTY;\n },\n progress: () => ctx.getState().progress,\n errorMessage: () => ctx.getState().error,\n };\n}\n","/**\n * Keeps results honest across document mutations. Every confirmed\n * mutation (own or remote) bumps the engine's mutation sequence, which\n * invalidates search cursors AND can change what's findable (redaction,\n * page delete, import). The correct reaction is always the same: re-run\n * the current query from scratch. Reruns are coalesced — a burst of\n * events (multi-part mutation, SSE catch-up) triggers one rescan.\n */\nimport type { EffectContext } from '@embedpdf/core';\n\nimport { SearchToken } from './types';\nimport type { SearchAction, SearchState } from './types';\n\nconst RERUN_DELAY_MS = 250;\n\nexport function registerSearchEffects(ctx: EffectContext<SearchState, SearchAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n const unsubscribe = doc.events.subscribe(() => {\n if (ctx.getState().status === 'idle') return;\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n ctx.get(SearchToken).rerun();\n }, RERUN_DELAY_MS);\n });\n\n ctx.cleanup(() => {\n if (timer !== null) clearTimeout(timer);\n unsubscribe();\n });\n}\n","import type { SearchAction, SearchState } from './types';\n\nexport const initialSearchState = (): SearchState => ({\n query: null,\n status: 'idle',\n hits: [],\n hitsByPage: {},\n activeIndex: -1,\n progress: { scanned: 0, total: 0 },\n error: null,\n});\n\nexport const searchReducer = (state: SearchState, action: SearchAction): SearchState => {\n switch (action.type) {\n case 'START':\n return { ...initialSearchState(), query: action.query, status: 'searching' };\n case 'APPEND': {\n if (action.hits.length === 0) {\n return { ...state, progress: { scanned: action.scanned, total: action.total } };\n }\n const hits = state.hits.concat(action.hits);\n const hitsByPage = { ...state.hitsByPage };\n for (let i = state.hits.length; i < hits.length; i++) {\n const pon = hits[i].pon;\n hitsByPage[pon] = (hitsByPage[pon] ?? []).concat(i);\n }\n return {\n ...state,\n hits,\n hitsByPage,\n // The first hit becomes active (so \"1/N\" reads right) — navigation,\n // not the stream, moves the camera.\n activeIndex: state.activeIndex === -1 ? 0 : state.activeIndex,\n progress: { scanned: action.scanned, total: action.total },\n };\n }\n case 'COMPLETE':\n return { ...state, status: 'complete' };\n case 'ERROR':\n return { ...state, status: 'error', error: action.message };\n case 'SET_ACTIVE':\n return { ...state, activeIndex: action.index };\n case 'CLEAR':\n return initialSearchState();\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { createSearchCapability } from './capability';\nimport { registerSearchEffects } from './effects';\nimport { initialSearchState, searchReducer } from './reducer';\nimport { SearchToken } from './types';\nimport type { SearchAction, SearchCapability, SearchPluginConfig, SearchState } from './types';\n\n/**\n * Document text search over `doc.search` — the engine's budgeted,\n * cursor-resumable slices. Document-scoped; no pointer handling, so it\n * needs no interaction hub. The Stage is optional: when present, scans\n * start at the current page (viewport-first) and hit navigation reveals\n * the hit's page; without it the host owns scrolling.\n */\nexport const searchPlugin = (config?: SearchPluginConfig) =>\n definePlugin<SearchState, SearchAction, SearchCapability>({\n id: 'search',\n token: SearchToken,\n scope: 'document',\n optional: [StageToken],\n initialState: initialSearchState,\n reduce: searchReducer,\n capability: (ctx) => createSearchCapability(ctx, config),\n effects: registerSearchEffects,\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/model.ts","../src/controller.ts","../src/search.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The search session: the query, the hits found so far (also grouped by\n * page), the active hit, and progress. Every function below is a pure\n * transition; the controller applies them with `ctx.state.update`.\n */\nimport { pageRefsEqual, type PageRef, type PluginErrorInfo } from '@embedpdf/core';\nimport type { SearchQuery } from '@embedpdf/engine-core/runtime';\n\nimport type { SearchHit, SearchProgress, SearchStatus } from './contract';\n\nexport interface SearchState {\n /** The query being searched, as the engine matches it. Null when idle. */\n readonly query: SearchQuery | null;\n readonly status: SearchStatus;\n /** The run that owns the session, so a run that lost it (superseded, cleared) leaves it alone. */\n readonly operationId: string | null;\n readonly hits: readonly SearchHit[];\n /** Page object number → the hits on that page; a page's array is replaced only when it gains hits. */\n readonly hitsByPage: Readonly<Record<number, readonly SearchHit[]>>;\n /** Index into `hits`, -1 when no hit is active. */\n readonly activeHitIndex: number;\n readonly progress: SearchProgress;\n readonly error: PluginErrorInfo | null;\n}\n\n/** The progress of no search, one object for every session that hasn't searched a page yet. */\nexport const NO_PROGRESS: SearchProgress = Object.freeze({ pagesSearched: 0, pageCount: 0 });\n\nexport const initialSearchState = (): SearchState => ({\n query: null,\n status: 'idle',\n operationId: null,\n hits: [],\n hitsByPage: {},\n activeHitIndex: -1,\n progress: NO_PROGRESS,\n error: null,\n});\n\n/** A new scan starts: everything from the previous session is dropped. */\nexport const startSession = (\n _state: SearchState,\n query: SearchQuery,\n operationId: string,\n): SearchState => ({ ...initialSearchState(), query, status: 'searching', operationId });\n\nconst sameProgress = (left: SearchProgress, right: SearchProgress): boolean =>\n left.pagesSearched === right.pagesSearched && left.pageCount === right.pageCount;\n\n/**\n * A slice of hits arrived. The first hit becomes active so \"1 of N\" reads\n * right; moving the camera is navigation's job, not the stream's. Progress\n * with the same counts keeps its object: readers, and `onProgressChanged`,\n * see a change only when the counts change.\n */\nexport function appendHits(\n state: SearchState,\n hits: readonly SearchHit[],\n slice: SearchProgress,\n): SearchState {\n const progress = sameProgress(state.progress, slice) ? state.progress : slice;\n if (hits.length === 0) return progress === state.progress ? state : { ...state, progress };\n const hitsByPage: Record<number, readonly SearchHit[]> = { ...state.hitsByPage };\n for (const hit of hits) {\n const pageObjectNumber = hit.page.objectNumber;\n hitsByPage[pageObjectNumber] = [...(hitsByPage[pageObjectNumber] ?? []), hit];\n }\n return {\n ...state,\n hits: [...state.hits, ...hits],\n hitsByPage,\n activeHitIndex: state.activeHitIndex === -1 ? 0 : state.activeHitIndex,\n progress,\n };\n}\n\nexport const completeSession = (state: SearchState): SearchState => ({\n ...state,\n status: 'complete',\n});\n\nexport const cancelSession = (state: SearchState): SearchState => ({\n ...state,\n status: 'cancelled',\n});\n\nexport const failSession = (state: SearchState, error: PluginErrorInfo): SearchState => ({\n ...state,\n status: 'error',\n error,\n});\n\nexport const setActiveHit = (state: SearchState, index: number): SearchState =>\n state.activeHitIndex === index ? state : { ...state, activeHitIndex: index };\n\nexport const clearSession = (state: SearchState): SearchState =>\n state.status === 'idle' ? state : initialSearchState();\n\n/**\n * Where `hit` is in `hits`: the same object, or else the hit that covers the\n * same characters of the same page, so a hit kept from before the search ran\n * again still finds its place. -1 when it isn't there.\n */\nexport function indexOfHit(hits: readonly SearchHit[], hit: SearchHit): number {\n const same = hits.indexOf(hit);\n if (same >= 0) return same;\n return hits.findIndex(\n (candidate) =>\n candidate.start === hit.start &&\n candidate.count === hit.count &&\n pageRefsEqual(candidate.page, hit.page),\n );\n}\n\n/**\n * The pages that have at least one hit, in document order: `pages` is the\n * document's page list, whose order moving a page changes (object numbers\n * don't follow it).\n */\nexport const pagesWithHits = (\n state: SearchState,\n pages: readonly { readonly ref: PageRef }[],\n): readonly PageRef[] =>\n pages\n .filter((page) => (state.hitsByPage[page.ref.objectNumber]?.length ?? 0) > 0)\n .map((page) => page.ref);\n","import {\n PluginError,\n isPluginError,\n toPluginError,\n toPluginErrorInfo,\n type PluginContext,\n type LatestCancellation,\n type PageRef,\n} from '@embedpdf/core';\nimport { boundsOfRects } from '@embedpdf/core-geometry';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport type { SearchQuery, SearchSlice } from '@embedpdf/engine-core/runtime';\nimport type {\n SearchActiveHitChangedEvent,\n SearchCancelledEvent,\n SearchCapability,\n SearchClearedEvent,\n SearchCompletedEvent,\n SearchFailedEvent,\n SearchHit,\n SearchOptions,\n SearchProgressChangedEvent,\n SearchResult,\n SearchRevealOptions,\n SearchSettings,\n SearchStartedEvent,\n} from './contract';\nimport {\n appendHits,\n cancelSession,\n clearSession,\n completeSession,\n failSession,\n indexOfHit,\n pagesWithHits,\n setActiveHit,\n startSession,\n type SearchState,\n} from './model';\n\nconst EMPTY: readonly SearchHit[] = Object.freeze([]);\nconst EMPTY_PAGES: readonly PageRef[] = Object.freeze([]);\n\n/** Reruns after document mutations are coalesced: a burst of events triggers one rescan. */\nconst RERUN_DELAY_MS = 250;\n\n/** How a `collect()` consumer observes the loop: the session updates state, findAll accumulates. */\ninterface CollectSink {\n /** Before the first slice, and again on a stale-cursor restart: reset any accumulation. */\n onStart(): void;\n onSlice(hits: readonly SearchHit[], pagesSearched: number, pageCount: number): void;\n}\n\n/**\n * The search controller. `search` runs on a `latest` lane (a newer search\n * supersedes the older one, which can no longer publish); `findAll` runs on\n * its own signal and never touches the session. Both share `collect`, the\n * one cursor loop over the engine's budgeted slices.\n *\n * The scan's own events (started, completed, cancelled, failed) fire where\n * the scan reaches those points. The progress, active-hit and cleared events\n * are derived from state changes, so they always agree with the getters.\n *\n * The settings are read where they're used, never copied, so\n * `updateSettings({ reveal })` applies to the next move at once.\n */\nexport function createSearchController(ctx: PluginContext<SearchState, SearchSettings>) {\n const settings = ctx.settings();\n const session = ctx.latest('session');\n const started = ctx.events.source<SearchStartedEvent>();\n const progressChanged = ctx.events.source<SearchProgressChangedEvent>();\n const completed = ctx.events.source<SearchCompletedEvent>();\n const cancelled = ctx.events.source<SearchCancelledEvent>();\n const failed = ctx.events.source<SearchFailedEvent>();\n const activeHitChanged = ctx.events.source<SearchActiveHitChangedEvent>();\n const cleared = ctx.events.source<SearchClearedEvent>();\n\n const state = () => ctx.state.get();\n\n ctx.state.onChange(({ previous, next }) => {\n // A slice moves it forward; a new search or clear() puts it back to none.\n if (previous.progress !== next.progress) {\n progressChanged.emit({ ...next.progress, hitCount: next.hits.length });\n }\n if (previous.activeHitIndex !== next.activeHitIndex) {\n activeHitChanged.emit({\n index: next.activeHitIndex,\n hit: next.hits[next.activeHitIndex] ?? null,\n });\n }\n if (previous.status !== 'idle' && next.status === 'idle') cleared.emit({});\n });\n\n // ── projection: engine slice → page-space hits ───────────────────────────\n\n function hitsFromSlice(slice: SearchSlice): SearchHit[] {\n const hits: SearchHit[] = [];\n for (const match of slice.matches) {\n // A page that vanished mid-search (deleted) drops its hits.\n const page = ctx.getPage(match.page);\n if (!page) continue;\n // Segments arrive in page space, ready to draw.\n const segments = match.segments;\n hits.push({\n page: match.page,\n pageIndex: page.index,\n start: match.start,\n count: match.count,\n segments,\n ...(segments.length\n ? { bounds: boundsOfRects(segments.map((segment) => segment.rect)) ?? undefined }\n : {}),\n ...(match.snippet ? { snippet: match.snippet } : {}),\n });\n }\n return hits;\n }\n\n // ── the mechanism ────────────────────────────────────────────────────────\n\n /**\n * One cursor loop over budgeted slices: permission fallback (snippets → none\n * when they are denied, unless pinned), restart-once on a stale cursor,\n * projection per slice. Returns true on exhaustion, false when the signal\n * aborted; throws on a real error (already a PluginError: ctx.doc is guarded).\n */\n async function collect(\n query: SearchQuery,\n options: { from?: PageRef; snippets?: boolean; signal?: AbortSignal },\n sink: CollectSink,\n ): Promise<boolean> {\n const { signal } = options;\n sink.onStart();\n\n let snippets = options.snippets ?? true;\n const snippetsPinned = options.snippets !== undefined;\n let cursor: string | undefined;\n let restarted = false;\n for (;;) {\n if (signal?.aborted) return false;\n let slice: SearchSlice;\n const request = {\n ...query,\n snippets,\n ...(cursor !== undefined\n ? { cursor }\n : options.from !== undefined\n ? { from: options.from }\n : {}),\n };\n try {\n // A scan nobody waits on, so it runs once the views' work is done.\n // Cancelling stops the engine working on the slice, not only the wait for it.\n slice = await ctx.cancellable(\n signal,\n ctx.doc.with({ priority: 'low' }).search.query(request),\n );\n } catch (error) {\n if (signal?.aborted) return false;\n // Snippets denied (no doc.text.copy)? Degrade to matches without them.\n if (\n !snippetsPinned &&\n snippets &&\n cursor === undefined &&\n isPluginError(error, 'permission-denied')\n ) {\n snippets = false;\n continue;\n }\n // A stale cursor rejects with InvalidArg: the document changed under the\n // loop. Position is lost, the query is not: restart once from scratch.\n if (cursor !== undefined && !restarted && isPluginError(error, 'invalid-input')) {\n restarted = true;\n cursor = undefined;\n sink.onStart();\n continue;\n }\n throw error;\n }\n if (signal?.aborted) return false;\n sink.onSlice(hitsFromSlice(slice), slice.pagesSearched, slice.pageCount);\n if (slice.nextCursor === null) return true;\n cursor = slice.nextCursor;\n }\n }\n\n // ── the session ──────────────────────────────────────────────────────────\n\n const viewportFirstPage = (): PageRef | undefined => {\n const stage = ctx.tryGet(StageToken);\n return stage?.getCurrentPage() ?? undefined;\n };\n\n // Async so a refusal or a page that isn't in the document rejects, like any other failure,\n // before the session is touched. The run still starts synchronously, so a newer search\n // supersedes an older one in the order they were called.\n async function search(query: SearchQuery, options: SearchOptions = {}): Promise<SearchResult> {\n if (query.text.length === 0) {\n clear();\n return { status: 'complete', hitCount: 0 };\n }\n ctx.assertAllowed('doc.text.search', 'search');\n const from = options.from === undefined ? viewportFirstPage() : ctx.pageOf(options.from).ref;\n let operationId = '';\n return session\n .run(async (run) => {\n operationId = run.id;\n const complete = await collect(\n query,\n { from, signal: run.signal },\n {\n onStart: () =>\n run.commit(() => {\n ctx.state.update(startSession, query, run.id);\n started.emit({ query });\n }),\n onSlice: (hits, pagesSearched, pageCount) =>\n run.commit(() => ctx.state.update(appendHits, hits, { pagesSearched, pageCount })),\n },\n );\n if (!complete) throw new PluginError('operation-cancelled', 'search', 'search aborted');\n run.commit(() => {\n ctx.state.update(completeSession);\n completed.emit({ hitCount: state().hits.length });\n });\n return { status: 'complete', hitCount: state().hits.length } as SearchResult;\n }, options)\n .catch((error: unknown): SearchResult => {\n if (isPluginError(error, 'instance-closed')) return { status: 'cancelled', hitCount: 0 };\n if (isPluginError(error, 'operation-cancelled')) {\n const why = (error.details as LatestCancellation | undefined)?.reason ?? 'cancelled';\n const reason: SearchCancelledEvent['reason'] =\n why === 'superseded' ? 'superseded' : why === 'cleared' ? 'cleared' : 'cancelled';\n // A superseded or cleared run has already lost the session; a cancelled one keeps its hits.\n if (reason === 'cancelled' && state().operationId === operationId) {\n ctx.state.update(cancelSession);\n }\n cancelled.emit({ reason });\n return {\n status: reason === 'superseded' ? 'superseded' : 'cancelled',\n hitCount: state().hits.length,\n };\n }\n const info = toPluginErrorInfo(toPluginError('search', error));\n if (state().operationId === operationId) ctx.state.update(failSession, info);\n failed.emit({ error: info });\n throw error;\n });\n }\n\n function clear(): void {\n session.cancel('cleared');\n ctx.state.update(clearSession);\n }\n\n function goToHit(target: SearchHit | number, options?: SearchRevealOptions): SearchHit | null {\n const { hits } = state();\n if (hits.length === 0) return null;\n const index =\n typeof target === 'number'\n ? ((target % hits.length) + hits.length) % hits.length\n : indexOfHit(hits, target);\n if (index < 0) return null;\n ctx.state.update(setActiveHit, index);\n reveal(hits[index], options);\n return hits[index];\n }\n\n function reveal(hit: SearchHit, options?: SearchRevealOptions): void {\n // Positioned reveal: the hit (not just its page) arrives at the anchor —\n // the call's options over the `reveal` setting. Zoom never changes.\n const arrival = { ...settings.get().reveal, ...options };\n ctx.tryGet(StageToken)?.reveal(hit.page, {\n rect: hit.bounds,\n anchor: arrival.anchor,\n behavior: arrival.behavior,\n });\n }\n\n /**\n * One page's hits, by the page's ref or index. A page that isn't in the\n * document has none: a layer may read while its page is being deleted.\n */\n const hitsOn = (page: PageRef | number): readonly SearchHit[] => {\n const found = ctx.getPage(page);\n return found ? (state().hitsByPage[found.ref.objectNumber] ?? EMPTY) : EMPTY;\n };\n\n const api: SearchCapability = {\n ...settings.api,\n\n // The twin of search() and findAll(), which refuse without doc.text.search.\n // Snippets need doc.text.copy too, but a search without it still runs: its\n // matches come without snippets (see collect).\n canSearch: (options) =>\n ctx.allows('doc.text.search') && (!options?.snippets || ctx.allows('doc.text.copy')),\n\n search,\n refresh: (options) => {\n const { query, status } = state();\n if (!query || status === 'idle') return Promise.resolve({ status: 'complete', hitCount: 0 });\n return search(query, options);\n },\n cancel: () => session.cancel('cancelled'),\n clear,\n\n nextHit: (options) => goToHit(state().activeHitIndex + 1, options),\n previousHit: (options) => goToHit(state().activeHitIndex - 1, options),\n goToHit,\n revealActiveHit: (options) => {\n const hit = api.getActiveHit();\n if (hit) reveal(hit, options);\n },\n\n getQuery: () => state().query,\n getStatus: () => state().status,\n listHits: (filter) => (filter?.page === undefined ? state().hits : hitsOn(filter.page)),\n getHitCount: (page) => (page === undefined ? state().hits : hitsOn(page)).length,\n listPagesWithHits: () =>\n state().hits.length ? pagesWithHits(state(), ctx.document()?.pages ?? []) : EMPTY_PAGES,\n getActiveHitIndex: () => state().activeHitIndex,\n getActiveHit: () => state().hits[state().activeHitIndex] ?? null,\n getProgress: () => state().progress,\n getError: () => state().error,\n\n // ── the service ──────────────────────────────────────────────────────\n findAll: async (query, options = {}) => {\n ctx.assertAllowed('doc.text.search', 'findAll');\n const all: SearchHit[] = [];\n const complete = await collect(\n query,\n {\n ...(options.snippets !== undefined ? { snippets: options.snippets } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n },\n {\n onStart: () => {\n all.length = 0;\n },\n onSlice: (hits) => all.push(...hits),\n },\n );\n if (!complete) throw new PluginError('operation-cancelled', 'search', 'findAll aborted');\n return all;\n },\n\n onStarted: started.on,\n onProgressChanged: progressChanged.on,\n onCompleted: completed.on,\n onCancelled: cancelled.on,\n onFailed: failed.on,\n onActiveHitChanged: activeHitChanged.on,\n onCleared: cleared.on,\n };\n\n return {\n api,\n connect() {\n // Every confirmed mutation (own or remote) invalidates cursors and can change\n // what is findable, so the running or finished session re-runs its query.\n let cancelRerun: (() => void) | null = null;\n ctx.listen(ctx.doc.events, () => {\n if (state().status === 'idle') return;\n cancelRerun?.();\n cancelRerun = ctx.clock.after(RERUN_DELAY_MS, () => {\n cancelRerun = null;\n void api.refresh().catch(() => {\n /* surfaced through getStatus() and onFailed */\n });\n });\n });\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { SEARCH_DEFAULTS, SearchToken, type SearchConfig } from './contract';\nimport { createSearchController } from './controller';\nimport { initialSearchState } from './model';\n\n/**\n * Document text search over the engine's budgeted, resumable slices.\n * Document-scoped; no pointer handling. The stage is optional: with one,\n * scans start at the current page and navigation reveals hits. `config` is\n * the settings the app registers, over {@link SEARCH_DEFAULTS}.\n */\nexport const searchPlugin = (config?: SearchConfig) =>\n definePlugin({\n id: 'search',\n token: SearchToken,\n scope: 'document',\n optional: [StageToken],\n state: initialSearchState,\n settings: { defaults: SEARCH_DEFAULTS, registered: config },\n create: createSearchController,\n });\n","/**\n * The search page's State table as code: what `useSearchState()` returns, and\n * the same fields in every other framework. The matches themselves are not\n * here: `useSearchHits()` reads them, so a component that only shows the\n * count doesn't re-render for every match that arrives.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { SearchToken } from './contract';\nimport { NO_PROGRESS } from './model';\n\nexport const searchState = defineState(SearchToken, {\n read: (search) => ({\n query: search.getQuery(),\n status: search.getStatus(),\n hitCount: search.getHitCount(),\n activeHitIndex: search.getActiveHitIndex(),\n activeHit: search.getActiveHit(),\n progress: search.getProgress(),\n error: search.getError(),\n }),\n empty: {\n query: null,\n status: 'idle',\n hitCount: 0,\n activeHitIndex: -1,\n activeHit: null,\n progress: NO_PROGRESS,\n error: null,\n },\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-search",
3
- "version": "3.0.0-next.13",
3
+ "version": "3.0.0-next.15",
4
4
  "private": false,
5
5
  "description": "Document text search: drives the engine's budgeted search slices, converts match rects to content space, and integrates with the Stage for viewport-first scanning and reveal-on-navigate.",
6
6
  "type": "module",
@@ -22,10 +22,10 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@embedpdf/core": "3.0.0-next.13",
26
- "@embedpdf/core-geometry": "3.0.0-next.13",
27
- "@embedpdf/plugin-stage": "3.0.0-next.13",
28
- "@embedpdf/engine-core": "3.0.0-next.13"
25
+ "@embedpdf/core": "3.0.0-next.15",
26
+ "@embedpdf/core-geometry": "3.0.0-next.15",
27
+ "@embedpdf/plugin-stage": "3.0.0-next.15",
28
+ "@embedpdf/engine-core": "3.0.0-next.15"
29
29
  },
30
30
  "devDependencies": {
31
31
  "typescript": "^5.0.0",
@@ -1,13 +0,0 @@
1
- let _embedpdf_core = require("@embedpdf/core");
2
- require("@embedpdf/engine-core/runtime");
3
- //#region src/types.ts
4
- const SearchToken = (0, _embedpdf_core.createCapabilityToken)("search");
5
- //#endregion
6
- Object.defineProperty(exports, "SearchToken", {
7
- enumerable: true,
8
- get: function() {
9
- return SearchToken;
10
- }
11
- });
12
-
13
- //# sourceMappingURL=contract-4PZsI7Ty.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-4PZsI7Ty.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { RevealAnchor, ScrollBehaviorKind } from '@embedpdf/plugin-stage/contract';\nimport type { SearchMode, SearchQuery, SearchSnippet } from '@embedpdf/engine-core/runtime';\n\n/**\n * One match, viewer-shaped: the engine's PDF-space line rects converted to\n * CONTENT space (y-down — the same space selection rects live in), plus the\n * page's display index so navigation can `reveal()` without a lookup.\n * `charStart`/`charCount` keep the engine's text-page offsets, so a hit can\n * later seed a selection or a highlight annotation without re-searching.\n */\n/**\n * One merged visual line of a match, in content space — the structural twin\n * of selection's `SelectionSegment` and the engine's `PdfTextSegment`.\n * `quad` is the geometric authority (corner-named, frame-geometric); `rect`\n * its AABB; `advance` the reading direction along the baseline.\n */\nexport interface TextSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\nexport interface SearchHit {\n pon: PageObjectNumber;\n pageIndex: number;\n charStart: number;\n charCount: number;\n /** Canonical visual-line segments (content space) — the drawing input. */\n segments: TextSegment[];\n /** Union envelope of the segments — the scroll/reveal target. Absent when\n * a match carries no drawable geometry (degenerate glyphs only). */\n bounds?: Rect;\n snippet?: SearchSnippet;\n}\n\n/**\n * `searching` means the slice loop is still walking pages — results are\n * usable the moment they appear (matches stream in viewport-first order).\n */\nexport type SearchStatus = 'idle' | 'searching' | 'complete' | 'error';\n\n/**\n * Execution options for the session `search()` — HOW the scan runs, never\n * part of what the query MEANS (which is why they're not on `SearchQuery`\n * and are never stored or restored into a search box).\n */\nexport interface SearchExecOptions {\n /** Scan origin override. Defaults to the Stage's current page (viewport-first). */\n startPage?: PageObjectNumber;\n}\n\n/** Options for the session-free `findAll()` service. */\nexport interface SearchFindAllOptions {\n /** Abort the scan; the returned promise rejects with `signal.reason`. */\n signal?: AbortSignal;\n /**\n * Pin the slice mode. Default: `'full'` with automatic `'rects'`\n * fallback when snippets are denied — pass `'rects'` when you only need\n * geometry (e.g. redact-by-term) to skip snippet extraction entirely.\n */\n mode?: SearchMode;\n}\n\nexport interface SearchState {\n /**\n * The query in flight/completed, null when idle/cleared — THE stored\n * form of the search intent, the same flat `SearchQuery` the engine\n * matches on, the wire caches by, and a search box renders. Survives\n * any search UI unmounting (results are document-scoped; so is this).\n */\n query: SearchQuery | null;\n status: SearchStatus;\n hits: SearchHit[];\n /** pon → indices into `hits` — the per-page layer's read model. */\n hitsByPage: Record<number, number[]>;\n /** Index into `hits`, -1 = none. */\n activeIndex: number;\n progress: { scanned: number; total: number };\n error: string | null;\n}\n\nexport type SearchAction =\n | { type: 'START'; query: SearchQuery }\n | { type: 'APPEND'; hits: SearchHit[]; scanned: number; total: number }\n | { type: 'COMPLETE' }\n | { type: 'ERROR'; message: string }\n | { type: 'SET_ACTIVE'; index: number }\n | { type: 'CLEAR' };\n\n/**\n * How hit navigation arrives (forwarded to the Stage's positioned reveal).\n * The default is the browser find-bar feel: the hit lands at the top-middle\n * of the viewport (`anchor: { y: 0.35 }`), horizontal only moves when the\n * hit is off-screen, zoom never changes, smooth tween.\n */\nexport interface SearchRevealOptions {\n anchor?: RevealAnchor;\n behavior?: ScrollBehaviorKind;\n}\n\nexport interface SearchPluginConfig {\n /** Arrival defaults for `next()`/`prev()`/`goTo()`. */\n reveal?: SearchRevealOptions;\n}\n\n/**\n * The search plugin is a find SERVICE plus one user-visible search SESSION\n * per document. `findAll` is the service; `search` is the session; the\n * sidebar, the highlights layer, and next/prev render the session.\n */\nexport interface SearchCapability {\n /**\n * The twin (permissions.md): would a search succeed now for this session?\n * No `mode` (or `'rects'`) asks about finding at all — `doc.text.search`\n * — which is what `search()` needs (the session degrades `'full'` to\n * `'rects'` by itself when snippets are denied). `canSearch('full')` also\n * requires `doc.text.copy` (a snippet reproduces document text) — ask it\n * before advertising snippet UI or pinning `mode: 'full'` in `findAll`.\n */\n canSearch(mode?: SearchMode): boolean;\n\n // ── the session: THE user-visible find, one per document ────────────────\n /**\n * Start a new search (supersedes and aborts any running one). Results\n * stream into state slice by slice; the first hit becomes active but the\n * camera does NOT move until `next()`/`prev()`/`goTo()`. An empty\n * `query.text` is \"stop searching\" — identical to `clear()`.\n */\n search(query: SearchQuery, exec?: SearchExecOptions): void;\n /** Re-run the current query from scratch (used after document mutations). */\n rerun(): void;\n clear(): void;\n\n /** Advance to the next/previous hit (wraps) and reveal it (see {@link SearchRevealOptions}). */\n next(): SearchHit | null;\n prev(): SearchHit | null;\n /** Jump to a specific hit index and reveal it; `reveal` overrides the plugin defaults. */\n goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null;\n\n /** The session's query — what a search box renders. Null when idle. */\n query(): SearchQuery | null;\n status(): SearchStatus;\n hits(): SearchHit[];\n hitCount(): number;\n activeIndex(): number;\n activeHit(): SearchHit | null;\n /** Hits on one page — the layer's input. Element identity is stable. */\n hitsForPage(pon: PageObjectNumber): SearchHit[];\n progress(): { scanned: number; total: number };\n errorMessage(): string | null;\n\n // ── the mechanism: session-free find service ─────────────────────────────\n /**\n * Run a query to completion and return every hit. Touches NO state — no\n * sidebar, no highlights, no camera, no activeIndex; the user's visible\n * search is never superseded. The primitive other features build on\n * (redact-by-term, link detection, occurrence badges). Scans in natural\n * page order; concurrent calls are independent.\n */\n findAll(query: SearchQuery, opts?: SearchFindAllOptions): Promise<SearchHit[]>;\n}\n\nexport const SearchToken = createCapabilityToken<SearchCapability>('search');\n"],"mappings":";;;AAoKA,MAAa,eAAA,GAAA,eAAA,sBAAA,CAAsD,QAAQ"}
@@ -1,170 +0,0 @@
1
- import { PageObjectNumber } from "@embedpdf/core";
2
- import { Rect, TextQuad } from "@embedpdf/core-geometry";
3
- import { RevealAnchor, ScrollBehaviorKind } from "@embedpdf/plugin-stage/contract";
4
- import { SearchMode, SearchQuery, SearchQuery as SearchQuery$1, SearchQueryIssue, SearchQueryValidation, SearchRegexValidation, SearchSnippet, SearchSnippet as SearchSnippet$1, validateSearchQuery, validateSearchRegex } from "@embedpdf/engine-core/runtime";
5
- //#region src/types.d.ts
6
- /**
7
- * One match, viewer-shaped: the engine's PDF-space line rects converted to
8
- * CONTENT space (y-down — the same space selection rects live in), plus the
9
- * page's display index so navigation can `reveal()` without a lookup.
10
- * `charStart`/`charCount` keep the engine's text-page offsets, so a hit can
11
- * later seed a selection or a highlight annotation without re-searching.
12
- */
13
- /**
14
- * One merged visual line of a match, in content space — the structural twin
15
- * of selection's `SelectionSegment` and the engine's `PdfTextSegment`.
16
- * `quad` is the geometric authority (corner-named, frame-geometric); `rect`
17
- * its AABB; `advance` the reading direction along the baseline.
18
- */
19
- interface TextSegment {
20
- quad: TextQuad;
21
- rect: Rect;
22
- advance: 1 | -1;
23
- }
24
- interface SearchHit {
25
- pon: PageObjectNumber;
26
- pageIndex: number;
27
- charStart: number;
28
- charCount: number;
29
- /** Canonical visual-line segments (content space) — the drawing input. */
30
- segments: TextSegment[];
31
- /** Union envelope of the segments — the scroll/reveal target. Absent when
32
- * a match carries no drawable geometry (degenerate glyphs only). */
33
- bounds?: Rect;
34
- snippet?: SearchSnippet;
35
- }
36
- /**
37
- * `searching` means the slice loop is still walking pages — results are
38
- * usable the moment they appear (matches stream in viewport-first order).
39
- */
40
- type SearchStatus = 'idle' | 'searching' | 'complete' | 'error';
41
- /**
42
- * Execution options for the session `search()` — HOW the scan runs, never
43
- * part of what the query MEANS (which is why they're not on `SearchQuery`
44
- * and are never stored or restored into a search box).
45
- */
46
- interface SearchExecOptions {
47
- /** Scan origin override. Defaults to the Stage's current page (viewport-first). */
48
- startPage?: PageObjectNumber;
49
- }
50
- /** Options for the session-free `findAll()` service. */
51
- interface SearchFindAllOptions {
52
- /** Abort the scan; the returned promise rejects with `signal.reason`. */
53
- signal?: AbortSignal;
54
- /**
55
- * Pin the slice mode. Default: `'full'` with automatic `'rects'`
56
- * fallback when snippets are denied — pass `'rects'` when you only need
57
- * geometry (e.g. redact-by-term) to skip snippet extraction entirely.
58
- */
59
- mode?: SearchMode;
60
- }
61
- interface SearchState {
62
- /**
63
- * The query in flight/completed, null when idle/cleared — THE stored
64
- * form of the search intent, the same flat `SearchQuery` the engine
65
- * matches on, the wire caches by, and a search box renders. Survives
66
- * any search UI unmounting (results are document-scoped; so is this).
67
- */
68
- query: SearchQuery | null;
69
- status: SearchStatus;
70
- hits: SearchHit[];
71
- /** pon → indices into `hits` — the per-page layer's read model. */
72
- hitsByPage: Record<number, number[]>;
73
- /** Index into `hits`, -1 = none. */
74
- activeIndex: number;
75
- progress: {
76
- scanned: number;
77
- total: number;
78
- };
79
- error: string | null;
80
- }
81
- type SearchAction = {
82
- type: 'START';
83
- query: SearchQuery;
84
- } | {
85
- type: 'APPEND';
86
- hits: SearchHit[];
87
- scanned: number;
88
- total: number;
89
- } | {
90
- type: 'COMPLETE';
91
- } | {
92
- type: 'ERROR';
93
- message: string;
94
- } | {
95
- type: 'SET_ACTIVE';
96
- index: number;
97
- } | {
98
- type: 'CLEAR';
99
- };
100
- /**
101
- * How hit navigation arrives (forwarded to the Stage's positioned reveal).
102
- * The default is the browser find-bar feel: the hit lands at the top-middle
103
- * of the viewport (`anchor: { y: 0.35 }`), horizontal only moves when the
104
- * hit is off-screen, zoom never changes, smooth tween.
105
- */
106
- interface SearchRevealOptions {
107
- anchor?: RevealAnchor;
108
- behavior?: ScrollBehaviorKind;
109
- }
110
- interface SearchPluginConfig {
111
- /** Arrival defaults for `next()`/`prev()`/`goTo()`. */
112
- reveal?: SearchRevealOptions;
113
- }
114
- /**
115
- * The search plugin is a find SERVICE plus one user-visible search SESSION
116
- * per document. `findAll` is the service; `search` is the session; the
117
- * sidebar, the highlights layer, and next/prev render the session.
118
- */
119
- interface SearchCapability {
120
- /**
121
- * The twin (permissions.md): would a search succeed now for this session?
122
- * No `mode` (or `'rects'`) asks about finding at all — `doc.text.search`
123
- * — which is what `search()` needs (the session degrades `'full'` to
124
- * `'rects'` by itself when snippets are denied). `canSearch('full')` also
125
- * requires `doc.text.copy` (a snippet reproduces document text) — ask it
126
- * before advertising snippet UI or pinning `mode: 'full'` in `findAll`.
127
- */
128
- canSearch(mode?: SearchMode): boolean;
129
- /**
130
- * Start a new search (supersedes and aborts any running one). Results
131
- * stream into state slice by slice; the first hit becomes active but the
132
- * camera does NOT move until `next()`/`prev()`/`goTo()`. An empty
133
- * `query.text` is "stop searching" — identical to `clear()`.
134
- */
135
- search(query: SearchQuery, exec?: SearchExecOptions): void;
136
- /** Re-run the current query from scratch (used after document mutations). */
137
- rerun(): void;
138
- clear(): void;
139
- /** Advance to the next/previous hit (wraps) and reveal it (see {@link SearchRevealOptions}). */
140
- next(): SearchHit | null;
141
- prev(): SearchHit | null;
142
- /** Jump to a specific hit index and reveal it; `reveal` overrides the plugin defaults. */
143
- goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null;
144
- /** The session's query — what a search box renders. Null when idle. */
145
- query(): SearchQuery | null;
146
- status(): SearchStatus;
147
- hits(): SearchHit[];
148
- hitCount(): number;
149
- activeIndex(): number;
150
- activeHit(): SearchHit | null;
151
- /** Hits on one page — the layer's input. Element identity is stable. */
152
- hitsForPage(pon: PageObjectNumber): SearchHit[];
153
- progress(): {
154
- scanned: number;
155
- total: number;
156
- };
157
- errorMessage(): string | null;
158
- /**
159
- * Run a query to completion and return every hit. Touches NO state — no
160
- * sidebar, no highlights, no camera, no activeIndex; the user's visible
161
- * search is never superseded. The primitive other features build on
162
- * (redact-by-term, link detection, occurrence badges). Scans in natural
163
- * page order; concurrent calls are independent.
164
- */
165
- findAll(query: SearchQuery, opts?: SearchFindAllOptions): Promise<SearchHit[]>;
166
- }
167
- declare const SearchToken: import("@embedpdf/core").CapabilityToken<SearchCapability>;
168
- //#endregion
169
- export { SearchToken as _, SearchSnippet$1 as a, SearchAction as c, SearchFindAllOptions as d, SearchHit as f, SearchStatus as g, SearchState as h, SearchRegexValidation as i, SearchCapability as l, SearchRevealOptions as m, SearchQueryIssue as n, validateSearchQuery as o, SearchPluginConfig as p, SearchQueryValidation as r, validateSearchRegex as s, SearchQuery$1 as t, SearchExecOptions as u };
170
- //# sourceMappingURL=contract-CSb66POC.d.cts.map
@@ -1,170 +0,0 @@
1
- import { PageObjectNumber } from "@embedpdf/core";
2
- import { RevealAnchor, ScrollBehaviorKind } from "@embedpdf/plugin-stage/contract";
3
- import { Rect, TextQuad } from "@embedpdf/core-geometry";
4
- import { SearchMode, SearchQuery, SearchQuery as SearchQuery$1, SearchQueryIssue, SearchQueryValidation, SearchRegexValidation, SearchSnippet, SearchSnippet as SearchSnippet$1, validateSearchQuery, validateSearchRegex } from "@embedpdf/engine-core/runtime";
5
- //#region src/types.d.ts
6
- /**
7
- * One match, viewer-shaped: the engine's PDF-space line rects converted to
8
- * CONTENT space (y-down — the same space selection rects live in), plus the
9
- * page's display index so navigation can `reveal()` without a lookup.
10
- * `charStart`/`charCount` keep the engine's text-page offsets, so a hit can
11
- * later seed a selection or a highlight annotation without re-searching.
12
- */
13
- /**
14
- * One merged visual line of a match, in content space — the structural twin
15
- * of selection's `SelectionSegment` and the engine's `PdfTextSegment`.
16
- * `quad` is the geometric authority (corner-named, frame-geometric); `rect`
17
- * its AABB; `advance` the reading direction along the baseline.
18
- */
19
- interface TextSegment {
20
- quad: TextQuad;
21
- rect: Rect;
22
- advance: 1 | -1;
23
- }
24
- interface SearchHit {
25
- pon: PageObjectNumber;
26
- pageIndex: number;
27
- charStart: number;
28
- charCount: number;
29
- /** Canonical visual-line segments (content space) — the drawing input. */
30
- segments: TextSegment[];
31
- /** Union envelope of the segments — the scroll/reveal target. Absent when
32
- * a match carries no drawable geometry (degenerate glyphs only). */
33
- bounds?: Rect;
34
- snippet?: SearchSnippet;
35
- }
36
- /**
37
- * `searching` means the slice loop is still walking pages — results are
38
- * usable the moment they appear (matches stream in viewport-first order).
39
- */
40
- type SearchStatus = 'idle' | 'searching' | 'complete' | 'error';
41
- /**
42
- * Execution options for the session `search()` — HOW the scan runs, never
43
- * part of what the query MEANS (which is why they're not on `SearchQuery`
44
- * and are never stored or restored into a search box).
45
- */
46
- interface SearchExecOptions {
47
- /** Scan origin override. Defaults to the Stage's current page (viewport-first). */
48
- startPage?: PageObjectNumber;
49
- }
50
- /** Options for the session-free `findAll()` service. */
51
- interface SearchFindAllOptions {
52
- /** Abort the scan; the returned promise rejects with `signal.reason`. */
53
- signal?: AbortSignal;
54
- /**
55
- * Pin the slice mode. Default: `'full'` with automatic `'rects'`
56
- * fallback when snippets are denied — pass `'rects'` when you only need
57
- * geometry (e.g. redact-by-term) to skip snippet extraction entirely.
58
- */
59
- mode?: SearchMode;
60
- }
61
- interface SearchState {
62
- /**
63
- * The query in flight/completed, null when idle/cleared — THE stored
64
- * form of the search intent, the same flat `SearchQuery` the engine
65
- * matches on, the wire caches by, and a search box renders. Survives
66
- * any search UI unmounting (results are document-scoped; so is this).
67
- */
68
- query: SearchQuery | null;
69
- status: SearchStatus;
70
- hits: SearchHit[];
71
- /** pon → indices into `hits` — the per-page layer's read model. */
72
- hitsByPage: Record<number, number[]>;
73
- /** Index into `hits`, -1 = none. */
74
- activeIndex: number;
75
- progress: {
76
- scanned: number;
77
- total: number;
78
- };
79
- error: string | null;
80
- }
81
- type SearchAction = {
82
- type: 'START';
83
- query: SearchQuery;
84
- } | {
85
- type: 'APPEND';
86
- hits: SearchHit[];
87
- scanned: number;
88
- total: number;
89
- } | {
90
- type: 'COMPLETE';
91
- } | {
92
- type: 'ERROR';
93
- message: string;
94
- } | {
95
- type: 'SET_ACTIVE';
96
- index: number;
97
- } | {
98
- type: 'CLEAR';
99
- };
100
- /**
101
- * How hit navigation arrives (forwarded to the Stage's positioned reveal).
102
- * The default is the browser find-bar feel: the hit lands at the top-middle
103
- * of the viewport (`anchor: { y: 0.35 }`), horizontal only moves when the
104
- * hit is off-screen, zoom never changes, smooth tween.
105
- */
106
- interface SearchRevealOptions {
107
- anchor?: RevealAnchor;
108
- behavior?: ScrollBehaviorKind;
109
- }
110
- interface SearchPluginConfig {
111
- /** Arrival defaults for `next()`/`prev()`/`goTo()`. */
112
- reveal?: SearchRevealOptions;
113
- }
114
- /**
115
- * The search plugin is a find SERVICE plus one user-visible search SESSION
116
- * per document. `findAll` is the service; `search` is the session; the
117
- * sidebar, the highlights layer, and next/prev render the session.
118
- */
119
- interface SearchCapability {
120
- /**
121
- * The twin (permissions.md): would a search succeed now for this session?
122
- * No `mode` (or `'rects'`) asks about finding at all — `doc.text.search`
123
- * — which is what `search()` needs (the session degrades `'full'` to
124
- * `'rects'` by itself when snippets are denied). `canSearch('full')` also
125
- * requires `doc.text.copy` (a snippet reproduces document text) — ask it
126
- * before advertising snippet UI or pinning `mode: 'full'` in `findAll`.
127
- */
128
- canSearch(mode?: SearchMode): boolean;
129
- /**
130
- * Start a new search (supersedes and aborts any running one). Results
131
- * stream into state slice by slice; the first hit becomes active but the
132
- * camera does NOT move until `next()`/`prev()`/`goTo()`. An empty
133
- * `query.text` is "stop searching" — identical to `clear()`.
134
- */
135
- search(query: SearchQuery, exec?: SearchExecOptions): void;
136
- /** Re-run the current query from scratch (used after document mutations). */
137
- rerun(): void;
138
- clear(): void;
139
- /** Advance to the next/previous hit (wraps) and reveal it (see {@link SearchRevealOptions}). */
140
- next(): SearchHit | null;
141
- prev(): SearchHit | null;
142
- /** Jump to a specific hit index and reveal it; `reveal` overrides the plugin defaults. */
143
- goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null;
144
- /** The session's query — what a search box renders. Null when idle. */
145
- query(): SearchQuery | null;
146
- status(): SearchStatus;
147
- hits(): SearchHit[];
148
- hitCount(): number;
149
- activeIndex(): number;
150
- activeHit(): SearchHit | null;
151
- /** Hits on one page — the layer's input. Element identity is stable. */
152
- hitsForPage(pon: PageObjectNumber): SearchHit[];
153
- progress(): {
154
- scanned: number;
155
- total: number;
156
- };
157
- errorMessage(): string | null;
158
- /**
159
- * Run a query to completion and return every hit. Touches NO state — no
160
- * sidebar, no highlights, no camera, no activeIndex; the user's visible
161
- * search is never superseded. The primitive other features build on
162
- * (redact-by-term, link detection, occurrence badges). Scans in natural
163
- * page order; concurrent calls are independent.
164
- */
165
- findAll(query: SearchQuery, opts?: SearchFindAllOptions): Promise<SearchHit[]>;
166
- }
167
- declare const SearchToken: import("@embedpdf/core").CapabilityToken<SearchCapability>;
168
- //#endregion
169
- export { SearchToken as _, SearchSnippet$1 as a, SearchAction as c, SearchFindAllOptions as d, SearchHit as f, SearchStatus as g, SearchState as h, SearchRegexValidation as i, SearchCapability as l, SearchRevealOptions as m, SearchQueryIssue as n, validateSearchQuery as o, SearchPluginConfig as p, SearchQueryValidation as r, validateSearchRegex as s, SearchQuery$1 as t, SearchExecOptions as u };
170
- //# sourceMappingURL=contract-Cj4xgdMj.d.ts.map
@@ -1,8 +0,0 @@
1
- import { createCapabilityToken } from "@embedpdf/core";
2
- import { validateSearchQuery, validateSearchRegex } from "@embedpdf/engine-core/runtime";
3
- //#region src/types.ts
4
- const SearchToken = createCapabilityToken("search");
5
- //#endregion
6
- export { validateSearchRegex as n, SearchToken as r, validateSearchQuery as t };
7
-
8
- //# sourceMappingURL=contract-YzH14CUn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-YzH14CUn.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { RevealAnchor, ScrollBehaviorKind } from '@embedpdf/plugin-stage/contract';\nimport type { SearchMode, SearchQuery, SearchSnippet } from '@embedpdf/engine-core/runtime';\n\n/**\n * One match, viewer-shaped: the engine's PDF-space line rects converted to\n * CONTENT space (y-down — the same space selection rects live in), plus the\n * page's display index so navigation can `reveal()` without a lookup.\n * `charStart`/`charCount` keep the engine's text-page offsets, so a hit can\n * later seed a selection or a highlight annotation without re-searching.\n */\n/**\n * One merged visual line of a match, in content space — the structural twin\n * of selection's `SelectionSegment` and the engine's `PdfTextSegment`.\n * `quad` is the geometric authority (corner-named, frame-geometric); `rect`\n * its AABB; `advance` the reading direction along the baseline.\n */\nexport interface TextSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\nexport interface SearchHit {\n pon: PageObjectNumber;\n pageIndex: number;\n charStart: number;\n charCount: number;\n /** Canonical visual-line segments (content space) — the drawing input. */\n segments: TextSegment[];\n /** Union envelope of the segments — the scroll/reveal target. Absent when\n * a match carries no drawable geometry (degenerate glyphs only). */\n bounds?: Rect;\n snippet?: SearchSnippet;\n}\n\n/**\n * `searching` means the slice loop is still walking pages — results are\n * usable the moment they appear (matches stream in viewport-first order).\n */\nexport type SearchStatus = 'idle' | 'searching' | 'complete' | 'error';\n\n/**\n * Execution options for the session `search()` — HOW the scan runs, never\n * part of what the query MEANS (which is why they're not on `SearchQuery`\n * and are never stored or restored into a search box).\n */\nexport interface SearchExecOptions {\n /** Scan origin override. Defaults to the Stage's current page (viewport-first). */\n startPage?: PageObjectNumber;\n}\n\n/** Options for the session-free `findAll()` service. */\nexport interface SearchFindAllOptions {\n /** Abort the scan; the returned promise rejects with `signal.reason`. */\n signal?: AbortSignal;\n /**\n * Pin the slice mode. Default: `'full'` with automatic `'rects'`\n * fallback when snippets are denied — pass `'rects'` when you only need\n * geometry (e.g. redact-by-term) to skip snippet extraction entirely.\n */\n mode?: SearchMode;\n}\n\nexport interface SearchState {\n /**\n * The query in flight/completed, null when idle/cleared — THE stored\n * form of the search intent, the same flat `SearchQuery` the engine\n * matches on, the wire caches by, and a search box renders. Survives\n * any search UI unmounting (results are document-scoped; so is this).\n */\n query: SearchQuery | null;\n status: SearchStatus;\n hits: SearchHit[];\n /** pon → indices into `hits` — the per-page layer's read model. */\n hitsByPage: Record<number, number[]>;\n /** Index into `hits`, -1 = none. */\n activeIndex: number;\n progress: { scanned: number; total: number };\n error: string | null;\n}\n\nexport type SearchAction =\n | { type: 'START'; query: SearchQuery }\n | { type: 'APPEND'; hits: SearchHit[]; scanned: number; total: number }\n | { type: 'COMPLETE' }\n | { type: 'ERROR'; message: string }\n | { type: 'SET_ACTIVE'; index: number }\n | { type: 'CLEAR' };\n\n/**\n * How hit navigation arrives (forwarded to the Stage's positioned reveal).\n * The default is the browser find-bar feel: the hit lands at the top-middle\n * of the viewport (`anchor: { y: 0.35 }`), horizontal only moves when the\n * hit is off-screen, zoom never changes, smooth tween.\n */\nexport interface SearchRevealOptions {\n anchor?: RevealAnchor;\n behavior?: ScrollBehaviorKind;\n}\n\nexport interface SearchPluginConfig {\n /** Arrival defaults for `next()`/`prev()`/`goTo()`. */\n reveal?: SearchRevealOptions;\n}\n\n/**\n * The search plugin is a find SERVICE plus one user-visible search SESSION\n * per document. `findAll` is the service; `search` is the session; the\n * sidebar, the highlights layer, and next/prev render the session.\n */\nexport interface SearchCapability {\n /**\n * The twin (permissions.md): would a search succeed now for this session?\n * No `mode` (or `'rects'`) asks about finding at all — `doc.text.search`\n * — which is what `search()` needs (the session degrades `'full'` to\n * `'rects'` by itself when snippets are denied). `canSearch('full')` also\n * requires `doc.text.copy` (a snippet reproduces document text) — ask it\n * before advertising snippet UI or pinning `mode: 'full'` in `findAll`.\n */\n canSearch(mode?: SearchMode): boolean;\n\n // ── the session: THE user-visible find, one per document ────────────────\n /**\n * Start a new search (supersedes and aborts any running one). Results\n * stream into state slice by slice; the first hit becomes active but the\n * camera does NOT move until `next()`/`prev()`/`goTo()`. An empty\n * `query.text` is \"stop searching\" — identical to `clear()`.\n */\n search(query: SearchQuery, exec?: SearchExecOptions): void;\n /** Re-run the current query from scratch (used after document mutations). */\n rerun(): void;\n clear(): void;\n\n /** Advance to the next/previous hit (wraps) and reveal it (see {@link SearchRevealOptions}). */\n next(): SearchHit | null;\n prev(): SearchHit | null;\n /** Jump to a specific hit index and reveal it; `reveal` overrides the plugin defaults. */\n goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null;\n\n /** The session's query — what a search box renders. Null when idle. */\n query(): SearchQuery | null;\n status(): SearchStatus;\n hits(): SearchHit[];\n hitCount(): number;\n activeIndex(): number;\n activeHit(): SearchHit | null;\n /** Hits on one page — the layer's input. Element identity is stable. */\n hitsForPage(pon: PageObjectNumber): SearchHit[];\n progress(): { scanned: number; total: number };\n errorMessage(): string | null;\n\n // ── the mechanism: session-free find service ─────────────────────────────\n /**\n * Run a query to completion and return every hit. Touches NO state — no\n * sidebar, no highlights, no camera, no activeIndex; the user's visible\n * search is never superseded. The primitive other features build on\n * (redact-by-term, link detection, occurrence badges). Scans in natural\n * page order; concurrent calls are independent.\n */\n findAll(query: SearchQuery, opts?: SearchFindAllOptions): Promise<SearchHit[]>;\n}\n\nexport const SearchToken = createCapabilityToken<SearchCapability>('search');\n"],"mappings":";;;AAoKA,MAAa,cAAc,sBAAwC,QAAQ"}