pdf-search-highlight 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -199,10 +199,11 @@ function App() {
199
199
  | Export | Description |
200
200
  |---|---|
201
201
  | `PDFRenderer` | Renders PDF pages into a container (canvas + text layer) |
202
- | `SearchController` | Headless search + highlight controller |
203
- | `PDFSearchViewer` | All-in-one: render + search + highlight + zoom + download |
202
+ | `SearchController` | Headless search + highlight controller. `search()` for single query, `searchMultiple()` for multi-context |
203
+ | `PDFSearchViewer` | All-in-one: render + search + highlight + zoom + download. `search()` + `searchMultiple()` |
204
204
  | `searchPage` | Low-level: search spans with flexible regex |
205
205
  | `HighlightManager` | Low-level: apply/clear highlights on spans |
206
+ | `SearchContext` | Type: `{ query: string; options?: SearchOptions }` — used with `searchMultiple()` |
206
207
 
207
208
  ### React (`pdf-search-highlight/react`)
208
209
 
@@ -210,7 +211,8 @@ function App() {
210
211
  |---|---|
211
212
  | `usePDFRenderer(pdfjsLib, options?)` | Hook: render PDF, returns `{ containerRef, pages, loadPDF, scale, setScale, zoomIn, zoomOut, download, ... }` |
212
213
  | `useSearchController(pages, options?)` | Hook: search + highlight, returns `{ search, searchMultiple, next, prev, goTo, clear, current, total }` |
213
- | `PDFSearchViewer` | All-in-one component with ref handle for imperative control |
214
+ | `PDFSearchViewer` | All-in-one component. Props: `searchQuery` (single) or `searchContexts` (multi). Ref handle: `nextMatch`, `prevMatch`, `searchMultiple`, `clearSearch`, ... |
215
+ | `SearchContext` | Type re-exported from core |
214
216
 
215
217
  ### PDFRenderer
216
218
 
@@ -268,17 +270,21 @@ search.contexts // last multi-context queries
268
270
  const viewer = new PDFSearchViewer(container, pdfjsLib, options);
269
271
 
270
272
  await viewer.loadPDF(source);
273
+
274
+ // Single search
271
275
  viewer.search('query', { caseSensitive: true });
272
276
 
273
- // Multi-context search
277
+ // Multi-context search — each context highlighted with a different color
274
278
  viewer.searchMultiple([
275
279
  { query: 'contract' },
276
280
  { query: 'payment' },
281
+ { query: 'deadline', options: { fuzzy: true } },
277
282
  ]);
278
283
 
279
- viewer.nextMatch();
280
- viewer.prevMatch();
281
- viewer.clearSearch();
284
+ // Navigation — works for both single and multi-context
285
+ viewer.nextMatch(); // Next match (all contexts, document order)
286
+ viewer.prevMatch(); // Previous match
287
+ viewer.clearSearch(); // Clear all highlights
282
288
 
283
289
  await viewer.zoomIn(); // Zoom in by 0.25
284
290
  await viewer.zoomOut(); // Zoom out by 0.25
@@ -36,6 +36,8 @@ interface PDFSearchViewerOptions {
36
36
  workerSrc?: string;
37
37
  /** Gap in pixels between rendered pages. Defaults to 20. */
38
38
  pageGap?: number;
39
+ /** Auto-scroll to active match on search/next/prev. Defaults to true. */
40
+ autoScroll?: boolean;
39
41
  /**
40
42
  * Custom CSS class names for viewer elements.
41
43
  * Override any or all to apply your own styles.
@@ -36,6 +36,8 @@ interface PDFSearchViewerOptions {
36
36
  workerSrc?: string;
37
37
  /** Gap in pixels between rendered pages. Defaults to 20. */
38
38
  pageGap?: number;
39
+ /** Auto-scroll to active match on search/next/prev. Defaults to true. */
40
+ autoScroll?: boolean;
39
41
  /**
40
42
  * Custom CSS class names for viewer elements.
41
43
  * Override any or all to apply your own styles.
@@ -379,6 +379,8 @@ var HighlightManager = class {
379
379
  constructor(highlightClass, activeHighlightClass) {
380
380
  this.matches = [];
381
381
  this.currentMatch = -1;
382
+ /** Whether to auto-scroll to the active match. Defaults to true. */
383
+ this.autoScroll = true;
382
384
  this.highlightClass = highlightClass;
383
385
  this.activeHighlightClass = activeHighlightClass;
384
386
  }
@@ -460,10 +462,12 @@ var HighlightManager = class {
460
462
  this.matches[index].marks.forEach(
461
463
  (m) => m.classList.add(this.activeHighlightClass)
462
464
  );
463
- this.matches[index].marks[0]?.scrollIntoView({
464
- behavior: "smooth",
465
- block: "center"
466
- });
465
+ if (this.autoScroll) {
466
+ this.matches[index].marks[0]?.scrollIntoView({
467
+ behavior: "smooth",
468
+ block: "center"
469
+ });
470
+ }
467
471
  }
468
472
  }
469
473
  /**
@@ -512,6 +516,9 @@ var PDFSearchViewer = class extends EventEmitter {
512
516
  cls.highlight,
513
517
  cls.activeHighlight
514
518
  );
519
+ if (options.autoScroll !== void 0) {
520
+ this.highlightManager.autoScroll = options.autoScroll;
521
+ }
515
522
  }
516
523
  /**
517
524
  * Load and render a PDF document.
@@ -744,6 +751,16 @@ var SearchController = class {
744
751
  this.onChange = null;
745
752
  const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };
746
753
  this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);
754
+ if (options.autoScroll !== void 0) {
755
+ this.highlightManager.autoScroll = options.autoScroll;
756
+ }
757
+ }
758
+ /** Get or set auto-scroll behavior. */
759
+ get autoScroll() {
760
+ return this.highlightManager.autoScroll;
761
+ }
762
+ set autoScroll(value) {
763
+ this.highlightManager.autoScroll = value;
747
764
  }
748
765
  /**
749
766
  * Set the pages to search on.
@@ -907,4 +924,4 @@ export {
907
924
  PDFSearchViewer,
908
925
  SearchController
909
926
  };
910
- //# sourceMappingURL=chunk-XFH7JUDJ.js.map
927
+ //# sourceMappingURL=chunk-4NJWRYPR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/constants.ts","../src/core/PDFRenderer.ts","../src/core/SearchEngine.ts","../src/core/HighlightManager.ts","../src/core/PDFSearchViewer.ts","../src/core/SearchController.ts"],"sourcesContent":["type Listener<T> = (data: T) => void;\n\nexport class EventEmitter<EventMap extends { [key: string]: unknown }> {\n private listeners = new Map<keyof EventMap, Set<Listener<any>>>();\n\n on<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n protected emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {\n this.listeners.get(event)?.forEach((fn) => fn(data));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import type { ClassNames } from '../types';\n\nexport const DEFAULT_CLASS_NAMES: Required<ClassNames> = {\n container: 'psh-container',\n page: 'psh-page',\n canvas: 'psh-canvas',\n textLayer: 'psh-text-layer',\n pageLabel: 'psh-page-label',\n highlight: 'highlight',\n activeHighlight: 'active',\n};\n\nexport const DEFAULT_SCALE = 'auto' as number | 'auto';\nexport const DEFAULT_PAGE_GAP = 20;\n\nexport const ZOOM_STEP = 0.25;\nexport const MIN_SCALE = 0.25;\nexport const MAX_SCALE = 5;\n\n/** Number of predefined multi-context highlight colors (cycles with modulo). */\nexport const MULTI_CONTEXT_COLOR_COUNT = 8;\n","import type { PDFSearchViewerOptions, ClassNames, PageData, SpanData } from '../types';\nimport { DEFAULT_CLASS_NAMES, DEFAULT_SCALE, DEFAULT_PAGE_GAP } from './constants';\n\n// pdfjs-dist types\ntype PDFDocumentProxy = any;\ntype PDFPageProxy = any;\n\n/**\n * Renders PDF pages into a container using canvas + text layer.\n *\n * Text layer approach (matching demo):\n * - Extract text content from each page\n * - Create absolutely-positioned <span> elements overlaying the canvas\n * - Position spans using the transform matrix from pdf.js\n * - Spans are transparent (for text selection) but allow DOM-based search/highlight\n */\nexport class PDFRenderer {\n private container: HTMLElement;\n private scale: number | 'auto';\n private pageGap: number;\n private cls: Required<ClassNames>;\n private workerSrc?: string;\n private pdfDoc: PDFDocumentProxy | null = null;\n private pageData: PageData[] = [];\n private pdfjsLib: any = null;\n private effectiveScale: number = 1;\n\n constructor(container: HTMLElement, options: PDFSearchViewerOptions) {\n this.container = container;\n this.scale = options.scale ?? DEFAULT_SCALE;\n this.pageGap = options.pageGap ?? DEFAULT_PAGE_GAP;\n this.workerSrc = options.workerSrc;\n this.cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n }\n\n /**\n * Set the pdfjs-dist library reference.\n * Must be called before loadDocument.\n */\n setPdfjsLib(lib: any): void {\n this.pdfjsLib = lib;\n if (this.workerSrc) {\n lib.GlobalWorkerOptions.workerSrc = this.workerSrc;\n }\n }\n\n /**\n * Load a PDF from File, ArrayBuffer, URL string, or Uint8Array.\n */\n async loadDocument(\n source: File | ArrayBuffer | Uint8Array | string\n ): Promise<number> {\n if (!this.pdfjsLib) {\n throw new Error(\n 'pdfjs-dist not set. Call setPdfjsLib(pdfjsLib) before loading a document.'\n );\n }\n\n this.cleanup();\n\n let data: ArrayBuffer | Uint8Array | { url: string };\n if (source instanceof File) {\n data = await source.arrayBuffer();\n } else if (typeof source === 'string') {\n data = { url: source };\n } else {\n data = source;\n }\n\n const loadingTask = this.pdfjsLib.getDocument({ data });\n this.pdfDoc = await loadingTask.promise;\n return this.pdfDoc.numPages;\n }\n\n /**\n * Render all pages into the container.\n * Preserves scroll position across re-renders (e.g. zoom).\n * Returns PageData[] for search/highlight.\n */\n async renderAllPages(): Promise<PageData[]> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n // Save scroll position relative to total content height\n const prevScrollTop = this.container.scrollTop;\n const prevScrollHeight = this.container.scrollHeight || 1;\n const scrollRatio = prevScrollTop / prevScrollHeight;\n\n this.container.innerHTML = '';\n this.container.classList.add(this.cls.container);\n this.pageData = [];\n\n const numPages = this.pdfDoc.numPages;\n\n for (let i = 1; i <= numPages; i++) {\n const page = await this.pdfDoc.getPage(i);\n const pd = await this.renderPage(page, i, numPages);\n this.pageData.push(pd);\n }\n\n // Restore scroll position proportionally\n if (prevScrollTop > 0) {\n this.container.scrollTop = scrollRatio * this.container.scrollHeight;\n }\n\n return this.pageData;\n }\n\n private async renderPage(\n page: PDFPageProxy,\n pageNum: number,\n totalPages: number\n ): Promise<PageData> {\n const scale = this.calculateScale(page);\n if (pageNum === 1) this.effectiveScale = scale;\n const vp = page.getViewport({ scale });\n\n // Page container\n const container = document.createElement('div');\n container.className = this.cls.page;\n container.style.position = 'relative';\n container.style.width = vp.width + 'px';\n container.style.height = vp.height + 'px';\n container.style.margin = '0 auto';\n container.style.marginBottom = this.pageGap + 'px';\n container.style.overflow = 'hidden';\n container.dataset.page = String(pageNum);\n\n // Canvas (2x for retina)\n const canvas = document.createElement('canvas');\n canvas.className = this.cls.canvas;\n canvas.width = vp.width * 2;\n canvas.height = vp.height * 2;\n canvas.style.width = vp.width + 'px';\n canvas.style.height = vp.height + 'px';\n canvas.style.display = 'block';\n const ctx = canvas.getContext('2d')!;\n ctx.scale(2, 2);\n await page.render({ canvasContext: ctx, viewport: vp }).promise;\n\n // Text layer\n const textLayer = document.createElement('div');\n textLayer.className = this.cls.textLayer;\n textLayer.style.position = 'absolute';\n textLayer.style.top = '0';\n textLayer.style.left = '0';\n textLayer.style.right = '0';\n textLayer.style.bottom = '0';\n textLayer.style.overflow = 'hidden';\n textLayer.style.lineHeight = '1';\n\n const tc = await page.getTextContent();\n const spans: SpanData[] = [];\n\n for (const item of tc.items) {\n if (!item.str && !item.hasEOL) continue;\n\n const tx = this.pdfjsLib.Util.transform(vp.transform, item.transform);\n const span = document.createElement('span');\n span.textContent = item.str || '';\n const fh = Math.hypot(tx[2], tx[3]);\n span.style.position = 'absolute';\n span.style.left = tx[4] + 'px';\n span.style.top = (tx[5] - fh) + 'px';\n span.style.fontSize = fh + 'px';\n span.style.color = 'transparent';\n span.style.whiteSpace = 'pre';\n span.style.cursor = 'text';\n span.style.transformOrigin = '0% 0%';\n if (item.fontName) span.style.fontFamily = item.fontName;\n\n const sw = tx[0] / fh;\n if (Math.abs(sw - 1) > 0.01) {\n span.style.transform = `scaleX(${sw})`;\n }\n\n textLayer.appendChild(span);\n spans.push({\n el: span,\n text: item.str || '',\n hasEOL: !!item.hasEOL,\n });\n }\n\n container.appendChild(canvas);\n container.appendChild(textLayer);\n this.container.appendChild(container);\n\n // Page label\n const label = document.createElement('div');\n label.className = this.cls.pageLabel;\n label.textContent = `Page ${pageNum} / ${totalPages}`;\n this.container.appendChild(label);\n\n return { container, spans };\n }\n\n private calculateScale(page: PDFPageProxy): number {\n if (this.scale !== 'auto' && typeof this.scale === 'number') {\n return this.scale;\n }\n const defaultVp = page.getViewport({ scale: 1 });\n const containerWidth = this.container.clientWidth || 800;\n return Math.min(containerWidth / defaultVp.width, 2);\n }\n\n /** Set the scale for subsequent renders. */\n setScale(scale: number | 'auto'): void {\n this.scale = scale;\n }\n\n /** Get the configured scale setting. */\n getScale(): number | 'auto' {\n return this.scale;\n }\n\n /** Get the actual numeric scale used in the last render. */\n getEffectiveScale(): number {\n return this.effectiveScale;\n }\n\n /**\n * Download the currently loaded PDF.\n */\n async download(filename: string = 'document.pdf'): Promise<void> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n const data = await this.pdfDoc.getData();\n const blob = new Blob([data as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n\n getClassNames(): Required<ClassNames> {\n return this.cls;\n }\n\n getPageData(): PageData[] {\n return this.pageData;\n }\n\n getPageCount(): number {\n return this.pdfDoc?.numPages ?? 0;\n }\n\n cleanup(): void {\n this.pdfDoc?.destroy();\n this.pdfDoc = null;\n this.pageData = [];\n this.container.innerHTML = '';\n }\n}\n","import type { SearchOptions, SpanData } from '../types';\n\nexport interface CharMapEntry {\n spanIdx: number;\n charIdx: number;\n}\n\nexport interface MatchRange {\n spanIdx: number;\n start: number;\n end: number;\n}\n\nexport interface SearchResult {\n /** Array of span ranges for each match */\n matchRanges: MatchRange[][];\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Build a flexible regex from query.\n *\n * For queries < 200 chars (after removing whitespace):\n * - Strip all whitespace from query\n * - Insert \\s* between every character\n * → \"and expensive\" becomes a\\s*n\\s*d\\s*e\\s*x\\s*p\\s*e\\s*n\\s*s\\s*i\\s*v\\s*e\n * → Matches regardless of whitespace differences in PDF text\n *\n * For queries >= 200 chars:\n * - Split by whitespace, join with \\s+\n */\nfunction buildFlexibleRegex(\n query: string,\n options: SearchOptions\n): RegExp | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const flexibleWhitespace = options.flexibleWhitespace ?? true;\n\n if (!flexibleWhitespace) {\n // Simple literal search\n const pattern = escapeRegex(trimmed);\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Remove all whitespace chars from query\n const chars = [...trimmed].filter((c) => !/\\s/.test(c));\n if (chars.length === 0) return null;\n\n if (chars.length > 200) {\n // Fallback: flexible between tokens only\n const tokens = trimmed.split(/\\s+/);\n const pattern = tokens.map((t) => escapeRegex(t)).join('\\\\s+');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Insert \\s* between every character\n const pattern = chars.map((c) => escapeRegex(c)).join('\\\\s*');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n}\n\ninterface FuzzyMatch {\n start: number;\n end: number;\n distance: number;\n}\n\n/**\n * Semi-global Levenshtein alignment for approximate substring matching.\n *\n * Finds all positions in `text` where a substring has edit distance ≤ maxErrors\n * from `query`. Uses O(m) space with single-column DP.\n *\n * Semi-global: first DP column = 0 (match can start anywhere in text),\n * but the full query must be covered.\n */\nfunction fuzzySearchText(\n text: string,\n query: string,\n maxErrors: number\n): FuzzyMatch[] {\n const n = text.length;\n const m = query.length;\n if (m === 0) return [];\n if (n === 0) return [];\n\n // DP: prev[j] = min edit distance to match query[0..j-1] ending at current text pos\n // Semi-global: prev[0] = 0 for all text positions (free start)\n let prev = new Uint32Array(m + 1);\n for (let j = 0; j <= m; j++) prev[j] = j;\n\n // Store full DP matrix columns for traceback\n const columns: Uint32Array[] = [prev.slice()];\n\n // Track match end positions\n const endPositions: Array<{ col: number; distance: number }> = [];\n\n for (let i = 1; i <= n; i++) {\n const curr = new Uint32Array(m + 1);\n curr[0] = 0; // semi-global: free start position\n for (let j = 1; j <= m; j++) {\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n prev[j] + 1, // deletion\n curr[j - 1] + 1, // insertion\n prev[j - 1] + cost // substitution\n );\n }\n columns.push(curr.slice());\n\n if (curr[m] <= maxErrors) {\n endPositions.push({ col: i, distance: curr[m] });\n }\n prev = curr;\n }\n\n if (endPositions.length === 0) return [];\n\n // Traceback to find start position for each match end\n const rawMatches: FuzzyMatch[] = [];\n for (const { col: endCol, distance } of endPositions) {\n // Trace back through the DP matrix to find where the match starts\n let j = m;\n let i = endCol;\n while (j > 0 && i > 0) {\n const c = columns[i];\n const p = columns[i - 1];\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n if (c[j] === p[j - 1] + cost) {\n // substitution or match — move diagonally\n i--;\n j--;\n } else if (c[j] === p[j] + 1) {\n // deletion from text — move left in text\n i--;\n } else {\n // insertion into text — move up in query\n j--;\n }\n }\n rawMatches.push({ start: i, end: endCol, distance });\n }\n\n // Merge overlapping matches, keeping the one with lowest distance\n if (rawMatches.length === 0) return [];\n rawMatches.sort((a, b) => a.start - b.start || a.distance - b.distance);\n\n const merged: FuzzyMatch[] = [rawMatches[0]];\n for (let i = 1; i < rawMatches.length; i++) {\n const prev = merged[merged.length - 1];\n const curr = rawMatches[i];\n if (curr.start < prev.end) {\n // Overlapping — keep the one with lower distance\n if (curr.distance < prev.distance) {\n merged[merged.length - 1] = curr;\n }\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Build fullText and charMap from spans.\n */\nfunction buildTextAndCharMap(spans: SpanData[]) {\n let fullText = '';\n const charMap: CharMapEntry[] = [];\n spans.forEach((s, si) => {\n for (let ci = 0; ci < s.text.length; ci++) {\n charMap.push({ spanIdx: si, charIdx: ci });\n fullText += s.text[ci];\n }\n });\n return { fullText, charMap };\n}\n\n/**\n * Map a start/end range in fullText to MatchRange[] via charMap.\n */\nfunction mapToSpanRanges(\n start: number,\n end: number,\n charMap: CharMapEntry[]\n): MatchRange[] {\n const range: MatchRange[] = [];\n for (let k = start; k < end; k++) {\n const cm = charMap[k];\n const last = range[range.length - 1];\n if (last && last.spanIdx === cm.spanIdx && last.end === cm.charIdx) {\n last.end = cm.charIdx + 1;\n } else {\n range.push({ spanIdx: cm.spanIdx, start: cm.charIdx, end: cm.charIdx + 1 });\n }\n }\n return range;\n}\n\n/**\n * Search for text across page spans using charMap-based matching.\n *\n * Algorithm:\n * 1. Concatenate all span texts into one string (fullText)\n * 2. Build charMap: charMap[i] = { spanIdx, charIdx } for each char in fullText\n * 3. Run regex or fuzzy search on fullText\n * 4. Map each match back to span ranges via charMap\n */\nexport function searchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions = {}\n): MatchRange[][] {\n const trimmed = query.trim();\n if (!trimmed) return [];\n\n if (options.fuzzy) {\n return fuzzySearchPage(spans, trimmed, options);\n }\n\n const regex = buildFlexibleRegex(query, options);\n if (!regex) return [];\n\n const { fullText, charMap } = buildTextAndCharMap(spans);\n\n // Find all regex matches\n const allMatchRanges: MatchRange[][] = [];\n let m: RegExpExecArray | null;\n regex.lastIndex = 0;\n\n while ((m = regex.exec(fullText)) !== null) {\n allMatchRanges.push(mapToSpanRanges(m.index, m.index + m[0].length, charMap));\n if (m[0].length === 0) regex.lastIndex++;\n }\n\n return allMatchRanges;\n}\n\nfunction fuzzySearchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions\n): MatchRange[][] {\n const { fullText, charMap } = buildTextAndCharMap(spans);\n if (fullText.length === 0) return [];\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const threshold = options.fuzzyThreshold ?? 0.6;\n\n const searchText = isCaseSensitive ? fullText : fullText.toLowerCase();\n const searchQuery = isCaseSensitive ? query : query.toLowerCase();\n\n // Strip whitespace from query for matching\n const strippedQuery = searchQuery.replace(/\\s+/g, '');\n if (strippedQuery.length === 0) return [];\n\n const maxErrors = Math.floor(strippedQuery.length * (1 - threshold));\n const matches = fuzzySearchText(searchText, strippedQuery, maxErrors);\n\n return matches.map((m) => mapToSpanRanges(m.start, m.end, charMap));\n}\n","import type { SearchMatch, SpanData, PageData } from '../types';\nimport type { MatchRange } from './SearchEngine';\n\n/**\n * Manages cross-span highlighting using the charMap approach.\n *\n * Algorithm (from demo):\n * 1. Group match ranges by spanIdx\n * 2. For each affected span, replace textContent with a DocumentFragment:\n * - Plain text nodes for non-matching parts\n * - <mark> elements for matching parts\n * 3. Collect marks per match for navigation\n */\nexport class HighlightManager {\n private matches: SearchMatch[] = [];\n private currentMatch = -1;\n private highlightClass: string;\n private activeHighlightClass: string;\n\n /** Whether to auto-scroll to the active match. Defaults to true. */\n autoScroll = true;\n\n constructor(highlightClass: string, activeHighlightClass: string) {\n this.highlightClass = highlightClass;\n this.activeHighlightClass = activeHighlightClass;\n }\n\n /**\n * Apply highlights for all matches on a page.\n * Returns the SearchMatch[] (array of mark groups).\n *\n * @param classPerMatch - Optional per-match CSS class names (for multi-context search).\n * When provided, `classPerMatch[i]` is the CSS class for `matchRanges[i]`.\n * When omitted, all matches use the default highlight class.\n */\n applyHighlights(\n pageSpans: SpanData[],\n matchRanges: MatchRange[][],\n classPerMatch?: string[]\n ): SearchMatch[] {\n if (!matchRanges.length) return [];\n\n // Group ranges by spanIdx, keeping track of which match they belong to\n const spanRanges: Record<\n number,\n { start: number; end: number; matchIdx: number }[]\n > = {};\n\n matchRanges.forEach((range, mi) => {\n range.forEach((r) => {\n if (!spanRanges[r.spanIdx]) spanRanges[r.spanIdx] = [];\n spanRanges[r.spanIdx].push({ start: r.start, end: r.end, matchIdx: mi });\n });\n });\n\n // Collect marks per match\n const matchMarks: HTMLElement[][] = matchRanges.map(() => []);\n\n // For each affected span, rebuild DOM with highlights\n for (const siStr of Object.keys(spanRanges)) {\n const si = parseInt(siStr, 10);\n const s = pageSpans[si];\n const ranges = spanRanges[si].sort((a, b) => a.start - b.start);\n\n const frag = document.createDocumentFragment();\n let last = 0;\n\n for (const r of ranges) {\n const actualStart = Math.max(r.start, last);\n\n // Add plain text before highlight\n if (actualStart > last) {\n frag.appendChild(document.createTextNode(s.text.slice(last, actualStart)));\n }\n\n // Add highlight mark\n if (actualStart < r.end) {\n const mark = document.createElement('mark');\n mark.className = classPerMatch?.[r.matchIdx] ?? this.highlightClass;\n mark.textContent = s.text.slice(actualStart, r.end);\n frag.appendChild(mark);\n matchMarks[r.matchIdx].push(mark);\n }\n\n last = Math.max(last, r.end);\n }\n\n // Add remaining plain text\n if (last < s.text.length) {\n frag.appendChild(document.createTextNode(s.text.slice(last)));\n }\n\n // Replace span content\n s.el.textContent = '';\n s.el.appendChild(frag);\n }\n\n return matchMarks\n .filter((marks) => marks.length > 0)\n .map((marks) => ({ marks }));\n }\n\n /**\n * Add matches to the global list.\n */\n addMatches(newMatches: SearchMatch[]): void {\n this.matches.push(...newMatches);\n }\n\n /**\n * Clear all highlights and restore original span text.\n */\n clearHighlights(allPageData: PageData[]): void {\n allPageData.forEach((pd) => {\n pd.spans.forEach((s) => {\n s.el.textContent = s.text;\n });\n });\n this.matches = [];\n this.currentMatch = -1;\n }\n\n /**\n * Set active match by index. Applies active CSS class and scrolls into view.\n */\n setActiveMatch(index: number): void {\n // Remove active class from previous\n if (this.currentMatch >= 0 && this.currentMatch < this.matches.length) {\n this.matches[this.currentMatch].marks.forEach((m) =>\n m.classList.remove(this.activeHighlightClass)\n );\n }\n\n this.currentMatch = index;\n\n if (index >= 0 && index < this.matches.length) {\n this.matches[index].marks.forEach((m) =>\n m.classList.add(this.activeHighlightClass)\n );\n // Scroll first mark into view\n if (this.autoScroll) {\n this.matches[index].marks[0]?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n }\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n next(): number {\n if (this.matches.length === 0) return -1;\n const newIdx = (this.currentMatch + 1) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prev(): number {\n if (this.matches.length === 0) return -1;\n const newIdx =\n (this.currentMatch - 1 + this.matches.length) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n getCurrentIndex(): number {\n return this.currentMatch;\n }\n\n getTotal(): number {\n return this.matches.length;\n }\n\n getMatches(): SearchMatch[] {\n return this.matches;\n }\n}\n","import { EventEmitter } from './EventEmitter';\nimport { PDFRenderer } from './PDFRenderer';\nimport { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, ZOOM_STEP, MIN_SCALE, MAX_SCALE, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type {\n PDFSearchViewerOptions,\n SearchOptions,\n SearchContext,\n PDFSearchViewerEventMap,\n PageData,\n} from '../types';\n\nexport type PDFSource = File | ArrayBuffer | Uint8Array | string;\n\n/**\n * Main PDF viewer with search and highlight functionality.\n *\n * Usage:\n * ```js\n * import * as pdfjsLib from 'pdfjs-dist';\n * import { PDFSearchViewer } from 'pdf-search-highlight';\n *\n * const viewer = new PDFSearchViewer(container, pdfjsLib, {\n * classNames: {\n * page: 'my-page',\n * highlight: 'my-highlight',\n * activeHighlight: 'my-active',\n * }\n * });\n * await viewer.loadPDF(file);\n * viewer.search('hello');\n * viewer.nextMatch();\n * ```\n */\nexport class PDFSearchViewer extends EventEmitter<PDFSearchViewerEventMap> {\n private renderer: PDFRenderer;\n private highlightManager: HighlightManager;\n private pageData: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n private destroyed = false;\n\n constructor(\n container: HTMLElement,\n pdfjsLib: any,\n options: PDFSearchViewerOptions = {}\n ) {\n super();\n\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n\n this.renderer = new PDFRenderer(container, options);\n this.renderer.setPdfjsLib(pdfjsLib);\n this.highlightManager = new HighlightManager(\n cls.highlight,\n cls.activeHighlight\n );\n if (options.autoScroll !== undefined) {\n this.highlightManager.autoScroll = options.autoScroll;\n }\n }\n\n /**\n * Load and render a PDF document.\n */\n async loadPDF(source: PDFSource): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n try {\n await this.renderer.loadDocument(source);\n this.pageData = await this.renderer.renderAllPages();\n const pageCount = this.renderer.getPageCount();\n this.emit('load', { pageCount });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', { error, context: 'loadPDF' });\n throw error;\n }\n }\n\n /**\n * Search for text across all pages.\n * Clears previous highlights and creates new ones.\n */\n search(query: string, options: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n // Clear previous highlights\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.emit('search', { query, total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n // Search each page and apply highlights\n for (const pd of this.pageData) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n // Auto-activate first match\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('search', { query, total });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different color (highlight-0, highlight-1, ...).\n * Navigation (nextMatch/prevMatch) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n this.highlightManager.clearHighlights(this.pageData);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.emit('searchmultiple', { contexts, total: 0, totalsPerContext: contexts.map(() => 0) });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n const totalsPerContext = new Array(validContexts.length).fill(0);\n\n for (const pd of this.pageData) {\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n const contextPerMatch: number[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n contextPerMatch.push(ci);\n }\n }\n\n // Sort all matches by document position\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // Count per context\n for (const i of indices) {\n totalsPerContext[contextPerMatch[i]]++;\n }\n\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('searchmultiple', { contexts, total, totalsPerContext });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n nextMatch(): number {\n const idx = this.highlightManager.next();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prevMatch(): number {\n const idx = this.highlightManager.prev();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Clear all search highlights.\n */\n clearSearch(): void {\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.emit('search', { query: '', total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n }\n\n /** Get the current scale setting. */\n getScale(): number | 'auto' {\n return this.renderer.getScale();\n }\n\n /** Set scale and re-render. Preserves current search state. */\n async setScale(scale: number | 'auto'): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n this.renderer.setScale(scale);\n await this.rerender();\n this.emit('zoom', { scale: this.renderer.getEffectiveScale() });\n }\n\n /** Zoom in by one step. */\n async zoomIn(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.min(current + ZOOM_STEP, MAX_SCALE);\n await this.setScale(newScale);\n }\n\n /** Zoom out by one step. */\n async zoomOut(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.max(current - ZOOM_STEP, MIN_SCALE);\n await this.setScale(newScale);\n }\n\n /** Download the currently loaded PDF. */\n async download(filename?: string): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n await this.renderer.download(filename);\n }\n\n private resolveCurrentScale(): number {\n const s = this.renderer.getScale();\n return s === 'auto' ? this.renderer.getEffectiveScale() : s;\n }\n\n private async rerender(): Promise<void> {\n this.highlightManager.clearHighlights(this.pageData);\n this.pageData = await this.renderer.renderAllPages();\n\n if (this.lastIsMultiContext && this.lastContexts.length > 0) {\n this.searchMultiple(this.lastContexts, this.lastSearchOptions);\n } else if (this.lastQuery.trim()) {\n this.search(this.lastQuery, this.lastSearchOptions);\n }\n }\n\n /**\n * Get total number of pages.\n */\n getPageCount(): number {\n return this.renderer.getPageCount();\n }\n\n /**\n * Get current active match index (0-based). -1 if none.\n */\n getCurrentMatchIndex(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /**\n * Get total number of matches.\n */\n getMatchCount(): number {\n return this.highlightManager.getTotal();\n }\n\n /**\n * Destroy the viewer, release all resources.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.highlightManager.clearHighlights(this.pageData);\n this.renderer.cleanup();\n this.removeAllListeners();\n this.pageData = [];\n }\n}\n","import { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type { SearchOptions, ClassNames, PageData, SearchContext } from '../types';\n\nexport interface SearchControllerOptions {\n classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;\n /** Auto-scroll to active match on search/next/prev. Defaults to true. */\n autoScroll?: boolean;\n}\n\n/**\n * Headless search + highlight controller.\n * Does NOT render PDF — works with any PageData[] you provide.\n *\n * Use this when you want full control over:\n * - Where the PDF is rendered\n * - Where the search UI lives\n * - How search results are displayed\n *\n * Usage:\n * ```js\n * import { PDFRenderer, SearchController } from 'pdf-search-highlight';\n *\n * // Render PDF wherever you want\n * const renderer = new PDFRenderer(pdfContainer, pdfjsLib, {});\n * const pages = await renderer.renderAllPages();\n *\n * // Search controller — no UI, just logic\n * const search = new SearchController();\n * search.setPages(pages);\n *\n * // Wire up your own UI\n * input.oninput = () => search.search(input.value);\n * nextBtn.onclick = () => search.next();\n * prevBtn.onclick = () => search.prev();\n *\n * // React to changes\n * search.onChange = ({ current, total }) => {\n * label.textContent = total > 0 ? `${current + 1}/${total}` : '';\n * };\n * ```\n */\nexport class SearchController {\n private highlightManager: HighlightManager;\n private pages: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n\n /** Callback fired when match state changes (search, next, prev, clear). */\n onChange: ((state: { current: number; total: number; query: string }) => void) | null = null;\n\n constructor(options: SearchControllerOptions = {}) {\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);\n if (options.autoScroll !== undefined) {\n this.highlightManager.autoScroll = options.autoScroll;\n }\n }\n\n /** Get or set auto-scroll behavior. */\n get autoScroll(): boolean {\n return this.highlightManager.autoScroll;\n }\n set autoScroll(value: boolean) {\n this.highlightManager.autoScroll = value;\n }\n\n /**\n * Set the pages to search on.\n * Call this after rendering PDF pages.\n */\n setPages(pages: PageData[]): void {\n const savedQuery = this.lastQuery;\n const savedOptions = this.lastSearchOptions;\n const savedContexts = [...this.lastContexts];\n const savedIsMulti = this.lastIsMultiContext;\n\n this.clear();\n this.pages = pages;\n\n // Re-apply search if there was an active query (e.g. after zoom)\n if (savedIsMulti && savedContexts.length > 0) {\n this.searchMultiple(savedContexts, savedOptions);\n } else if (savedQuery.trim()) {\n this.search(savedQuery, savedOptions);\n }\n }\n\n /**\n * Search for text across all pages.\n * Returns total number of matches.\n */\n search(query: string, options: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different CSS class (highlight-0, highlight-1, ...).\n * Navigation (next/prev) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n // 1. Search all contexts on this page\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n }\n }\n\n // 2. Sort all matches by document position (span index, then char offset)\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // 3. Apply highlights with per-match classes\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /** Navigate to next match. Returns new index. */\n next(): number {\n const idx = this.highlightManager.next();\n this.notify();\n return idx;\n }\n\n /** Navigate to previous match. Returns new index. */\n prev(): number {\n const idx = this.highlightManager.prev();\n this.notify();\n return idx;\n }\n\n /** Go to a specific match by index. */\n goTo(index: number): void {\n this.highlightManager.setActiveMatch(index);\n this.notify();\n }\n\n /** Clear all highlights. */\n clear(): void {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.notify();\n }\n\n /** Current match index (0-based). -1 if none. */\n get current(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /** Total number of matches. */\n get total(): number {\n return this.highlightManager.getTotal();\n }\n\n /** Last searched query. */\n get query(): string {\n return this.lastQuery;\n }\n\n /** Last searched contexts (for multi-context search). */\n get contexts(): SearchContext[] {\n return this.lastContexts;\n }\n\n private notify(): void {\n this.onChange?.({\n current: this.highlightManager.getCurrentIndex(),\n total: this.highlightManager.getTotal(),\n query: this.lastQuery,\n });\n }\n}\n"],"mappings":"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","names":["pattern","prev"]}
@@ -379,6 +379,8 @@ var HighlightManager = class {
379
379
  constructor(highlightClass, activeHighlightClass) {
380
380
  this.matches = [];
381
381
  this.currentMatch = -1;
382
+ /** Whether to auto-scroll to the active match. Defaults to true. */
383
+ this.autoScroll = true;
382
384
  this.highlightClass = highlightClass;
383
385
  this.activeHighlightClass = activeHighlightClass;
384
386
  }
@@ -460,10 +462,12 @@ var HighlightManager = class {
460
462
  this.matches[index].marks.forEach(
461
463
  (m) => m.classList.add(this.activeHighlightClass)
462
464
  );
463
- _optionalChain([this, 'access', _18 => _18.matches, 'access', _19 => _19[index], 'access', _20 => _20.marks, 'access', _21 => _21[0], 'optionalAccess', _22 => _22.scrollIntoView, 'call', _23 => _23({
464
- behavior: "smooth",
465
- block: "center"
466
- })]);
465
+ if (this.autoScroll) {
466
+ _optionalChain([this, 'access', _18 => _18.matches, 'access', _19 => _19[index], 'access', _20 => _20.marks, 'access', _21 => _21[0], 'optionalAccess', _22 => _22.scrollIntoView, 'call', _23 => _23({
467
+ behavior: "smooth",
468
+ block: "center"
469
+ })]);
470
+ }
467
471
  }
468
472
  }
469
473
  /**
@@ -512,6 +516,9 @@ var PDFSearchViewer = class extends EventEmitter {
512
516
  cls.highlight,
513
517
  cls.activeHighlight
514
518
  );
519
+ if (options.autoScroll !== void 0) {
520
+ this.highlightManager.autoScroll = options.autoScroll;
521
+ }
515
522
  }
516
523
  /**
517
524
  * Load and render a PDF document.
@@ -744,6 +751,16 @@ var SearchController = class {
744
751
  this.onChange = null;
745
752
  const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };
746
753
  this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);
754
+ if (options.autoScroll !== void 0) {
755
+ this.highlightManager.autoScroll = options.autoScroll;
756
+ }
757
+ }
758
+ /** Get or set auto-scroll behavior. */
759
+ get autoScroll() {
760
+ return this.highlightManager.autoScroll;
761
+ }
762
+ set autoScroll(value) {
763
+ this.highlightManager.autoScroll = value;
747
764
  }
748
765
  /**
749
766
  * Set the pages to search on.
@@ -907,4 +924,4 @@ var SearchController = class {
907
924
 
908
925
 
909
926
  exports.EventEmitter = EventEmitter; exports.DEFAULT_CLASS_NAMES = DEFAULT_CLASS_NAMES; exports.DEFAULT_SCALE = DEFAULT_SCALE; exports.DEFAULT_PAGE_GAP = DEFAULT_PAGE_GAP; exports.ZOOM_STEP = ZOOM_STEP; exports.MIN_SCALE = MIN_SCALE; exports.MAX_SCALE = MAX_SCALE; exports.MULTI_CONTEXT_COLOR_COUNT = MULTI_CONTEXT_COLOR_COUNT; exports.PDFRenderer = PDFRenderer; exports.searchPage = searchPage; exports.HighlightManager = HighlightManager; exports.PDFSearchViewer = PDFSearchViewer; exports.SearchController = SearchController;
910
- //# sourceMappingURL=chunk-GDUHZCFR.cjs.map
927
+ //# sourceMappingURL=chunk-Z2S4WPR7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/hoangnguyen/Desktop/labs/pdf-search-highlight/dist/chunk-Z2S4WPR7.cjs","../src/core/EventEmitter.ts","../src/core/constants.ts","../src/core/PDFRenderer.ts","../src/core/SearchEngine.ts","../src/core/HighlightManager.ts","../src/core/PDFSearchViewer.ts","../src/core/SearchController.ts"],"names":[],"mappings":"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","file":"/Users/hoangnguyen/Desktop/labs/pdf-search-highlight/dist/chunk-Z2S4WPR7.cjs","sourcesContent":[null,"type Listener<T> = (data: T) => void;\n\nexport class EventEmitter<EventMap extends { [key: string]: unknown }> {\n private listeners = new Map<keyof EventMap, Set<Listener<any>>>();\n\n on<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n protected emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {\n this.listeners.get(event)?.forEach((fn) => fn(data));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import type { ClassNames } from '../types';\n\nexport const DEFAULT_CLASS_NAMES: Required<ClassNames> = {\n container: 'psh-container',\n page: 'psh-page',\n canvas: 'psh-canvas',\n textLayer: 'psh-text-layer',\n pageLabel: 'psh-page-label',\n highlight: 'highlight',\n activeHighlight: 'active',\n};\n\nexport const DEFAULT_SCALE = 'auto' as number | 'auto';\nexport const DEFAULT_PAGE_GAP = 20;\n\nexport const ZOOM_STEP = 0.25;\nexport const MIN_SCALE = 0.25;\nexport const MAX_SCALE = 5;\n\n/** Number of predefined multi-context highlight colors (cycles with modulo). */\nexport const MULTI_CONTEXT_COLOR_COUNT = 8;\n","import type { PDFSearchViewerOptions, ClassNames, PageData, SpanData } from '../types';\nimport { DEFAULT_CLASS_NAMES, DEFAULT_SCALE, DEFAULT_PAGE_GAP } from './constants';\n\n// pdfjs-dist types\ntype PDFDocumentProxy = any;\ntype PDFPageProxy = any;\n\n/**\n * Renders PDF pages into a container using canvas + text layer.\n *\n * Text layer approach (matching demo):\n * - Extract text content from each page\n * - Create absolutely-positioned <span> elements overlaying the canvas\n * - Position spans using the transform matrix from pdf.js\n * - Spans are transparent (for text selection) but allow DOM-based search/highlight\n */\nexport class PDFRenderer {\n private container: HTMLElement;\n private scale: number | 'auto';\n private pageGap: number;\n private cls: Required<ClassNames>;\n private workerSrc?: string;\n private pdfDoc: PDFDocumentProxy | null = null;\n private pageData: PageData[] = [];\n private pdfjsLib: any = null;\n private effectiveScale: number = 1;\n\n constructor(container: HTMLElement, options: PDFSearchViewerOptions) {\n this.container = container;\n this.scale = options.scale ?? DEFAULT_SCALE;\n this.pageGap = options.pageGap ?? DEFAULT_PAGE_GAP;\n this.workerSrc = options.workerSrc;\n this.cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n }\n\n /**\n * Set the pdfjs-dist library reference.\n * Must be called before loadDocument.\n */\n setPdfjsLib(lib: any): void {\n this.pdfjsLib = lib;\n if (this.workerSrc) {\n lib.GlobalWorkerOptions.workerSrc = this.workerSrc;\n }\n }\n\n /**\n * Load a PDF from File, ArrayBuffer, URL string, or Uint8Array.\n */\n async loadDocument(\n source: File | ArrayBuffer | Uint8Array | string\n ): Promise<number> {\n if (!this.pdfjsLib) {\n throw new Error(\n 'pdfjs-dist not set. Call setPdfjsLib(pdfjsLib) before loading a document.'\n );\n }\n\n this.cleanup();\n\n let data: ArrayBuffer | Uint8Array | { url: string };\n if (source instanceof File) {\n data = await source.arrayBuffer();\n } else if (typeof source === 'string') {\n data = { url: source };\n } else {\n data = source;\n }\n\n const loadingTask = this.pdfjsLib.getDocument({ data });\n this.pdfDoc = await loadingTask.promise;\n return this.pdfDoc.numPages;\n }\n\n /**\n * Render all pages into the container.\n * Preserves scroll position across re-renders (e.g. zoom).\n * Returns PageData[] for search/highlight.\n */\n async renderAllPages(): Promise<PageData[]> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n // Save scroll position relative to total content height\n const prevScrollTop = this.container.scrollTop;\n const prevScrollHeight = this.container.scrollHeight || 1;\n const scrollRatio = prevScrollTop / prevScrollHeight;\n\n this.container.innerHTML = '';\n this.container.classList.add(this.cls.container);\n this.pageData = [];\n\n const numPages = this.pdfDoc.numPages;\n\n for (let i = 1; i <= numPages; i++) {\n const page = await this.pdfDoc.getPage(i);\n const pd = await this.renderPage(page, i, numPages);\n this.pageData.push(pd);\n }\n\n // Restore scroll position proportionally\n if (prevScrollTop > 0) {\n this.container.scrollTop = scrollRatio * this.container.scrollHeight;\n }\n\n return this.pageData;\n }\n\n private async renderPage(\n page: PDFPageProxy,\n pageNum: number,\n totalPages: number\n ): Promise<PageData> {\n const scale = this.calculateScale(page);\n if (pageNum === 1) this.effectiveScale = scale;\n const vp = page.getViewport({ scale });\n\n // Page container\n const container = document.createElement('div');\n container.className = this.cls.page;\n container.style.position = 'relative';\n container.style.width = vp.width + 'px';\n container.style.height = vp.height + 'px';\n container.style.margin = '0 auto';\n container.style.marginBottom = this.pageGap + 'px';\n container.style.overflow = 'hidden';\n container.dataset.page = String(pageNum);\n\n // Canvas (2x for retina)\n const canvas = document.createElement('canvas');\n canvas.className = this.cls.canvas;\n canvas.width = vp.width * 2;\n canvas.height = vp.height * 2;\n canvas.style.width = vp.width + 'px';\n canvas.style.height = vp.height + 'px';\n canvas.style.display = 'block';\n const ctx = canvas.getContext('2d')!;\n ctx.scale(2, 2);\n await page.render({ canvasContext: ctx, viewport: vp }).promise;\n\n // Text layer\n const textLayer = document.createElement('div');\n textLayer.className = this.cls.textLayer;\n textLayer.style.position = 'absolute';\n textLayer.style.top = '0';\n textLayer.style.left = '0';\n textLayer.style.right = '0';\n textLayer.style.bottom = '0';\n textLayer.style.overflow = 'hidden';\n textLayer.style.lineHeight = '1';\n\n const tc = await page.getTextContent();\n const spans: SpanData[] = [];\n\n for (const item of tc.items) {\n if (!item.str && !item.hasEOL) continue;\n\n const tx = this.pdfjsLib.Util.transform(vp.transform, item.transform);\n const span = document.createElement('span');\n span.textContent = item.str || '';\n const fh = Math.hypot(tx[2], tx[3]);\n span.style.position = 'absolute';\n span.style.left = tx[4] + 'px';\n span.style.top = (tx[5] - fh) + 'px';\n span.style.fontSize = fh + 'px';\n span.style.color = 'transparent';\n span.style.whiteSpace = 'pre';\n span.style.cursor = 'text';\n span.style.transformOrigin = '0% 0%';\n if (item.fontName) span.style.fontFamily = item.fontName;\n\n const sw = tx[0] / fh;\n if (Math.abs(sw - 1) > 0.01) {\n span.style.transform = `scaleX(${sw})`;\n }\n\n textLayer.appendChild(span);\n spans.push({\n el: span,\n text: item.str || '',\n hasEOL: !!item.hasEOL,\n });\n }\n\n container.appendChild(canvas);\n container.appendChild(textLayer);\n this.container.appendChild(container);\n\n // Page label\n const label = document.createElement('div');\n label.className = this.cls.pageLabel;\n label.textContent = `Page ${pageNum} / ${totalPages}`;\n this.container.appendChild(label);\n\n return { container, spans };\n }\n\n private calculateScale(page: PDFPageProxy): number {\n if (this.scale !== 'auto' && typeof this.scale === 'number') {\n return this.scale;\n }\n const defaultVp = page.getViewport({ scale: 1 });\n const containerWidth = this.container.clientWidth || 800;\n return Math.min(containerWidth / defaultVp.width, 2);\n }\n\n /** Set the scale for subsequent renders. */\n setScale(scale: number | 'auto'): void {\n this.scale = scale;\n }\n\n /** Get the configured scale setting. */\n getScale(): number | 'auto' {\n return this.scale;\n }\n\n /** Get the actual numeric scale used in the last render. */\n getEffectiveScale(): number {\n return this.effectiveScale;\n }\n\n /**\n * Download the currently loaded PDF.\n */\n async download(filename: string = 'document.pdf'): Promise<void> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n const data = await this.pdfDoc.getData();\n const blob = new Blob([data as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n\n getClassNames(): Required<ClassNames> {\n return this.cls;\n }\n\n getPageData(): PageData[] {\n return this.pageData;\n }\n\n getPageCount(): number {\n return this.pdfDoc?.numPages ?? 0;\n }\n\n cleanup(): void {\n this.pdfDoc?.destroy();\n this.pdfDoc = null;\n this.pageData = [];\n this.container.innerHTML = '';\n }\n}\n","import type { SearchOptions, SpanData } from '../types';\n\nexport interface CharMapEntry {\n spanIdx: number;\n charIdx: number;\n}\n\nexport interface MatchRange {\n spanIdx: number;\n start: number;\n end: number;\n}\n\nexport interface SearchResult {\n /** Array of span ranges for each match */\n matchRanges: MatchRange[][];\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Build a flexible regex from query.\n *\n * For queries < 200 chars (after removing whitespace):\n * - Strip all whitespace from query\n * - Insert \\s* between every character\n * → \"and expensive\" becomes a\\s*n\\s*d\\s*e\\s*x\\s*p\\s*e\\s*n\\s*s\\s*i\\s*v\\s*e\n * → Matches regardless of whitespace differences in PDF text\n *\n * For queries >= 200 chars:\n * - Split by whitespace, join with \\s+\n */\nfunction buildFlexibleRegex(\n query: string,\n options: SearchOptions\n): RegExp | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const flexibleWhitespace = options.flexibleWhitespace ?? true;\n\n if (!flexibleWhitespace) {\n // Simple literal search\n const pattern = escapeRegex(trimmed);\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Remove all whitespace chars from query\n const chars = [...trimmed].filter((c) => !/\\s/.test(c));\n if (chars.length === 0) return null;\n\n if (chars.length > 200) {\n // Fallback: flexible between tokens only\n const tokens = trimmed.split(/\\s+/);\n const pattern = tokens.map((t) => escapeRegex(t)).join('\\\\s+');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Insert \\s* between every character\n const pattern = chars.map((c) => escapeRegex(c)).join('\\\\s*');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n}\n\ninterface FuzzyMatch {\n start: number;\n end: number;\n distance: number;\n}\n\n/**\n * Semi-global Levenshtein alignment for approximate substring matching.\n *\n * Finds all positions in `text` where a substring has edit distance ≤ maxErrors\n * from `query`. Uses O(m) space with single-column DP.\n *\n * Semi-global: first DP column = 0 (match can start anywhere in text),\n * but the full query must be covered.\n */\nfunction fuzzySearchText(\n text: string,\n query: string,\n maxErrors: number\n): FuzzyMatch[] {\n const n = text.length;\n const m = query.length;\n if (m === 0) return [];\n if (n === 0) return [];\n\n // DP: prev[j] = min edit distance to match query[0..j-1] ending at current text pos\n // Semi-global: prev[0] = 0 for all text positions (free start)\n let prev = new Uint32Array(m + 1);\n for (let j = 0; j <= m; j++) prev[j] = j;\n\n // Store full DP matrix columns for traceback\n const columns: Uint32Array[] = [prev.slice()];\n\n // Track match end positions\n const endPositions: Array<{ col: number; distance: number }> = [];\n\n for (let i = 1; i <= n; i++) {\n const curr = new Uint32Array(m + 1);\n curr[0] = 0; // semi-global: free start position\n for (let j = 1; j <= m; j++) {\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n prev[j] + 1, // deletion\n curr[j - 1] + 1, // insertion\n prev[j - 1] + cost // substitution\n );\n }\n columns.push(curr.slice());\n\n if (curr[m] <= maxErrors) {\n endPositions.push({ col: i, distance: curr[m] });\n }\n prev = curr;\n }\n\n if (endPositions.length === 0) return [];\n\n // Traceback to find start position for each match end\n const rawMatches: FuzzyMatch[] = [];\n for (const { col: endCol, distance } of endPositions) {\n // Trace back through the DP matrix to find where the match starts\n let j = m;\n let i = endCol;\n while (j > 0 && i > 0) {\n const c = columns[i];\n const p = columns[i - 1];\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n if (c[j] === p[j - 1] + cost) {\n // substitution or match — move diagonally\n i--;\n j--;\n } else if (c[j] === p[j] + 1) {\n // deletion from text — move left in text\n i--;\n } else {\n // insertion into text — move up in query\n j--;\n }\n }\n rawMatches.push({ start: i, end: endCol, distance });\n }\n\n // Merge overlapping matches, keeping the one with lowest distance\n if (rawMatches.length === 0) return [];\n rawMatches.sort((a, b) => a.start - b.start || a.distance - b.distance);\n\n const merged: FuzzyMatch[] = [rawMatches[0]];\n for (let i = 1; i < rawMatches.length; i++) {\n const prev = merged[merged.length - 1];\n const curr = rawMatches[i];\n if (curr.start < prev.end) {\n // Overlapping — keep the one with lower distance\n if (curr.distance < prev.distance) {\n merged[merged.length - 1] = curr;\n }\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Build fullText and charMap from spans.\n */\nfunction buildTextAndCharMap(spans: SpanData[]) {\n let fullText = '';\n const charMap: CharMapEntry[] = [];\n spans.forEach((s, si) => {\n for (let ci = 0; ci < s.text.length; ci++) {\n charMap.push({ spanIdx: si, charIdx: ci });\n fullText += s.text[ci];\n }\n });\n return { fullText, charMap };\n}\n\n/**\n * Map a start/end range in fullText to MatchRange[] via charMap.\n */\nfunction mapToSpanRanges(\n start: number,\n end: number,\n charMap: CharMapEntry[]\n): MatchRange[] {\n const range: MatchRange[] = [];\n for (let k = start; k < end; k++) {\n const cm = charMap[k];\n const last = range[range.length - 1];\n if (last && last.spanIdx === cm.spanIdx && last.end === cm.charIdx) {\n last.end = cm.charIdx + 1;\n } else {\n range.push({ spanIdx: cm.spanIdx, start: cm.charIdx, end: cm.charIdx + 1 });\n }\n }\n return range;\n}\n\n/**\n * Search for text across page spans using charMap-based matching.\n *\n * Algorithm:\n * 1. Concatenate all span texts into one string (fullText)\n * 2. Build charMap: charMap[i] = { spanIdx, charIdx } for each char in fullText\n * 3. Run regex or fuzzy search on fullText\n * 4. Map each match back to span ranges via charMap\n */\nexport function searchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions = {}\n): MatchRange[][] {\n const trimmed = query.trim();\n if (!trimmed) return [];\n\n if (options.fuzzy) {\n return fuzzySearchPage(spans, trimmed, options);\n }\n\n const regex = buildFlexibleRegex(query, options);\n if (!regex) return [];\n\n const { fullText, charMap } = buildTextAndCharMap(spans);\n\n // Find all regex matches\n const allMatchRanges: MatchRange[][] = [];\n let m: RegExpExecArray | null;\n regex.lastIndex = 0;\n\n while ((m = regex.exec(fullText)) !== null) {\n allMatchRanges.push(mapToSpanRanges(m.index, m.index + m[0].length, charMap));\n if (m[0].length === 0) regex.lastIndex++;\n }\n\n return allMatchRanges;\n}\n\nfunction fuzzySearchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions\n): MatchRange[][] {\n const { fullText, charMap } = buildTextAndCharMap(spans);\n if (fullText.length === 0) return [];\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const threshold = options.fuzzyThreshold ?? 0.6;\n\n const searchText = isCaseSensitive ? fullText : fullText.toLowerCase();\n const searchQuery = isCaseSensitive ? query : query.toLowerCase();\n\n // Strip whitespace from query for matching\n const strippedQuery = searchQuery.replace(/\\s+/g, '');\n if (strippedQuery.length === 0) return [];\n\n const maxErrors = Math.floor(strippedQuery.length * (1 - threshold));\n const matches = fuzzySearchText(searchText, strippedQuery, maxErrors);\n\n return matches.map((m) => mapToSpanRanges(m.start, m.end, charMap));\n}\n","import type { SearchMatch, SpanData, PageData } from '../types';\nimport type { MatchRange } from './SearchEngine';\n\n/**\n * Manages cross-span highlighting using the charMap approach.\n *\n * Algorithm (from demo):\n * 1. Group match ranges by spanIdx\n * 2. For each affected span, replace textContent with a DocumentFragment:\n * - Plain text nodes for non-matching parts\n * - <mark> elements for matching parts\n * 3. Collect marks per match for navigation\n */\nexport class HighlightManager {\n private matches: SearchMatch[] = [];\n private currentMatch = -1;\n private highlightClass: string;\n private activeHighlightClass: string;\n\n /** Whether to auto-scroll to the active match. Defaults to true. */\n autoScroll = true;\n\n constructor(highlightClass: string, activeHighlightClass: string) {\n this.highlightClass = highlightClass;\n this.activeHighlightClass = activeHighlightClass;\n }\n\n /**\n * Apply highlights for all matches on a page.\n * Returns the SearchMatch[] (array of mark groups).\n *\n * @param classPerMatch - Optional per-match CSS class names (for multi-context search).\n * When provided, `classPerMatch[i]` is the CSS class for `matchRanges[i]`.\n * When omitted, all matches use the default highlight class.\n */\n applyHighlights(\n pageSpans: SpanData[],\n matchRanges: MatchRange[][],\n classPerMatch?: string[]\n ): SearchMatch[] {\n if (!matchRanges.length) return [];\n\n // Group ranges by spanIdx, keeping track of which match they belong to\n const spanRanges: Record<\n number,\n { start: number; end: number; matchIdx: number }[]\n > = {};\n\n matchRanges.forEach((range, mi) => {\n range.forEach((r) => {\n if (!spanRanges[r.spanIdx]) spanRanges[r.spanIdx] = [];\n spanRanges[r.spanIdx].push({ start: r.start, end: r.end, matchIdx: mi });\n });\n });\n\n // Collect marks per match\n const matchMarks: HTMLElement[][] = matchRanges.map(() => []);\n\n // For each affected span, rebuild DOM with highlights\n for (const siStr of Object.keys(spanRanges)) {\n const si = parseInt(siStr, 10);\n const s = pageSpans[si];\n const ranges = spanRanges[si].sort((a, b) => a.start - b.start);\n\n const frag = document.createDocumentFragment();\n let last = 0;\n\n for (const r of ranges) {\n const actualStart = Math.max(r.start, last);\n\n // Add plain text before highlight\n if (actualStart > last) {\n frag.appendChild(document.createTextNode(s.text.slice(last, actualStart)));\n }\n\n // Add highlight mark\n if (actualStart < r.end) {\n const mark = document.createElement('mark');\n mark.className = classPerMatch?.[r.matchIdx] ?? this.highlightClass;\n mark.textContent = s.text.slice(actualStart, r.end);\n frag.appendChild(mark);\n matchMarks[r.matchIdx].push(mark);\n }\n\n last = Math.max(last, r.end);\n }\n\n // Add remaining plain text\n if (last < s.text.length) {\n frag.appendChild(document.createTextNode(s.text.slice(last)));\n }\n\n // Replace span content\n s.el.textContent = '';\n s.el.appendChild(frag);\n }\n\n return matchMarks\n .filter((marks) => marks.length > 0)\n .map((marks) => ({ marks }));\n }\n\n /**\n * Add matches to the global list.\n */\n addMatches(newMatches: SearchMatch[]): void {\n this.matches.push(...newMatches);\n }\n\n /**\n * Clear all highlights and restore original span text.\n */\n clearHighlights(allPageData: PageData[]): void {\n allPageData.forEach((pd) => {\n pd.spans.forEach((s) => {\n s.el.textContent = s.text;\n });\n });\n this.matches = [];\n this.currentMatch = -1;\n }\n\n /**\n * Set active match by index. Applies active CSS class and scrolls into view.\n */\n setActiveMatch(index: number): void {\n // Remove active class from previous\n if (this.currentMatch >= 0 && this.currentMatch < this.matches.length) {\n this.matches[this.currentMatch].marks.forEach((m) =>\n m.classList.remove(this.activeHighlightClass)\n );\n }\n\n this.currentMatch = index;\n\n if (index >= 0 && index < this.matches.length) {\n this.matches[index].marks.forEach((m) =>\n m.classList.add(this.activeHighlightClass)\n );\n // Scroll first mark into view\n if (this.autoScroll) {\n this.matches[index].marks[0]?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n }\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n next(): number {\n if (this.matches.length === 0) return -1;\n const newIdx = (this.currentMatch + 1) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prev(): number {\n if (this.matches.length === 0) return -1;\n const newIdx =\n (this.currentMatch - 1 + this.matches.length) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n getCurrentIndex(): number {\n return this.currentMatch;\n }\n\n getTotal(): number {\n return this.matches.length;\n }\n\n getMatches(): SearchMatch[] {\n return this.matches;\n }\n}\n","import { EventEmitter } from './EventEmitter';\nimport { PDFRenderer } from './PDFRenderer';\nimport { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, ZOOM_STEP, MIN_SCALE, MAX_SCALE, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type {\n PDFSearchViewerOptions,\n SearchOptions,\n SearchContext,\n PDFSearchViewerEventMap,\n PageData,\n} from '../types';\n\nexport type PDFSource = File | ArrayBuffer | Uint8Array | string;\n\n/**\n * Main PDF viewer with search and highlight functionality.\n *\n * Usage:\n * ```js\n * import * as pdfjsLib from 'pdfjs-dist';\n * import { PDFSearchViewer } from 'pdf-search-highlight';\n *\n * const viewer = new PDFSearchViewer(container, pdfjsLib, {\n * classNames: {\n * page: 'my-page',\n * highlight: 'my-highlight',\n * activeHighlight: 'my-active',\n * }\n * });\n * await viewer.loadPDF(file);\n * viewer.search('hello');\n * viewer.nextMatch();\n * ```\n */\nexport class PDFSearchViewer extends EventEmitter<PDFSearchViewerEventMap> {\n private renderer: PDFRenderer;\n private highlightManager: HighlightManager;\n private pageData: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n private destroyed = false;\n\n constructor(\n container: HTMLElement,\n pdfjsLib: any,\n options: PDFSearchViewerOptions = {}\n ) {\n super();\n\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n\n this.renderer = new PDFRenderer(container, options);\n this.renderer.setPdfjsLib(pdfjsLib);\n this.highlightManager = new HighlightManager(\n cls.highlight,\n cls.activeHighlight\n );\n if (options.autoScroll !== undefined) {\n this.highlightManager.autoScroll = options.autoScroll;\n }\n }\n\n /**\n * Load and render a PDF document.\n */\n async loadPDF(source: PDFSource): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n try {\n await this.renderer.loadDocument(source);\n this.pageData = await this.renderer.renderAllPages();\n const pageCount = this.renderer.getPageCount();\n this.emit('load', { pageCount });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', { error, context: 'loadPDF' });\n throw error;\n }\n }\n\n /**\n * Search for text across all pages.\n * Clears previous highlights and creates new ones.\n */\n search(query: string, options: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n // Clear previous highlights\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.emit('search', { query, total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n // Search each page and apply highlights\n for (const pd of this.pageData) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n // Auto-activate first match\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('search', { query, total });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different color (highlight-0, highlight-1, ...).\n * Navigation (nextMatch/prevMatch) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n this.highlightManager.clearHighlights(this.pageData);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.emit('searchmultiple', { contexts, total: 0, totalsPerContext: contexts.map(() => 0) });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n const totalsPerContext = new Array(validContexts.length).fill(0);\n\n for (const pd of this.pageData) {\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n const contextPerMatch: number[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n contextPerMatch.push(ci);\n }\n }\n\n // Sort all matches by document position\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // Count per context\n for (const i of indices) {\n totalsPerContext[contextPerMatch[i]]++;\n }\n\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('searchmultiple', { contexts, total, totalsPerContext });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n nextMatch(): number {\n const idx = this.highlightManager.next();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prevMatch(): number {\n const idx = this.highlightManager.prev();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Clear all search highlights.\n */\n clearSearch(): void {\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.emit('search', { query: '', total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n }\n\n /** Get the current scale setting. */\n getScale(): number | 'auto' {\n return this.renderer.getScale();\n }\n\n /** Set scale and re-render. Preserves current search state. */\n async setScale(scale: number | 'auto'): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n this.renderer.setScale(scale);\n await this.rerender();\n this.emit('zoom', { scale: this.renderer.getEffectiveScale() });\n }\n\n /** Zoom in by one step. */\n async zoomIn(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.min(current + ZOOM_STEP, MAX_SCALE);\n await this.setScale(newScale);\n }\n\n /** Zoom out by one step. */\n async zoomOut(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.max(current - ZOOM_STEP, MIN_SCALE);\n await this.setScale(newScale);\n }\n\n /** Download the currently loaded PDF. */\n async download(filename?: string): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n await this.renderer.download(filename);\n }\n\n private resolveCurrentScale(): number {\n const s = this.renderer.getScale();\n return s === 'auto' ? this.renderer.getEffectiveScale() : s;\n }\n\n private async rerender(): Promise<void> {\n this.highlightManager.clearHighlights(this.pageData);\n this.pageData = await this.renderer.renderAllPages();\n\n if (this.lastIsMultiContext && this.lastContexts.length > 0) {\n this.searchMultiple(this.lastContexts, this.lastSearchOptions);\n } else if (this.lastQuery.trim()) {\n this.search(this.lastQuery, this.lastSearchOptions);\n }\n }\n\n /**\n * Get total number of pages.\n */\n getPageCount(): number {\n return this.renderer.getPageCount();\n }\n\n /**\n * Get current active match index (0-based). -1 if none.\n */\n getCurrentMatchIndex(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /**\n * Get total number of matches.\n */\n getMatchCount(): number {\n return this.highlightManager.getTotal();\n }\n\n /**\n * Destroy the viewer, release all resources.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.highlightManager.clearHighlights(this.pageData);\n this.renderer.cleanup();\n this.removeAllListeners();\n this.pageData = [];\n }\n}\n","import { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type { SearchOptions, ClassNames, PageData, SearchContext } from '../types';\n\nexport interface SearchControllerOptions {\n classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;\n /** Auto-scroll to active match on search/next/prev. Defaults to true. */\n autoScroll?: boolean;\n}\n\n/**\n * Headless search + highlight controller.\n * Does NOT render PDF — works with any PageData[] you provide.\n *\n * Use this when you want full control over:\n * - Where the PDF is rendered\n * - Where the search UI lives\n * - How search results are displayed\n *\n * Usage:\n * ```js\n * import { PDFRenderer, SearchController } from 'pdf-search-highlight';\n *\n * // Render PDF wherever you want\n * const renderer = new PDFRenderer(pdfContainer, pdfjsLib, {});\n * const pages = await renderer.renderAllPages();\n *\n * // Search controller — no UI, just logic\n * const search = new SearchController();\n * search.setPages(pages);\n *\n * // Wire up your own UI\n * input.oninput = () => search.search(input.value);\n * nextBtn.onclick = () => search.next();\n * prevBtn.onclick = () => search.prev();\n *\n * // React to changes\n * search.onChange = ({ current, total }) => {\n * label.textContent = total > 0 ? `${current + 1}/${total}` : '';\n * };\n * ```\n */\nexport class SearchController {\n private highlightManager: HighlightManager;\n private pages: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n\n /** Callback fired when match state changes (search, next, prev, clear). */\n onChange: ((state: { current: number; total: number; query: string }) => void) | null = null;\n\n constructor(options: SearchControllerOptions = {}) {\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);\n if (options.autoScroll !== undefined) {\n this.highlightManager.autoScroll = options.autoScroll;\n }\n }\n\n /** Get or set auto-scroll behavior. */\n get autoScroll(): boolean {\n return this.highlightManager.autoScroll;\n }\n set autoScroll(value: boolean) {\n this.highlightManager.autoScroll = value;\n }\n\n /**\n * Set the pages to search on.\n * Call this after rendering PDF pages.\n */\n setPages(pages: PageData[]): void {\n const savedQuery = this.lastQuery;\n const savedOptions = this.lastSearchOptions;\n const savedContexts = [...this.lastContexts];\n const savedIsMulti = this.lastIsMultiContext;\n\n this.clear();\n this.pages = pages;\n\n // Re-apply search if there was an active query (e.g. after zoom)\n if (savedIsMulti && savedContexts.length > 0) {\n this.searchMultiple(savedContexts, savedOptions);\n } else if (savedQuery.trim()) {\n this.search(savedQuery, savedOptions);\n }\n }\n\n /**\n * Search for text across all pages.\n * Returns total number of matches.\n */\n search(query: string, options: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different CSS class (highlight-0, highlight-1, ...).\n * Navigation (next/prev) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n // 1. Search all contexts on this page\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n }\n }\n\n // 2. Sort all matches by document position (span index, then char offset)\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // 3. Apply highlights with per-match classes\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /** Navigate to next match. Returns new index. */\n next(): number {\n const idx = this.highlightManager.next();\n this.notify();\n return idx;\n }\n\n /** Navigate to previous match. Returns new index. */\n prev(): number {\n const idx = this.highlightManager.prev();\n this.notify();\n return idx;\n }\n\n /** Go to a specific match by index. */\n goTo(index: number): void {\n this.highlightManager.setActiveMatch(index);\n this.notify();\n }\n\n /** Clear all highlights. */\n clear(): void {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.notify();\n }\n\n /** Current match index (0-based). -1 if none. */\n get current(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /** Total number of matches. */\n get total(): number {\n return this.highlightManager.getTotal();\n }\n\n /** Last searched query. */\n get query(): string {\n return this.lastQuery;\n }\n\n /** Last searched contexts (for multi-context search). */\n get contexts(): SearchContext[] {\n return this.lastContexts;\n }\n\n private notify(): void {\n this.onChange?.({\n current: this.highlightManager.getCurrentIndex(),\n total: this.highlightManager.getTotal(),\n query: this.lastQuery,\n });\n }\n}\n"]}
@@ -12,7 +12,7 @@
12
12
 
13
13
 
14
14
 
15
- var _chunkGDUHZCFRcjs = require('../chunk-GDUHZCFR.cjs');
15
+ var _chunkZ2S4WPR7cjs = require('../chunk-Z2S4WPR7.cjs');
16
16
 
17
17
 
18
18
 
@@ -27,5 +27,5 @@ var _chunkGDUHZCFRcjs = require('../chunk-GDUHZCFR.cjs');
27
27
 
28
28
 
29
29
 
30
- exports.DEFAULT_CLASS_NAMES = _chunkGDUHZCFRcjs.DEFAULT_CLASS_NAMES; exports.DEFAULT_PAGE_GAP = _chunkGDUHZCFRcjs.DEFAULT_PAGE_GAP; exports.DEFAULT_SCALE = _chunkGDUHZCFRcjs.DEFAULT_SCALE; exports.EventEmitter = _chunkGDUHZCFRcjs.EventEmitter; exports.HighlightManager = _chunkGDUHZCFRcjs.HighlightManager; exports.MAX_SCALE = _chunkGDUHZCFRcjs.MAX_SCALE; exports.MIN_SCALE = _chunkGDUHZCFRcjs.MIN_SCALE; exports.MULTI_CONTEXT_COLOR_COUNT = _chunkGDUHZCFRcjs.MULTI_CONTEXT_COLOR_COUNT; exports.PDFRenderer = _chunkGDUHZCFRcjs.PDFRenderer; exports.PDFSearchViewer = _chunkGDUHZCFRcjs.PDFSearchViewer; exports.SearchController = _chunkGDUHZCFRcjs.SearchController; exports.ZOOM_STEP = _chunkGDUHZCFRcjs.ZOOM_STEP; exports.searchPage = _chunkGDUHZCFRcjs.searchPage;
30
+ exports.DEFAULT_CLASS_NAMES = _chunkZ2S4WPR7cjs.DEFAULT_CLASS_NAMES; exports.DEFAULT_PAGE_GAP = _chunkZ2S4WPR7cjs.DEFAULT_PAGE_GAP; exports.DEFAULT_SCALE = _chunkZ2S4WPR7cjs.DEFAULT_SCALE; exports.EventEmitter = _chunkZ2S4WPR7cjs.EventEmitter; exports.HighlightManager = _chunkZ2S4WPR7cjs.HighlightManager; exports.MAX_SCALE = _chunkZ2S4WPR7cjs.MAX_SCALE; exports.MIN_SCALE = _chunkZ2S4WPR7cjs.MIN_SCALE; exports.MULTI_CONTEXT_COLOR_COUNT = _chunkZ2S4WPR7cjs.MULTI_CONTEXT_COLOR_COUNT; exports.PDFRenderer = _chunkZ2S4WPR7cjs.PDFRenderer; exports.PDFSearchViewer = _chunkZ2S4WPR7cjs.PDFSearchViewer; exports.SearchController = _chunkZ2S4WPR7cjs.SearchController; exports.ZOOM_STEP = _chunkZ2S4WPR7cjs.ZOOM_STEP; exports.searchPage = _chunkZ2S4WPR7cjs.searchPage;
31
31
  //# sourceMappingURL=index.cjs.map
@@ -1,8 +1,10 @@
1
- import { C as ClassNames, P as PageData, S as SearchOptions, a as SearchContext, b as PDFSearchViewerOptions, c as SpanData, d as SearchMatch } from '../PDFSearchViewer-DzKHoKTp.cjs';
2
- export { E as EventEmitter, e as PDFSearchViewer, f as PDFSearchViewerEventMap, g as PDFSource } from '../PDFSearchViewer-DzKHoKTp.cjs';
1
+ import { C as ClassNames, P as PageData, S as SearchOptions, a as SearchContext, b as PDFSearchViewerOptions, c as SpanData, d as SearchMatch } from '../PDFSearchViewer-DkU4bVBD.cjs';
2
+ export { E as EventEmitter, e as PDFSearchViewer, f as PDFSearchViewerEventMap, g as PDFSource } from '../PDFSearchViewer-DkU4bVBD.cjs';
3
3
 
4
4
  interface SearchControllerOptions {
5
5
  classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;
6
+ /** Auto-scroll to active match on search/next/prev. Defaults to true. */
7
+ autoScroll?: boolean;
6
8
  }
7
9
  /**
8
10
  * Headless search + highlight controller.
@@ -50,6 +52,9 @@ declare class SearchController {
50
52
  query: string;
51
53
  }) => void) | null;
52
54
  constructor(options?: SearchControllerOptions);
55
+ /** Get or set auto-scroll behavior. */
56
+ get autoScroll(): boolean;
57
+ set autoScroll(value: boolean);
53
58
  /**
54
59
  * Set the pages to search on.
55
60
  * Call this after rendering PDF pages.
@@ -170,6 +175,8 @@ declare class HighlightManager {
170
175
  private currentMatch;
171
176
  private highlightClass;
172
177
  private activeHighlightClass;
178
+ /** Whether to auto-scroll to the active match. Defaults to true. */
179
+ autoScroll: boolean;
173
180
  constructor(highlightClass: string, activeHighlightClass: string);
174
181
  /**
175
182
  * Apply highlights for all matches on a page.
@@ -1,8 +1,10 @@
1
- import { C as ClassNames, P as PageData, S as SearchOptions, a as SearchContext, b as PDFSearchViewerOptions, c as SpanData, d as SearchMatch } from '../PDFSearchViewer-DzKHoKTp.js';
2
- export { E as EventEmitter, e as PDFSearchViewer, f as PDFSearchViewerEventMap, g as PDFSource } from '../PDFSearchViewer-DzKHoKTp.js';
1
+ import { C as ClassNames, P as PageData, S as SearchOptions, a as SearchContext, b as PDFSearchViewerOptions, c as SpanData, d as SearchMatch } from '../PDFSearchViewer-DkU4bVBD.js';
2
+ export { E as EventEmitter, e as PDFSearchViewer, f as PDFSearchViewerEventMap, g as PDFSource } from '../PDFSearchViewer-DkU4bVBD.js';
3
3
 
4
4
  interface SearchControllerOptions {
5
5
  classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;
6
+ /** Auto-scroll to active match on search/next/prev. Defaults to true. */
7
+ autoScroll?: boolean;
6
8
  }
7
9
  /**
8
10
  * Headless search + highlight controller.
@@ -50,6 +52,9 @@ declare class SearchController {
50
52
  query: string;
51
53
  }) => void) | null;
52
54
  constructor(options?: SearchControllerOptions);
55
+ /** Get or set auto-scroll behavior. */
56
+ get autoScroll(): boolean;
57
+ set autoScroll(value: boolean);
53
58
  /**
54
59
  * Set the pages to search on.
55
60
  * Call this after rendering PDF pages.
@@ -170,6 +175,8 @@ declare class HighlightManager {
170
175
  private currentMatch;
171
176
  private highlightClass;
172
177
  private activeHighlightClass;
178
+ /** Whether to auto-scroll to the active match. Defaults to true. */
179
+ autoScroll: boolean;
173
180
  constructor(highlightClass: string, activeHighlightClass: string);
174
181
  /**
175
182
  * Apply highlights for all matches on a page.
@@ -12,7 +12,7 @@ import {
12
12
  SearchController,
13
13
  ZOOM_STEP,
14
14
  searchPage
15
- } from "../chunk-XFH7JUDJ.js";
15
+ } from "../chunk-4NJWRYPR.js";
16
16
  export {
17
17
  DEFAULT_CLASS_NAMES,
18
18
  DEFAULT_PAGE_GAP,
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkGDUHZCFRcjs = require('../chunk-GDUHZCFR.cjs');
8
+ var _chunkZ2S4WPR7cjs = require('../chunk-Z2S4WPR7.cjs');
9
9
 
10
10
  // src/react/usePDFRenderer.ts
11
11
  var _react = require('react');
@@ -27,7 +27,7 @@ function usePDFRenderer(pdfjsLib, options = {}) {
27
27
  const getRenderer = _react.useCallback.call(void 0, () => {
28
28
  if (!containerRef.current) throw new Error("Container ref not attached");
29
29
  if (!rendererRef.current) {
30
- const r = new (0, _chunkGDUHZCFRcjs.PDFRenderer)(containerRef.current, optionsRef.current);
30
+ const r = new (0, _chunkZ2S4WPR7cjs.PDFRenderer)(containerRef.current, optionsRef.current);
31
31
  r.setPdfjsLib(pdfjsLib);
32
32
  rendererRef.current = r;
33
33
  }
@@ -64,13 +64,13 @@ function usePDFRenderer(pdfjsLib, options = {}) {
64
64
  const zoomIn = _react.useCallback.call(void 0, async () => {
65
65
  const renderer = getRenderer();
66
66
  const current = renderer.getScale() === "auto" ? renderer.getEffectiveScale() : renderer.getScale();
67
- const newScale = Math.min(current + _chunkGDUHZCFRcjs.ZOOM_STEP, _chunkGDUHZCFRcjs.MAX_SCALE);
67
+ const newScale = Math.min(current + _chunkZ2S4WPR7cjs.ZOOM_STEP, _chunkZ2S4WPR7cjs.MAX_SCALE);
68
68
  return setScale(newScale);
69
69
  }, [getRenderer, setScale]);
70
70
  const zoomOut = _react.useCallback.call(void 0, async () => {
71
71
  const renderer = getRenderer();
72
72
  const current = renderer.getScale() === "auto" ? renderer.getEffectiveScale() : renderer.getScale();
73
- const newScale = Math.max(current - _chunkGDUHZCFRcjs.ZOOM_STEP, _chunkGDUHZCFRcjs.MIN_SCALE);
73
+ const newScale = Math.max(current - _chunkZ2S4WPR7cjs.ZOOM_STEP, _chunkZ2S4WPR7cjs.MIN_SCALE);
74
74
  return setScale(newScale);
75
75
  }, [getRenderer, setScale]);
76
76
  const download = _react.useCallback.call(void 0,
@@ -108,7 +108,7 @@ function useSearchController(pages, options = {}) {
108
108
  const [current, setCurrent] = _react.useState.call(void 0, -1);
109
109
  const [total, setTotal] = _react.useState.call(void 0, 0);
110
110
  if (!controllerRef.current) {
111
- controllerRef.current = new (0, _chunkGDUHZCFRcjs.SearchController)(options);
111
+ controllerRef.current = new (0, _chunkZ2S4WPR7cjs.SearchController)(options);
112
112
  }
113
113
  _react.useEffect.call(void 0, () => {
114
114
  const ctrl = controllerRef.current;
@@ -178,7 +178,7 @@ var PDFSearchViewer2 = _react.forwardRef.call(void 0, function PDFSearchViewer3(
178
178
  callbackRefs.current = { onLoad, onSearch, onSearchMultiple, onMatchChange, onZoom, onError };
179
179
  _react.useEffect.call(void 0, () => {
180
180
  if (!containerRef.current || !pdfjsLib) return;
181
- const core = new (0, _chunkGDUHZCFRcjs.PDFSearchViewer)(
181
+ const core = new (0, _chunkZ2S4WPR7cjs.PDFSearchViewer)(
182
182
  containerRef.current,
183
183
  pdfjsLib,
184
184
  _nullishCoalesce(viewerOptions, () => ( {}))
@@ -1,5 +1,5 @@
1
- import { P as PageData, g as PDFSource, b as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, a as SearchContext, e as PDFSearchViewer$1 } from '../PDFSearchViewer-DzKHoKTp.cjs';
2
- export { d as SearchMatch } from '../PDFSearchViewer-DzKHoKTp.cjs';
1
+ import { P as PageData, g as PDFSource, b as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, a as SearchContext, e as PDFSearchViewer$1 } from '../PDFSearchViewer-DkU4bVBD.cjs';
2
+ export { d as SearchMatch } from '../PDFSearchViewer-DkU4bVBD.cjs';
3
3
  import * as react from 'react';
4
4
  import { CSSProperties } from 'react';
5
5
 
@@ -1,5 +1,5 @@
1
- import { P as PageData, g as PDFSource, b as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, a as SearchContext, e as PDFSearchViewer$1 } from '../PDFSearchViewer-DzKHoKTp.js';
2
- export { d as SearchMatch } from '../PDFSearchViewer-DzKHoKTp.js';
1
+ import { P as PageData, g as PDFSource, b as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, a as SearchContext, e as PDFSearchViewer$1 } from '../PDFSearchViewer-DkU4bVBD.js';
2
+ export { d as SearchMatch } from '../PDFSearchViewer-DkU4bVBD.js';
3
3
  import * as react from 'react';
4
4
  import { CSSProperties } from 'react';
5
5
 
@@ -5,7 +5,7 @@ import {
5
5
  PDFSearchViewer,
6
6
  SearchController,
7
7
  ZOOM_STEP
8
- } from "../chunk-XFH7JUDJ.js";
8
+ } from "../chunk-4NJWRYPR.js";
9
9
 
10
10
  // src/react/usePDFRenderer.ts
11
11
  import { useRef, useEffect, useCallback, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-search-highlight",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Drop-in PDF viewer with text search and highlight. Vanilla JS core + React wrapper.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.cjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/hoangnguyen/Desktop/labs/pdf-search-highlight/dist/chunk-GDUHZCFR.cjs","../src/core/EventEmitter.ts","../src/core/constants.ts","../src/core/PDFRenderer.ts","../src/core/SearchEngine.ts","../src/core/HighlightManager.ts","../src/core/PDFSearchViewer.ts","../src/core/SearchController.ts"],"names":[],"mappings":"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","file":"/Users/hoangnguyen/Desktop/labs/pdf-search-highlight/dist/chunk-GDUHZCFR.cjs","sourcesContent":[null,"type Listener<T> = (data: T) => void;\n\nexport class EventEmitter<EventMap extends { [key: string]: unknown }> {\n private listeners = new Map<keyof EventMap, Set<Listener<any>>>();\n\n on<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n protected emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {\n this.listeners.get(event)?.forEach((fn) => fn(data));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import type { ClassNames } from '../types';\n\nexport const DEFAULT_CLASS_NAMES: Required<ClassNames> = {\n container: 'psh-container',\n page: 'psh-page',\n canvas: 'psh-canvas',\n textLayer: 'psh-text-layer',\n pageLabel: 'psh-page-label',\n highlight: 'highlight',\n activeHighlight: 'active',\n};\n\nexport const DEFAULT_SCALE = 'auto' as number | 'auto';\nexport const DEFAULT_PAGE_GAP = 20;\n\nexport const ZOOM_STEP = 0.25;\nexport const MIN_SCALE = 0.25;\nexport const MAX_SCALE = 5;\n\n/** Number of predefined multi-context highlight colors (cycles with modulo). */\nexport const MULTI_CONTEXT_COLOR_COUNT = 8;\n","import type { PDFSearchViewerOptions, ClassNames, PageData, SpanData } from '../types';\nimport { DEFAULT_CLASS_NAMES, DEFAULT_SCALE, DEFAULT_PAGE_GAP } from './constants';\n\n// pdfjs-dist types\ntype PDFDocumentProxy = any;\ntype PDFPageProxy = any;\n\n/**\n * Renders PDF pages into a container using canvas + text layer.\n *\n * Text layer approach (matching demo):\n * - Extract text content from each page\n * - Create absolutely-positioned <span> elements overlaying the canvas\n * - Position spans using the transform matrix from pdf.js\n * - Spans are transparent (for text selection) but allow DOM-based search/highlight\n */\nexport class PDFRenderer {\n private container: HTMLElement;\n private scale: number | 'auto';\n private pageGap: number;\n private cls: Required<ClassNames>;\n private workerSrc?: string;\n private pdfDoc: PDFDocumentProxy | null = null;\n private pageData: PageData[] = [];\n private pdfjsLib: any = null;\n private effectiveScale: number = 1;\n\n constructor(container: HTMLElement, options: PDFSearchViewerOptions) {\n this.container = container;\n this.scale = options.scale ?? DEFAULT_SCALE;\n this.pageGap = options.pageGap ?? DEFAULT_PAGE_GAP;\n this.workerSrc = options.workerSrc;\n this.cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n }\n\n /**\n * Set the pdfjs-dist library reference.\n * Must be called before loadDocument.\n */\n setPdfjsLib(lib: any): void {\n this.pdfjsLib = lib;\n if (this.workerSrc) {\n lib.GlobalWorkerOptions.workerSrc = this.workerSrc;\n }\n }\n\n /**\n * Load a PDF from File, ArrayBuffer, URL string, or Uint8Array.\n */\n async loadDocument(\n source: File | ArrayBuffer | Uint8Array | string\n ): Promise<number> {\n if (!this.pdfjsLib) {\n throw new Error(\n 'pdfjs-dist not set. Call setPdfjsLib(pdfjsLib) before loading a document.'\n );\n }\n\n this.cleanup();\n\n let data: ArrayBuffer | Uint8Array | { url: string };\n if (source instanceof File) {\n data = await source.arrayBuffer();\n } else if (typeof source === 'string') {\n data = { url: source };\n } else {\n data = source;\n }\n\n const loadingTask = this.pdfjsLib.getDocument({ data });\n this.pdfDoc = await loadingTask.promise;\n return this.pdfDoc.numPages;\n }\n\n /**\n * Render all pages into the container.\n * Preserves scroll position across re-renders (e.g. zoom).\n * Returns PageData[] for search/highlight.\n */\n async renderAllPages(): Promise<PageData[]> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n // Save scroll position relative to total content height\n const prevScrollTop = this.container.scrollTop;\n const prevScrollHeight = this.container.scrollHeight || 1;\n const scrollRatio = prevScrollTop / prevScrollHeight;\n\n this.container.innerHTML = '';\n this.container.classList.add(this.cls.container);\n this.pageData = [];\n\n const numPages = this.pdfDoc.numPages;\n\n for (let i = 1; i <= numPages; i++) {\n const page = await this.pdfDoc.getPage(i);\n const pd = await this.renderPage(page, i, numPages);\n this.pageData.push(pd);\n }\n\n // Restore scroll position proportionally\n if (prevScrollTop > 0) {\n this.container.scrollTop = scrollRatio * this.container.scrollHeight;\n }\n\n return this.pageData;\n }\n\n private async renderPage(\n page: PDFPageProxy,\n pageNum: number,\n totalPages: number\n ): Promise<PageData> {\n const scale = this.calculateScale(page);\n if (pageNum === 1) this.effectiveScale = scale;\n const vp = page.getViewport({ scale });\n\n // Page container\n const container = document.createElement('div');\n container.className = this.cls.page;\n container.style.position = 'relative';\n container.style.width = vp.width + 'px';\n container.style.height = vp.height + 'px';\n container.style.margin = '0 auto';\n container.style.marginBottom = this.pageGap + 'px';\n container.style.overflow = 'hidden';\n container.dataset.page = String(pageNum);\n\n // Canvas (2x for retina)\n const canvas = document.createElement('canvas');\n canvas.className = this.cls.canvas;\n canvas.width = vp.width * 2;\n canvas.height = vp.height * 2;\n canvas.style.width = vp.width + 'px';\n canvas.style.height = vp.height + 'px';\n canvas.style.display = 'block';\n const ctx = canvas.getContext('2d')!;\n ctx.scale(2, 2);\n await page.render({ canvasContext: ctx, viewport: vp }).promise;\n\n // Text layer\n const textLayer = document.createElement('div');\n textLayer.className = this.cls.textLayer;\n textLayer.style.position = 'absolute';\n textLayer.style.top = '0';\n textLayer.style.left = '0';\n textLayer.style.right = '0';\n textLayer.style.bottom = '0';\n textLayer.style.overflow = 'hidden';\n textLayer.style.lineHeight = '1';\n\n const tc = await page.getTextContent();\n const spans: SpanData[] = [];\n\n for (const item of tc.items) {\n if (!item.str && !item.hasEOL) continue;\n\n const tx = this.pdfjsLib.Util.transform(vp.transform, item.transform);\n const span = document.createElement('span');\n span.textContent = item.str || '';\n const fh = Math.hypot(tx[2], tx[3]);\n span.style.position = 'absolute';\n span.style.left = tx[4] + 'px';\n span.style.top = (tx[5] - fh) + 'px';\n span.style.fontSize = fh + 'px';\n span.style.color = 'transparent';\n span.style.whiteSpace = 'pre';\n span.style.cursor = 'text';\n span.style.transformOrigin = '0% 0%';\n if (item.fontName) span.style.fontFamily = item.fontName;\n\n const sw = tx[0] / fh;\n if (Math.abs(sw - 1) > 0.01) {\n span.style.transform = `scaleX(${sw})`;\n }\n\n textLayer.appendChild(span);\n spans.push({\n el: span,\n text: item.str || '',\n hasEOL: !!item.hasEOL,\n });\n }\n\n container.appendChild(canvas);\n container.appendChild(textLayer);\n this.container.appendChild(container);\n\n // Page label\n const label = document.createElement('div');\n label.className = this.cls.pageLabel;\n label.textContent = `Page ${pageNum} / ${totalPages}`;\n this.container.appendChild(label);\n\n return { container, spans };\n }\n\n private calculateScale(page: PDFPageProxy): number {\n if (this.scale !== 'auto' && typeof this.scale === 'number') {\n return this.scale;\n }\n const defaultVp = page.getViewport({ scale: 1 });\n const containerWidth = this.container.clientWidth || 800;\n return Math.min(containerWidth / defaultVp.width, 2);\n }\n\n /** Set the scale for subsequent renders. */\n setScale(scale: number | 'auto'): void {\n this.scale = scale;\n }\n\n /** Get the configured scale setting. */\n getScale(): number | 'auto' {\n return this.scale;\n }\n\n /** Get the actual numeric scale used in the last render. */\n getEffectiveScale(): number {\n return this.effectiveScale;\n }\n\n /**\n * Download the currently loaded PDF.\n */\n async download(filename: string = 'document.pdf'): Promise<void> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n const data = await this.pdfDoc.getData();\n const blob = new Blob([data as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n\n getClassNames(): Required<ClassNames> {\n return this.cls;\n }\n\n getPageData(): PageData[] {\n return this.pageData;\n }\n\n getPageCount(): number {\n return this.pdfDoc?.numPages ?? 0;\n }\n\n cleanup(): void {\n this.pdfDoc?.destroy();\n this.pdfDoc = null;\n this.pageData = [];\n this.container.innerHTML = '';\n }\n}\n","import type { SearchOptions, SpanData } from '../types';\n\nexport interface CharMapEntry {\n spanIdx: number;\n charIdx: number;\n}\n\nexport interface MatchRange {\n spanIdx: number;\n start: number;\n end: number;\n}\n\nexport interface SearchResult {\n /** Array of span ranges for each match */\n matchRanges: MatchRange[][];\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Build a flexible regex from query.\n *\n * For queries < 200 chars (after removing whitespace):\n * - Strip all whitespace from query\n * - Insert \\s* between every character\n * → \"and expensive\" becomes a\\s*n\\s*d\\s*e\\s*x\\s*p\\s*e\\s*n\\s*s\\s*i\\s*v\\s*e\n * → Matches regardless of whitespace differences in PDF text\n *\n * For queries >= 200 chars:\n * - Split by whitespace, join with \\s+\n */\nfunction buildFlexibleRegex(\n query: string,\n options: SearchOptions\n): RegExp | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const flexibleWhitespace = options.flexibleWhitespace ?? true;\n\n if (!flexibleWhitespace) {\n // Simple literal search\n const pattern = escapeRegex(trimmed);\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Remove all whitespace chars from query\n const chars = [...trimmed].filter((c) => !/\\s/.test(c));\n if (chars.length === 0) return null;\n\n if (chars.length > 200) {\n // Fallback: flexible between tokens only\n const tokens = trimmed.split(/\\s+/);\n const pattern = tokens.map((t) => escapeRegex(t)).join('\\\\s+');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Insert \\s* between every character\n const pattern = chars.map((c) => escapeRegex(c)).join('\\\\s*');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n}\n\ninterface FuzzyMatch {\n start: number;\n end: number;\n distance: number;\n}\n\n/**\n * Semi-global Levenshtein alignment for approximate substring matching.\n *\n * Finds all positions in `text` where a substring has edit distance ≤ maxErrors\n * from `query`. Uses O(m) space with single-column DP.\n *\n * Semi-global: first DP column = 0 (match can start anywhere in text),\n * but the full query must be covered.\n */\nfunction fuzzySearchText(\n text: string,\n query: string,\n maxErrors: number\n): FuzzyMatch[] {\n const n = text.length;\n const m = query.length;\n if (m === 0) return [];\n if (n === 0) return [];\n\n // DP: prev[j] = min edit distance to match query[0..j-1] ending at current text pos\n // Semi-global: prev[0] = 0 for all text positions (free start)\n let prev = new Uint32Array(m + 1);\n for (let j = 0; j <= m; j++) prev[j] = j;\n\n // Store full DP matrix columns for traceback\n const columns: Uint32Array[] = [prev.slice()];\n\n // Track match end positions\n const endPositions: Array<{ col: number; distance: number }> = [];\n\n for (let i = 1; i <= n; i++) {\n const curr = new Uint32Array(m + 1);\n curr[0] = 0; // semi-global: free start position\n for (let j = 1; j <= m; j++) {\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n prev[j] + 1, // deletion\n curr[j - 1] + 1, // insertion\n prev[j - 1] + cost // substitution\n );\n }\n columns.push(curr.slice());\n\n if (curr[m] <= maxErrors) {\n endPositions.push({ col: i, distance: curr[m] });\n }\n prev = curr;\n }\n\n if (endPositions.length === 0) return [];\n\n // Traceback to find start position for each match end\n const rawMatches: FuzzyMatch[] = [];\n for (const { col: endCol, distance } of endPositions) {\n // Trace back through the DP matrix to find where the match starts\n let j = m;\n let i = endCol;\n while (j > 0 && i > 0) {\n const c = columns[i];\n const p = columns[i - 1];\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n if (c[j] === p[j - 1] + cost) {\n // substitution or match — move diagonally\n i--;\n j--;\n } else if (c[j] === p[j] + 1) {\n // deletion from text — move left in text\n i--;\n } else {\n // insertion into text — move up in query\n j--;\n }\n }\n rawMatches.push({ start: i, end: endCol, distance });\n }\n\n // Merge overlapping matches, keeping the one with lowest distance\n if (rawMatches.length === 0) return [];\n rawMatches.sort((a, b) => a.start - b.start || a.distance - b.distance);\n\n const merged: FuzzyMatch[] = [rawMatches[0]];\n for (let i = 1; i < rawMatches.length; i++) {\n const prev = merged[merged.length - 1];\n const curr = rawMatches[i];\n if (curr.start < prev.end) {\n // Overlapping — keep the one with lower distance\n if (curr.distance < prev.distance) {\n merged[merged.length - 1] = curr;\n }\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Build fullText and charMap from spans.\n */\nfunction buildTextAndCharMap(spans: SpanData[]) {\n let fullText = '';\n const charMap: CharMapEntry[] = [];\n spans.forEach((s, si) => {\n for (let ci = 0; ci < s.text.length; ci++) {\n charMap.push({ spanIdx: si, charIdx: ci });\n fullText += s.text[ci];\n }\n });\n return { fullText, charMap };\n}\n\n/**\n * Map a start/end range in fullText to MatchRange[] via charMap.\n */\nfunction mapToSpanRanges(\n start: number,\n end: number,\n charMap: CharMapEntry[]\n): MatchRange[] {\n const range: MatchRange[] = [];\n for (let k = start; k < end; k++) {\n const cm = charMap[k];\n const last = range[range.length - 1];\n if (last && last.spanIdx === cm.spanIdx && last.end === cm.charIdx) {\n last.end = cm.charIdx + 1;\n } else {\n range.push({ spanIdx: cm.spanIdx, start: cm.charIdx, end: cm.charIdx + 1 });\n }\n }\n return range;\n}\n\n/**\n * Search for text across page spans using charMap-based matching.\n *\n * Algorithm:\n * 1. Concatenate all span texts into one string (fullText)\n * 2. Build charMap: charMap[i] = { spanIdx, charIdx } for each char in fullText\n * 3. Run regex or fuzzy search on fullText\n * 4. Map each match back to span ranges via charMap\n */\nexport function searchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions = {}\n): MatchRange[][] {\n const trimmed = query.trim();\n if (!trimmed) return [];\n\n if (options.fuzzy) {\n return fuzzySearchPage(spans, trimmed, options);\n }\n\n const regex = buildFlexibleRegex(query, options);\n if (!regex) return [];\n\n const { fullText, charMap } = buildTextAndCharMap(spans);\n\n // Find all regex matches\n const allMatchRanges: MatchRange[][] = [];\n let m: RegExpExecArray | null;\n regex.lastIndex = 0;\n\n while ((m = regex.exec(fullText)) !== null) {\n allMatchRanges.push(mapToSpanRanges(m.index, m.index + m[0].length, charMap));\n if (m[0].length === 0) regex.lastIndex++;\n }\n\n return allMatchRanges;\n}\n\nfunction fuzzySearchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions\n): MatchRange[][] {\n const { fullText, charMap } = buildTextAndCharMap(spans);\n if (fullText.length === 0) return [];\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const threshold = options.fuzzyThreshold ?? 0.6;\n\n const searchText = isCaseSensitive ? fullText : fullText.toLowerCase();\n const searchQuery = isCaseSensitive ? query : query.toLowerCase();\n\n // Strip whitespace from query for matching\n const strippedQuery = searchQuery.replace(/\\s+/g, '');\n if (strippedQuery.length === 0) return [];\n\n const maxErrors = Math.floor(strippedQuery.length * (1 - threshold));\n const matches = fuzzySearchText(searchText, strippedQuery, maxErrors);\n\n return matches.map((m) => mapToSpanRanges(m.start, m.end, charMap));\n}\n","import type { SearchMatch, SpanData, PageData } from '../types';\nimport type { MatchRange } from './SearchEngine';\n\n/**\n * Manages cross-span highlighting using the charMap approach.\n *\n * Algorithm (from demo):\n * 1. Group match ranges by spanIdx\n * 2. For each affected span, replace textContent with a DocumentFragment:\n * - Plain text nodes for non-matching parts\n * - <mark> elements for matching parts\n * 3. Collect marks per match for navigation\n */\nexport class HighlightManager {\n private matches: SearchMatch[] = [];\n private currentMatch = -1;\n private highlightClass: string;\n private activeHighlightClass: string;\n\n constructor(highlightClass: string, activeHighlightClass: string) {\n this.highlightClass = highlightClass;\n this.activeHighlightClass = activeHighlightClass;\n }\n\n /**\n * Apply highlights for all matches on a page.\n * Returns the SearchMatch[] (array of mark groups).\n *\n * @param classPerMatch - Optional per-match CSS class names (for multi-context search).\n * When provided, `classPerMatch[i]` is the CSS class for `matchRanges[i]`.\n * When omitted, all matches use the default highlight class.\n */\n applyHighlights(\n pageSpans: SpanData[],\n matchRanges: MatchRange[][],\n classPerMatch?: string[]\n ): SearchMatch[] {\n if (!matchRanges.length) return [];\n\n // Group ranges by spanIdx, keeping track of which match they belong to\n const spanRanges: Record<\n number,\n { start: number; end: number; matchIdx: number }[]\n > = {};\n\n matchRanges.forEach((range, mi) => {\n range.forEach((r) => {\n if (!spanRanges[r.spanIdx]) spanRanges[r.spanIdx] = [];\n spanRanges[r.spanIdx].push({ start: r.start, end: r.end, matchIdx: mi });\n });\n });\n\n // Collect marks per match\n const matchMarks: HTMLElement[][] = matchRanges.map(() => []);\n\n // For each affected span, rebuild DOM with highlights\n for (const siStr of Object.keys(spanRanges)) {\n const si = parseInt(siStr, 10);\n const s = pageSpans[si];\n const ranges = spanRanges[si].sort((a, b) => a.start - b.start);\n\n const frag = document.createDocumentFragment();\n let last = 0;\n\n for (const r of ranges) {\n const actualStart = Math.max(r.start, last);\n\n // Add plain text before highlight\n if (actualStart > last) {\n frag.appendChild(document.createTextNode(s.text.slice(last, actualStart)));\n }\n\n // Add highlight mark\n if (actualStart < r.end) {\n const mark = document.createElement('mark');\n mark.className = classPerMatch?.[r.matchIdx] ?? this.highlightClass;\n mark.textContent = s.text.slice(actualStart, r.end);\n frag.appendChild(mark);\n matchMarks[r.matchIdx].push(mark);\n }\n\n last = Math.max(last, r.end);\n }\n\n // Add remaining plain text\n if (last < s.text.length) {\n frag.appendChild(document.createTextNode(s.text.slice(last)));\n }\n\n // Replace span content\n s.el.textContent = '';\n s.el.appendChild(frag);\n }\n\n return matchMarks\n .filter((marks) => marks.length > 0)\n .map((marks) => ({ marks }));\n }\n\n /**\n * Add matches to the global list.\n */\n addMatches(newMatches: SearchMatch[]): void {\n this.matches.push(...newMatches);\n }\n\n /**\n * Clear all highlights and restore original span text.\n */\n clearHighlights(allPageData: PageData[]): void {\n allPageData.forEach((pd) => {\n pd.spans.forEach((s) => {\n s.el.textContent = s.text;\n });\n });\n this.matches = [];\n this.currentMatch = -1;\n }\n\n /**\n * Set active match by index. Applies active CSS class and scrolls into view.\n */\n setActiveMatch(index: number): void {\n // Remove active class from previous\n if (this.currentMatch >= 0 && this.currentMatch < this.matches.length) {\n this.matches[this.currentMatch].marks.forEach((m) =>\n m.classList.remove(this.activeHighlightClass)\n );\n }\n\n this.currentMatch = index;\n\n if (index >= 0 && index < this.matches.length) {\n this.matches[index].marks.forEach((m) =>\n m.classList.add(this.activeHighlightClass)\n );\n // Scroll first mark into view\n this.matches[index].marks[0]?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n next(): number {\n if (this.matches.length === 0) return -1;\n const newIdx = (this.currentMatch + 1) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prev(): number {\n if (this.matches.length === 0) return -1;\n const newIdx =\n (this.currentMatch - 1 + this.matches.length) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n getCurrentIndex(): number {\n return this.currentMatch;\n }\n\n getTotal(): number {\n return this.matches.length;\n }\n\n getMatches(): SearchMatch[] {\n return this.matches;\n }\n}\n","import { EventEmitter } from './EventEmitter';\nimport { PDFRenderer } from './PDFRenderer';\nimport { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, ZOOM_STEP, MIN_SCALE, MAX_SCALE, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type {\n PDFSearchViewerOptions,\n SearchOptions,\n SearchContext,\n PDFSearchViewerEventMap,\n PageData,\n} from '../types';\n\nexport type PDFSource = File | ArrayBuffer | Uint8Array | string;\n\n/**\n * Main PDF viewer with search and highlight functionality.\n *\n * Usage:\n * ```js\n * import * as pdfjsLib from 'pdfjs-dist';\n * import { PDFSearchViewer } from 'pdf-search-highlight';\n *\n * const viewer = new PDFSearchViewer(container, pdfjsLib, {\n * classNames: {\n * page: 'my-page',\n * highlight: 'my-highlight',\n * activeHighlight: 'my-active',\n * }\n * });\n * await viewer.loadPDF(file);\n * viewer.search('hello');\n * viewer.nextMatch();\n * ```\n */\nexport class PDFSearchViewer extends EventEmitter<PDFSearchViewerEventMap> {\n private renderer: PDFRenderer;\n private highlightManager: HighlightManager;\n private pageData: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n private destroyed = false;\n\n constructor(\n container: HTMLElement,\n pdfjsLib: any,\n options: PDFSearchViewerOptions = {}\n ) {\n super();\n\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n\n this.renderer = new PDFRenderer(container, options);\n this.renderer.setPdfjsLib(pdfjsLib);\n this.highlightManager = new HighlightManager(\n cls.highlight,\n cls.activeHighlight\n );\n }\n\n /**\n * Load and render a PDF document.\n */\n async loadPDF(source: PDFSource): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n try {\n await this.renderer.loadDocument(source);\n this.pageData = await this.renderer.renderAllPages();\n const pageCount = this.renderer.getPageCount();\n this.emit('load', { pageCount });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', { error, context: 'loadPDF' });\n throw error;\n }\n }\n\n /**\n * Search for text across all pages.\n * Clears previous highlights and creates new ones.\n */\n search(query: string, options: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n // Clear previous highlights\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.emit('search', { query, total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n // Search each page and apply highlights\n for (const pd of this.pageData) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n // Auto-activate first match\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('search', { query, total });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different color (highlight-0, highlight-1, ...).\n * Navigation (nextMatch/prevMatch) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n this.highlightManager.clearHighlights(this.pageData);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.emit('searchmultiple', { contexts, total: 0, totalsPerContext: contexts.map(() => 0) });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n const totalsPerContext = new Array(validContexts.length).fill(0);\n\n for (const pd of this.pageData) {\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n const contextPerMatch: number[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n contextPerMatch.push(ci);\n }\n }\n\n // Sort all matches by document position\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // Count per context\n for (const i of indices) {\n totalsPerContext[contextPerMatch[i]]++;\n }\n\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('searchmultiple', { contexts, total, totalsPerContext });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n nextMatch(): number {\n const idx = this.highlightManager.next();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prevMatch(): number {\n const idx = this.highlightManager.prev();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Clear all search highlights.\n */\n clearSearch(): void {\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.emit('search', { query: '', total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n }\n\n /** Get the current scale setting. */\n getScale(): number | 'auto' {\n return this.renderer.getScale();\n }\n\n /** Set scale and re-render. Preserves current search state. */\n async setScale(scale: number | 'auto'): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n this.renderer.setScale(scale);\n await this.rerender();\n this.emit('zoom', { scale: this.renderer.getEffectiveScale() });\n }\n\n /** Zoom in by one step. */\n async zoomIn(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.min(current + ZOOM_STEP, MAX_SCALE);\n await this.setScale(newScale);\n }\n\n /** Zoom out by one step. */\n async zoomOut(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.max(current - ZOOM_STEP, MIN_SCALE);\n await this.setScale(newScale);\n }\n\n /** Download the currently loaded PDF. */\n async download(filename?: string): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n await this.renderer.download(filename);\n }\n\n private resolveCurrentScale(): number {\n const s = this.renderer.getScale();\n return s === 'auto' ? this.renderer.getEffectiveScale() : s;\n }\n\n private async rerender(): Promise<void> {\n this.highlightManager.clearHighlights(this.pageData);\n this.pageData = await this.renderer.renderAllPages();\n\n if (this.lastIsMultiContext && this.lastContexts.length > 0) {\n this.searchMultiple(this.lastContexts, this.lastSearchOptions);\n } else if (this.lastQuery.trim()) {\n this.search(this.lastQuery, this.lastSearchOptions);\n }\n }\n\n /**\n * Get total number of pages.\n */\n getPageCount(): number {\n return this.renderer.getPageCount();\n }\n\n /**\n * Get current active match index (0-based). -1 if none.\n */\n getCurrentMatchIndex(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /**\n * Get total number of matches.\n */\n getMatchCount(): number {\n return this.highlightManager.getTotal();\n }\n\n /**\n * Destroy the viewer, release all resources.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.highlightManager.clearHighlights(this.pageData);\n this.renderer.cleanup();\n this.removeAllListeners();\n this.pageData = [];\n }\n}\n","import { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type { SearchOptions, ClassNames, PageData, SearchContext } from '../types';\n\nexport interface SearchControllerOptions {\n classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;\n}\n\n/**\n * Headless search + highlight controller.\n * Does NOT render PDF — works with any PageData[] you provide.\n *\n * Use this when you want full control over:\n * - Where the PDF is rendered\n * - Where the search UI lives\n * - How search results are displayed\n *\n * Usage:\n * ```js\n * import { PDFRenderer, SearchController } from 'pdf-search-highlight';\n *\n * // Render PDF wherever you want\n * const renderer = new PDFRenderer(pdfContainer, pdfjsLib, {});\n * const pages = await renderer.renderAllPages();\n *\n * // Search controller — no UI, just logic\n * const search = new SearchController();\n * search.setPages(pages);\n *\n * // Wire up your own UI\n * input.oninput = () => search.search(input.value);\n * nextBtn.onclick = () => search.next();\n * prevBtn.onclick = () => search.prev();\n *\n * // React to changes\n * search.onChange = ({ current, total }) => {\n * label.textContent = total > 0 ? `${current + 1}/${total}` : '';\n * };\n * ```\n */\nexport class SearchController {\n private highlightManager: HighlightManager;\n private pages: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n\n /** Callback fired when match state changes (search, next, prev, clear). */\n onChange: ((state: { current: number; total: number; query: string }) => void) | null = null;\n\n constructor(options: SearchControllerOptions = {}) {\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);\n }\n\n /**\n * Set the pages to search on.\n * Call this after rendering PDF pages.\n */\n setPages(pages: PageData[]): void {\n const savedQuery = this.lastQuery;\n const savedOptions = this.lastSearchOptions;\n const savedContexts = [...this.lastContexts];\n const savedIsMulti = this.lastIsMultiContext;\n\n this.clear();\n this.pages = pages;\n\n // Re-apply search if there was an active query (e.g. after zoom)\n if (savedIsMulti && savedContexts.length > 0) {\n this.searchMultiple(savedContexts, savedOptions);\n } else if (savedQuery.trim()) {\n this.search(savedQuery, savedOptions);\n }\n }\n\n /**\n * Search for text across all pages.\n * Returns total number of matches.\n */\n search(query: string, options: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different CSS class (highlight-0, highlight-1, ...).\n * Navigation (next/prev) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n // 1. Search all contexts on this page\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n }\n }\n\n // 2. Sort all matches by document position (span index, then char offset)\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // 3. Apply highlights with per-match classes\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /** Navigate to next match. Returns new index. */\n next(): number {\n const idx = this.highlightManager.next();\n this.notify();\n return idx;\n }\n\n /** Navigate to previous match. Returns new index. */\n prev(): number {\n const idx = this.highlightManager.prev();\n this.notify();\n return idx;\n }\n\n /** Go to a specific match by index. */\n goTo(index: number): void {\n this.highlightManager.setActiveMatch(index);\n this.notify();\n }\n\n /** Clear all highlights. */\n clear(): void {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.notify();\n }\n\n /** Current match index (0-based). -1 if none. */\n get current(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /** Total number of matches. */\n get total(): number {\n return this.highlightManager.getTotal();\n }\n\n /** Last searched query. */\n get query(): string {\n return this.lastQuery;\n }\n\n /** Last searched contexts (for multi-context search). */\n get contexts(): SearchContext[] {\n return this.lastContexts;\n }\n\n private notify(): void {\n this.onChange?.({\n current: this.highlightManager.getCurrentIndex(),\n total: this.highlightManager.getTotal(),\n query: this.lastQuery,\n });\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/constants.ts","../src/core/PDFRenderer.ts","../src/core/SearchEngine.ts","../src/core/HighlightManager.ts","../src/core/PDFSearchViewer.ts","../src/core/SearchController.ts"],"sourcesContent":["type Listener<T> = (data: T) => void;\n\nexport class EventEmitter<EventMap extends { [key: string]: unknown }> {\n private listeners = new Map<keyof EventMap, Set<Listener<any>>>();\n\n on<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n return this;\n }\n\n off<K extends keyof EventMap>(event: K, listener: Listener<EventMap[K]>): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n protected emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {\n this.listeners.get(event)?.forEach((fn) => fn(data));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import type { ClassNames } from '../types';\n\nexport const DEFAULT_CLASS_NAMES: Required<ClassNames> = {\n container: 'psh-container',\n page: 'psh-page',\n canvas: 'psh-canvas',\n textLayer: 'psh-text-layer',\n pageLabel: 'psh-page-label',\n highlight: 'highlight',\n activeHighlight: 'active',\n};\n\nexport const DEFAULT_SCALE = 'auto' as number | 'auto';\nexport const DEFAULT_PAGE_GAP = 20;\n\nexport const ZOOM_STEP = 0.25;\nexport const MIN_SCALE = 0.25;\nexport const MAX_SCALE = 5;\n\n/** Number of predefined multi-context highlight colors (cycles with modulo). */\nexport const MULTI_CONTEXT_COLOR_COUNT = 8;\n","import type { PDFSearchViewerOptions, ClassNames, PageData, SpanData } from '../types';\nimport { DEFAULT_CLASS_NAMES, DEFAULT_SCALE, DEFAULT_PAGE_GAP } from './constants';\n\n// pdfjs-dist types\ntype PDFDocumentProxy = any;\ntype PDFPageProxy = any;\n\n/**\n * Renders PDF pages into a container using canvas + text layer.\n *\n * Text layer approach (matching demo):\n * - Extract text content from each page\n * - Create absolutely-positioned <span> elements overlaying the canvas\n * - Position spans using the transform matrix from pdf.js\n * - Spans are transparent (for text selection) but allow DOM-based search/highlight\n */\nexport class PDFRenderer {\n private container: HTMLElement;\n private scale: number | 'auto';\n private pageGap: number;\n private cls: Required<ClassNames>;\n private workerSrc?: string;\n private pdfDoc: PDFDocumentProxy | null = null;\n private pageData: PageData[] = [];\n private pdfjsLib: any = null;\n private effectiveScale: number = 1;\n\n constructor(container: HTMLElement, options: PDFSearchViewerOptions) {\n this.container = container;\n this.scale = options.scale ?? DEFAULT_SCALE;\n this.pageGap = options.pageGap ?? DEFAULT_PAGE_GAP;\n this.workerSrc = options.workerSrc;\n this.cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n }\n\n /**\n * Set the pdfjs-dist library reference.\n * Must be called before loadDocument.\n */\n setPdfjsLib(lib: any): void {\n this.pdfjsLib = lib;\n if (this.workerSrc) {\n lib.GlobalWorkerOptions.workerSrc = this.workerSrc;\n }\n }\n\n /**\n * Load a PDF from File, ArrayBuffer, URL string, or Uint8Array.\n */\n async loadDocument(\n source: File | ArrayBuffer | Uint8Array | string\n ): Promise<number> {\n if (!this.pdfjsLib) {\n throw new Error(\n 'pdfjs-dist not set. Call setPdfjsLib(pdfjsLib) before loading a document.'\n );\n }\n\n this.cleanup();\n\n let data: ArrayBuffer | Uint8Array | { url: string };\n if (source instanceof File) {\n data = await source.arrayBuffer();\n } else if (typeof source === 'string') {\n data = { url: source };\n } else {\n data = source;\n }\n\n const loadingTask = this.pdfjsLib.getDocument({ data });\n this.pdfDoc = await loadingTask.promise;\n return this.pdfDoc.numPages;\n }\n\n /**\n * Render all pages into the container.\n * Preserves scroll position across re-renders (e.g. zoom).\n * Returns PageData[] for search/highlight.\n */\n async renderAllPages(): Promise<PageData[]> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n // Save scroll position relative to total content height\n const prevScrollTop = this.container.scrollTop;\n const prevScrollHeight = this.container.scrollHeight || 1;\n const scrollRatio = prevScrollTop / prevScrollHeight;\n\n this.container.innerHTML = '';\n this.container.classList.add(this.cls.container);\n this.pageData = [];\n\n const numPages = this.pdfDoc.numPages;\n\n for (let i = 1; i <= numPages; i++) {\n const page = await this.pdfDoc.getPage(i);\n const pd = await this.renderPage(page, i, numPages);\n this.pageData.push(pd);\n }\n\n // Restore scroll position proportionally\n if (prevScrollTop > 0) {\n this.container.scrollTop = scrollRatio * this.container.scrollHeight;\n }\n\n return this.pageData;\n }\n\n private async renderPage(\n page: PDFPageProxy,\n pageNum: number,\n totalPages: number\n ): Promise<PageData> {\n const scale = this.calculateScale(page);\n if (pageNum === 1) this.effectiveScale = scale;\n const vp = page.getViewport({ scale });\n\n // Page container\n const container = document.createElement('div');\n container.className = this.cls.page;\n container.style.position = 'relative';\n container.style.width = vp.width + 'px';\n container.style.height = vp.height + 'px';\n container.style.margin = '0 auto';\n container.style.marginBottom = this.pageGap + 'px';\n container.style.overflow = 'hidden';\n container.dataset.page = String(pageNum);\n\n // Canvas (2x for retina)\n const canvas = document.createElement('canvas');\n canvas.className = this.cls.canvas;\n canvas.width = vp.width * 2;\n canvas.height = vp.height * 2;\n canvas.style.width = vp.width + 'px';\n canvas.style.height = vp.height + 'px';\n canvas.style.display = 'block';\n const ctx = canvas.getContext('2d')!;\n ctx.scale(2, 2);\n await page.render({ canvasContext: ctx, viewport: vp }).promise;\n\n // Text layer\n const textLayer = document.createElement('div');\n textLayer.className = this.cls.textLayer;\n textLayer.style.position = 'absolute';\n textLayer.style.top = '0';\n textLayer.style.left = '0';\n textLayer.style.right = '0';\n textLayer.style.bottom = '0';\n textLayer.style.overflow = 'hidden';\n textLayer.style.lineHeight = '1';\n\n const tc = await page.getTextContent();\n const spans: SpanData[] = [];\n\n for (const item of tc.items) {\n if (!item.str && !item.hasEOL) continue;\n\n const tx = this.pdfjsLib.Util.transform(vp.transform, item.transform);\n const span = document.createElement('span');\n span.textContent = item.str || '';\n const fh = Math.hypot(tx[2], tx[3]);\n span.style.position = 'absolute';\n span.style.left = tx[4] + 'px';\n span.style.top = (tx[5] - fh) + 'px';\n span.style.fontSize = fh + 'px';\n span.style.color = 'transparent';\n span.style.whiteSpace = 'pre';\n span.style.cursor = 'text';\n span.style.transformOrigin = '0% 0%';\n if (item.fontName) span.style.fontFamily = item.fontName;\n\n const sw = tx[0] / fh;\n if (Math.abs(sw - 1) > 0.01) {\n span.style.transform = `scaleX(${sw})`;\n }\n\n textLayer.appendChild(span);\n spans.push({\n el: span,\n text: item.str || '',\n hasEOL: !!item.hasEOL,\n });\n }\n\n container.appendChild(canvas);\n container.appendChild(textLayer);\n this.container.appendChild(container);\n\n // Page label\n const label = document.createElement('div');\n label.className = this.cls.pageLabel;\n label.textContent = `Page ${pageNum} / ${totalPages}`;\n this.container.appendChild(label);\n\n return { container, spans };\n }\n\n private calculateScale(page: PDFPageProxy): number {\n if (this.scale !== 'auto' && typeof this.scale === 'number') {\n return this.scale;\n }\n const defaultVp = page.getViewport({ scale: 1 });\n const containerWidth = this.container.clientWidth || 800;\n return Math.min(containerWidth / defaultVp.width, 2);\n }\n\n /** Set the scale for subsequent renders. */\n setScale(scale: number | 'auto'): void {\n this.scale = scale;\n }\n\n /** Get the configured scale setting. */\n getScale(): number | 'auto' {\n return this.scale;\n }\n\n /** Get the actual numeric scale used in the last render. */\n getEffectiveScale(): number {\n return this.effectiveScale;\n }\n\n /**\n * Download the currently loaded PDF.\n */\n async download(filename: string = 'document.pdf'): Promise<void> {\n if (!this.pdfDoc) throw new Error('No PDF document loaded');\n\n const data = await this.pdfDoc.getData();\n const blob = new Blob([data as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n\n getClassNames(): Required<ClassNames> {\n return this.cls;\n }\n\n getPageData(): PageData[] {\n return this.pageData;\n }\n\n getPageCount(): number {\n return this.pdfDoc?.numPages ?? 0;\n }\n\n cleanup(): void {\n this.pdfDoc?.destroy();\n this.pdfDoc = null;\n this.pageData = [];\n this.container.innerHTML = '';\n }\n}\n","import type { SearchOptions, SpanData } from '../types';\n\nexport interface CharMapEntry {\n spanIdx: number;\n charIdx: number;\n}\n\nexport interface MatchRange {\n spanIdx: number;\n start: number;\n end: number;\n}\n\nexport interface SearchResult {\n /** Array of span ranges for each match */\n matchRanges: MatchRange[][];\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Build a flexible regex from query.\n *\n * For queries < 200 chars (after removing whitespace):\n * - Strip all whitespace from query\n * - Insert \\s* between every character\n * → \"and expensive\" becomes a\\s*n\\s*d\\s*e\\s*x\\s*p\\s*e\\s*n\\s*s\\s*i\\s*v\\s*e\n * → Matches regardless of whitespace differences in PDF text\n *\n * For queries >= 200 chars:\n * - Split by whitespace, join with \\s+\n */\nfunction buildFlexibleRegex(\n query: string,\n options: SearchOptions\n): RegExp | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const flexibleWhitespace = options.flexibleWhitespace ?? true;\n\n if (!flexibleWhitespace) {\n // Simple literal search\n const pattern = escapeRegex(trimmed);\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Remove all whitespace chars from query\n const chars = [...trimmed].filter((c) => !/\\s/.test(c));\n if (chars.length === 0) return null;\n\n if (chars.length > 200) {\n // Fallback: flexible between tokens only\n const tokens = trimmed.split(/\\s+/);\n const pattern = tokens.map((t) => escapeRegex(t)).join('\\\\s+');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n }\n\n // Insert \\s* between every character\n const pattern = chars.map((c) => escapeRegex(c)).join('\\\\s*');\n return new RegExp(pattern, isCaseSensitive ? 'g' : 'gi');\n}\n\ninterface FuzzyMatch {\n start: number;\n end: number;\n distance: number;\n}\n\n/**\n * Semi-global Levenshtein alignment for approximate substring matching.\n *\n * Finds all positions in `text` where a substring has edit distance ≤ maxErrors\n * from `query`. Uses O(m) space with single-column DP.\n *\n * Semi-global: first DP column = 0 (match can start anywhere in text),\n * but the full query must be covered.\n */\nfunction fuzzySearchText(\n text: string,\n query: string,\n maxErrors: number\n): FuzzyMatch[] {\n const n = text.length;\n const m = query.length;\n if (m === 0) return [];\n if (n === 0) return [];\n\n // DP: prev[j] = min edit distance to match query[0..j-1] ending at current text pos\n // Semi-global: prev[0] = 0 for all text positions (free start)\n let prev = new Uint32Array(m + 1);\n for (let j = 0; j <= m; j++) prev[j] = j;\n\n // Store full DP matrix columns for traceback\n const columns: Uint32Array[] = [prev.slice()];\n\n // Track match end positions\n const endPositions: Array<{ col: number; distance: number }> = [];\n\n for (let i = 1; i <= n; i++) {\n const curr = new Uint32Array(m + 1);\n curr[0] = 0; // semi-global: free start position\n for (let j = 1; j <= m; j++) {\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n prev[j] + 1, // deletion\n curr[j - 1] + 1, // insertion\n prev[j - 1] + cost // substitution\n );\n }\n columns.push(curr.slice());\n\n if (curr[m] <= maxErrors) {\n endPositions.push({ col: i, distance: curr[m] });\n }\n prev = curr;\n }\n\n if (endPositions.length === 0) return [];\n\n // Traceback to find start position for each match end\n const rawMatches: FuzzyMatch[] = [];\n for (const { col: endCol, distance } of endPositions) {\n // Trace back through the DP matrix to find where the match starts\n let j = m;\n let i = endCol;\n while (j > 0 && i > 0) {\n const c = columns[i];\n const p = columns[i - 1];\n const cost = text[i - 1] === query[j - 1] ? 0 : 1;\n if (c[j] === p[j - 1] + cost) {\n // substitution or match — move diagonally\n i--;\n j--;\n } else if (c[j] === p[j] + 1) {\n // deletion from text — move left in text\n i--;\n } else {\n // insertion into text — move up in query\n j--;\n }\n }\n rawMatches.push({ start: i, end: endCol, distance });\n }\n\n // Merge overlapping matches, keeping the one with lowest distance\n if (rawMatches.length === 0) return [];\n rawMatches.sort((a, b) => a.start - b.start || a.distance - b.distance);\n\n const merged: FuzzyMatch[] = [rawMatches[0]];\n for (let i = 1; i < rawMatches.length; i++) {\n const prev = merged[merged.length - 1];\n const curr = rawMatches[i];\n if (curr.start < prev.end) {\n // Overlapping — keep the one with lower distance\n if (curr.distance < prev.distance) {\n merged[merged.length - 1] = curr;\n }\n } else {\n merged.push(curr);\n }\n }\n\n return merged;\n}\n\n/**\n * Build fullText and charMap from spans.\n */\nfunction buildTextAndCharMap(spans: SpanData[]) {\n let fullText = '';\n const charMap: CharMapEntry[] = [];\n spans.forEach((s, si) => {\n for (let ci = 0; ci < s.text.length; ci++) {\n charMap.push({ spanIdx: si, charIdx: ci });\n fullText += s.text[ci];\n }\n });\n return { fullText, charMap };\n}\n\n/**\n * Map a start/end range in fullText to MatchRange[] via charMap.\n */\nfunction mapToSpanRanges(\n start: number,\n end: number,\n charMap: CharMapEntry[]\n): MatchRange[] {\n const range: MatchRange[] = [];\n for (let k = start; k < end; k++) {\n const cm = charMap[k];\n const last = range[range.length - 1];\n if (last && last.spanIdx === cm.spanIdx && last.end === cm.charIdx) {\n last.end = cm.charIdx + 1;\n } else {\n range.push({ spanIdx: cm.spanIdx, start: cm.charIdx, end: cm.charIdx + 1 });\n }\n }\n return range;\n}\n\n/**\n * Search for text across page spans using charMap-based matching.\n *\n * Algorithm:\n * 1. Concatenate all span texts into one string (fullText)\n * 2. Build charMap: charMap[i] = { spanIdx, charIdx } for each char in fullText\n * 3. Run regex or fuzzy search on fullText\n * 4. Map each match back to span ranges via charMap\n */\nexport function searchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions = {}\n): MatchRange[][] {\n const trimmed = query.trim();\n if (!trimmed) return [];\n\n if (options.fuzzy) {\n return fuzzySearchPage(spans, trimmed, options);\n }\n\n const regex = buildFlexibleRegex(query, options);\n if (!regex) return [];\n\n const { fullText, charMap } = buildTextAndCharMap(spans);\n\n // Find all regex matches\n const allMatchRanges: MatchRange[][] = [];\n let m: RegExpExecArray | null;\n regex.lastIndex = 0;\n\n while ((m = regex.exec(fullText)) !== null) {\n allMatchRanges.push(mapToSpanRanges(m.index, m.index + m[0].length, charMap));\n if (m[0].length === 0) regex.lastIndex++;\n }\n\n return allMatchRanges;\n}\n\nfunction fuzzySearchPage(\n spans: SpanData[],\n query: string,\n options: SearchOptions\n): MatchRange[][] {\n const { fullText, charMap } = buildTextAndCharMap(spans);\n if (fullText.length === 0) return [];\n\n const isCaseSensitive = options.caseSensitive ?? false;\n const threshold = options.fuzzyThreshold ?? 0.6;\n\n const searchText = isCaseSensitive ? fullText : fullText.toLowerCase();\n const searchQuery = isCaseSensitive ? query : query.toLowerCase();\n\n // Strip whitespace from query for matching\n const strippedQuery = searchQuery.replace(/\\s+/g, '');\n if (strippedQuery.length === 0) return [];\n\n const maxErrors = Math.floor(strippedQuery.length * (1 - threshold));\n const matches = fuzzySearchText(searchText, strippedQuery, maxErrors);\n\n return matches.map((m) => mapToSpanRanges(m.start, m.end, charMap));\n}\n","import type { SearchMatch, SpanData, PageData } from '../types';\nimport type { MatchRange } from './SearchEngine';\n\n/**\n * Manages cross-span highlighting using the charMap approach.\n *\n * Algorithm (from demo):\n * 1. Group match ranges by spanIdx\n * 2. For each affected span, replace textContent with a DocumentFragment:\n * - Plain text nodes for non-matching parts\n * - <mark> elements for matching parts\n * 3. Collect marks per match for navigation\n */\nexport class HighlightManager {\n private matches: SearchMatch[] = [];\n private currentMatch = -1;\n private highlightClass: string;\n private activeHighlightClass: string;\n\n constructor(highlightClass: string, activeHighlightClass: string) {\n this.highlightClass = highlightClass;\n this.activeHighlightClass = activeHighlightClass;\n }\n\n /**\n * Apply highlights for all matches on a page.\n * Returns the SearchMatch[] (array of mark groups).\n *\n * @param classPerMatch - Optional per-match CSS class names (for multi-context search).\n * When provided, `classPerMatch[i]` is the CSS class for `matchRanges[i]`.\n * When omitted, all matches use the default highlight class.\n */\n applyHighlights(\n pageSpans: SpanData[],\n matchRanges: MatchRange[][],\n classPerMatch?: string[]\n ): SearchMatch[] {\n if (!matchRanges.length) return [];\n\n // Group ranges by spanIdx, keeping track of which match they belong to\n const spanRanges: Record<\n number,\n { start: number; end: number; matchIdx: number }[]\n > = {};\n\n matchRanges.forEach((range, mi) => {\n range.forEach((r) => {\n if (!spanRanges[r.spanIdx]) spanRanges[r.spanIdx] = [];\n spanRanges[r.spanIdx].push({ start: r.start, end: r.end, matchIdx: mi });\n });\n });\n\n // Collect marks per match\n const matchMarks: HTMLElement[][] = matchRanges.map(() => []);\n\n // For each affected span, rebuild DOM with highlights\n for (const siStr of Object.keys(spanRanges)) {\n const si = parseInt(siStr, 10);\n const s = pageSpans[si];\n const ranges = spanRanges[si].sort((a, b) => a.start - b.start);\n\n const frag = document.createDocumentFragment();\n let last = 0;\n\n for (const r of ranges) {\n const actualStart = Math.max(r.start, last);\n\n // Add plain text before highlight\n if (actualStart > last) {\n frag.appendChild(document.createTextNode(s.text.slice(last, actualStart)));\n }\n\n // Add highlight mark\n if (actualStart < r.end) {\n const mark = document.createElement('mark');\n mark.className = classPerMatch?.[r.matchIdx] ?? this.highlightClass;\n mark.textContent = s.text.slice(actualStart, r.end);\n frag.appendChild(mark);\n matchMarks[r.matchIdx].push(mark);\n }\n\n last = Math.max(last, r.end);\n }\n\n // Add remaining plain text\n if (last < s.text.length) {\n frag.appendChild(document.createTextNode(s.text.slice(last)));\n }\n\n // Replace span content\n s.el.textContent = '';\n s.el.appendChild(frag);\n }\n\n return matchMarks\n .filter((marks) => marks.length > 0)\n .map((marks) => ({ marks }));\n }\n\n /**\n * Add matches to the global list.\n */\n addMatches(newMatches: SearchMatch[]): void {\n this.matches.push(...newMatches);\n }\n\n /**\n * Clear all highlights and restore original span text.\n */\n clearHighlights(allPageData: PageData[]): void {\n allPageData.forEach((pd) => {\n pd.spans.forEach((s) => {\n s.el.textContent = s.text;\n });\n });\n this.matches = [];\n this.currentMatch = -1;\n }\n\n /**\n * Set active match by index. Applies active CSS class and scrolls into view.\n */\n setActiveMatch(index: number): void {\n // Remove active class from previous\n if (this.currentMatch >= 0 && this.currentMatch < this.matches.length) {\n this.matches[this.currentMatch].marks.forEach((m) =>\n m.classList.remove(this.activeHighlightClass)\n );\n }\n\n this.currentMatch = index;\n\n if (index >= 0 && index < this.matches.length) {\n this.matches[index].marks.forEach((m) =>\n m.classList.add(this.activeHighlightClass)\n );\n // Scroll first mark into view\n this.matches[index].marks[0]?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n next(): number {\n if (this.matches.length === 0) return -1;\n const newIdx = (this.currentMatch + 1) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prev(): number {\n if (this.matches.length === 0) return -1;\n const newIdx =\n (this.currentMatch - 1 + this.matches.length) % this.matches.length;\n this.setActiveMatch(newIdx);\n return newIdx;\n }\n\n getCurrentIndex(): number {\n return this.currentMatch;\n }\n\n getTotal(): number {\n return this.matches.length;\n }\n\n getMatches(): SearchMatch[] {\n return this.matches;\n }\n}\n","import { EventEmitter } from './EventEmitter';\nimport { PDFRenderer } from './PDFRenderer';\nimport { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, ZOOM_STEP, MIN_SCALE, MAX_SCALE, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type {\n PDFSearchViewerOptions,\n SearchOptions,\n SearchContext,\n PDFSearchViewerEventMap,\n PageData,\n} from '../types';\n\nexport type PDFSource = File | ArrayBuffer | Uint8Array | string;\n\n/**\n * Main PDF viewer with search and highlight functionality.\n *\n * Usage:\n * ```js\n * import * as pdfjsLib from 'pdfjs-dist';\n * import { PDFSearchViewer } from 'pdf-search-highlight';\n *\n * const viewer = new PDFSearchViewer(container, pdfjsLib, {\n * classNames: {\n * page: 'my-page',\n * highlight: 'my-highlight',\n * activeHighlight: 'my-active',\n * }\n * });\n * await viewer.loadPDF(file);\n * viewer.search('hello');\n * viewer.nextMatch();\n * ```\n */\nexport class PDFSearchViewer extends EventEmitter<PDFSearchViewerEventMap> {\n private renderer: PDFRenderer;\n private highlightManager: HighlightManager;\n private pageData: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n private destroyed = false;\n\n constructor(\n container: HTMLElement,\n pdfjsLib: any,\n options: PDFSearchViewerOptions = {}\n ) {\n super();\n\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n\n this.renderer = new PDFRenderer(container, options);\n this.renderer.setPdfjsLib(pdfjsLib);\n this.highlightManager = new HighlightManager(\n cls.highlight,\n cls.activeHighlight\n );\n }\n\n /**\n * Load and render a PDF document.\n */\n async loadPDF(source: PDFSource): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n try {\n await this.renderer.loadDocument(source);\n this.pageData = await this.renderer.renderAllPages();\n const pageCount = this.renderer.getPageCount();\n this.emit('load', { pageCount });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', { error, context: 'loadPDF' });\n throw error;\n }\n }\n\n /**\n * Search for text across all pages.\n * Clears previous highlights and creates new ones.\n */\n search(query: string, options: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n // Clear previous highlights\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.emit('search', { query, total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n // Search each page and apply highlights\n for (const pd of this.pageData) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n // Auto-activate first match\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('search', { query, total });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different color (highlight-0, highlight-1, ...).\n * Navigation (nextMatch/prevMatch) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n\n this.highlightManager.clearHighlights(this.pageData);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.emit('searchmultiple', { contexts, total: 0, totalsPerContext: contexts.map(() => 0) });\n this.emit('matchchange', { current: -1, total: 0 });\n return 0;\n }\n\n const totalsPerContext = new Array(validContexts.length).fill(0);\n\n for (const pd of this.pageData) {\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n const contextPerMatch: number[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n contextPerMatch.push(ci);\n }\n }\n\n // Sort all matches by document position\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // Count per context\n for (const i of indices) {\n totalsPerContext[contextPerMatch[i]]++;\n }\n\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.emit('searchmultiple', { contexts, total, totalsPerContext });\n this.emit('matchchange', {\n current: total > 0 ? 0 : -1,\n total,\n });\n\n return total;\n }\n\n /**\n * Navigate to next match (wraps around).\n */\n nextMatch(): number {\n const idx = this.highlightManager.next();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Navigate to previous match (wraps around).\n */\n prevMatch(): number {\n const idx = this.highlightManager.prev();\n this.emit('matchchange', {\n current: idx,\n total: this.highlightManager.getTotal(),\n });\n return idx;\n }\n\n /**\n * Clear all search highlights.\n */\n clearSearch(): void {\n this.highlightManager.clearHighlights(this.pageData);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.emit('search', { query: '', total: 0 });\n this.emit('matchchange', { current: -1, total: 0 });\n }\n\n /** Get the current scale setting. */\n getScale(): number | 'auto' {\n return this.renderer.getScale();\n }\n\n /** Set scale and re-render. Preserves current search state. */\n async setScale(scale: number | 'auto'): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n this.renderer.setScale(scale);\n await this.rerender();\n this.emit('zoom', { scale: this.renderer.getEffectiveScale() });\n }\n\n /** Zoom in by one step. */\n async zoomIn(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.min(current + ZOOM_STEP, MAX_SCALE);\n await this.setScale(newScale);\n }\n\n /** Zoom out by one step. */\n async zoomOut(): Promise<void> {\n const current = this.resolveCurrentScale();\n const newScale = Math.max(current - ZOOM_STEP, MIN_SCALE);\n await this.setScale(newScale);\n }\n\n /** Download the currently loaded PDF. */\n async download(filename?: string): Promise<void> {\n if (this.destroyed) throw new Error('PDFSearchViewer has been destroyed');\n await this.renderer.download(filename);\n }\n\n private resolveCurrentScale(): number {\n const s = this.renderer.getScale();\n return s === 'auto' ? this.renderer.getEffectiveScale() : s;\n }\n\n private async rerender(): Promise<void> {\n this.highlightManager.clearHighlights(this.pageData);\n this.pageData = await this.renderer.renderAllPages();\n\n if (this.lastIsMultiContext && this.lastContexts.length > 0) {\n this.searchMultiple(this.lastContexts, this.lastSearchOptions);\n } else if (this.lastQuery.trim()) {\n this.search(this.lastQuery, this.lastSearchOptions);\n }\n }\n\n /**\n * Get total number of pages.\n */\n getPageCount(): number {\n return this.renderer.getPageCount();\n }\n\n /**\n * Get current active match index (0-based). -1 if none.\n */\n getCurrentMatchIndex(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /**\n * Get total number of matches.\n */\n getMatchCount(): number {\n return this.highlightManager.getTotal();\n }\n\n /**\n * Destroy the viewer, release all resources.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.highlightManager.clearHighlights(this.pageData);\n this.renderer.cleanup();\n this.removeAllListeners();\n this.pageData = [];\n }\n}\n","import { searchPage } from './SearchEngine';\nimport type { MatchRange } from './SearchEngine';\nimport { HighlightManager } from './HighlightManager';\nimport { DEFAULT_CLASS_NAMES, MULTI_CONTEXT_COLOR_COUNT } from './constants';\nimport type { SearchOptions, ClassNames, PageData, SearchContext } from '../types';\n\nexport interface SearchControllerOptions {\n classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;\n}\n\n/**\n * Headless search + highlight controller.\n * Does NOT render PDF — works with any PageData[] you provide.\n *\n * Use this when you want full control over:\n * - Where the PDF is rendered\n * - Where the search UI lives\n * - How search results are displayed\n *\n * Usage:\n * ```js\n * import { PDFRenderer, SearchController } from 'pdf-search-highlight';\n *\n * // Render PDF wherever you want\n * const renderer = new PDFRenderer(pdfContainer, pdfjsLib, {});\n * const pages = await renderer.renderAllPages();\n *\n * // Search controller — no UI, just logic\n * const search = new SearchController();\n * search.setPages(pages);\n *\n * // Wire up your own UI\n * input.oninput = () => search.search(input.value);\n * nextBtn.onclick = () => search.next();\n * prevBtn.onclick = () => search.prev();\n *\n * // React to changes\n * search.onChange = ({ current, total }) => {\n * label.textContent = total > 0 ? `${current + 1}/${total}` : '';\n * };\n * ```\n */\nexport class SearchController {\n private highlightManager: HighlightManager;\n private pages: PageData[] = [];\n private lastQuery = '';\n private lastSearchOptions: SearchOptions = {};\n private lastContexts: SearchContext[] = [];\n private lastIsMultiContext = false;\n\n /** Callback fired when match state changes (search, next, prev, clear). */\n onChange: ((state: { current: number; total: number; query: string }) => void) | null = null;\n\n constructor(options: SearchControllerOptions = {}) {\n const cls = { ...DEFAULT_CLASS_NAMES, ...options.classNames };\n this.highlightManager = new HighlightManager(cls.highlight, cls.activeHighlight);\n }\n\n /**\n * Set the pages to search on.\n * Call this after rendering PDF pages.\n */\n setPages(pages: PageData[]): void {\n const savedQuery = this.lastQuery;\n const savedOptions = this.lastSearchOptions;\n const savedContexts = [...this.lastContexts];\n const savedIsMulti = this.lastIsMultiContext;\n\n this.clear();\n this.pages = pages;\n\n // Re-apply search if there was an active query (e.g. after zoom)\n if (savedIsMulti && savedContexts.length > 0) {\n this.searchMultiple(savedContexts, savedOptions);\n } else if (savedQuery.trim()) {\n this.search(savedQuery, savedOptions);\n }\n }\n\n /**\n * Search for text across all pages.\n * Returns total number of matches.\n */\n search(query: string, options: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = query;\n this.lastSearchOptions = options;\n this.lastIsMultiContext = false;\n this.lastContexts = [];\n\n const trimmed = query.trim();\n if (!trimmed) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n const matchRanges = searchPage(pd.spans, trimmed, options);\n const matches = this.highlightManager.applyHighlights(pd.spans, matchRanges);\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /**\n * Search for multiple query contexts across all pages.\n * Each context is highlighted with a different CSS class (highlight-0, highlight-1, ...).\n * Navigation (next/prev) cycles through ALL matches in document order.\n * Returns total number of matches across all contexts.\n */\n searchMultiple(contexts: SearchContext[], sharedOptions: SearchOptions = {}): number {\n this.highlightManager.clearHighlights(this.pages);\n this.lastContexts = contexts;\n this.lastIsMultiContext = true;\n this.lastQuery = '';\n this.lastSearchOptions = sharedOptions;\n\n const validContexts = contexts.filter((c) => c.query.trim());\n if (validContexts.length === 0) {\n this.notify();\n return 0;\n }\n\n for (const pd of this.pages) {\n // 1. Search all contexts on this page\n const allMatchRanges: MatchRange[][] = [];\n const classPerMatch: string[] = [];\n\n for (let ci = 0; ci < validContexts.length; ci++) {\n const ctx = validContexts[ci];\n const opts = { ...sharedOptions, ...ctx.options };\n const pageMatches = searchPage(pd.spans, ctx.query.trim(), opts);\n\n for (const matchRange of pageMatches) {\n allMatchRanges.push(matchRange);\n classPerMatch.push(`highlight-${ci % MULTI_CONTEXT_COLOR_COUNT}`);\n }\n }\n\n // 2. Sort all matches by document position (span index, then char offset)\n const indices = allMatchRanges.map((_, i) => i);\n indices.sort((a, b) => {\n const aFirst = allMatchRanges[a][0];\n const bFirst = allMatchRanges[b][0];\n if (!aFirst || !bFirst) return 0;\n if (aFirst.spanIdx !== bFirst.spanIdx) return aFirst.spanIdx - bFirst.spanIdx;\n return aFirst.start - bFirst.start;\n });\n\n const sortedRanges = indices.map((i) => allMatchRanges[i]);\n const sortedClasses = indices.map((i) => classPerMatch[i]);\n\n // 3. Apply highlights with per-match classes\n const matches = this.highlightManager.applyHighlights(\n pd.spans,\n sortedRanges,\n sortedClasses\n );\n this.highlightManager.addMatches(matches);\n }\n\n const total = this.highlightManager.getTotal();\n if (total > 0) {\n this.highlightManager.setActiveMatch(0);\n }\n\n this.notify();\n return total;\n }\n\n /** Navigate to next match. Returns new index. */\n next(): number {\n const idx = this.highlightManager.next();\n this.notify();\n return idx;\n }\n\n /** Navigate to previous match. Returns new index. */\n prev(): number {\n const idx = this.highlightManager.prev();\n this.notify();\n return idx;\n }\n\n /** Go to a specific match by index. */\n goTo(index: number): void {\n this.highlightManager.setActiveMatch(index);\n this.notify();\n }\n\n /** Clear all highlights. */\n clear(): void {\n this.highlightManager.clearHighlights(this.pages);\n this.lastQuery = '';\n this.lastContexts = [];\n this.lastIsMultiContext = false;\n this.notify();\n }\n\n /** Current match index (0-based). -1 if none. */\n get current(): number {\n return this.highlightManager.getCurrentIndex();\n }\n\n /** Total number of matches. */\n get total(): number {\n return this.highlightManager.getTotal();\n }\n\n /** Last searched query. */\n get query(): string {\n return this.lastQuery;\n }\n\n /** Last searched contexts (for multi-context search). */\n get contexts(): SearchContext[] {\n return this.lastContexts;\n }\n\n private notify(): void {\n this.onChange?.({\n current: this.highlightManager.getCurrentIndex(),\n total: this.highlightManager.getTotal(),\n query: this.lastQuery,\n });\n }\n}\n"],"mappings":"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","names":["pattern","prev"]}