惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
MongoDB | Blog
MongoDB | Blog
GbyAI
GbyAI
L
LangChain Blog
B
Blog
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
U
Unit 42
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
小众软件
小众软件
I
InfoQ
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
C
Check Point Blog

Inside Nutrient

A guide to the invisible work behind documents Introducing Nutrient Documents for Salesforce: Native document generation and signing Document AI vs. traditional OCR: Choosing between OCR, AI, and hybrid pipelines PDF SDK compliance and security evaluation checklist for enterprise teams (2026) Invariant Corp replaces paper processes with Nutrient Workflow and scales without limits What is process mapping? A complete guide Nutrient vs. Conga Composer for Salesforce document generation (2026) Document routing: How to automate document distribution The CTO’s AI playbook: Why accountability architecture beats orchestration Compliance workflow automation: Why built-in compliance is table stakes Workflow diagrams: Examples, symbols, and how to build one that actually runs Digital forms: Replace paper forms with automated workflows Approval workflow software: How to automate approvals Why document-centric automation is different The CEO’s AI playbook: Why decision architecture beats model selection Nutrient SDK product updates for Q1 2026 PDF redaction verification: How to prove sensitive data is permanently removed What is a VPAT? The complete guide to accessibility conformance reports What is PDF/UA? The accessible PDF standard explained Salesforce eSignatures: Generate, sign, and track documents in one flow Online document viewer: Options, tradeoffs, and how to embed one Document viewer for web apps: React, Vue, Angular (2026) Best document viewers in 2026: A buyer’s guide How to edit a PDF in Python: Add text, images, and annotations Nutrient advances Workflow platform with agentic AI for enterprise-grade speed and consistency in document-heavy operations How to create a Salesforce quote template from opportunity data The business case for accessibility: Five ways it drives enterprise value Python PDF library comparison (2026): 7 libraries for developers Why your AI agent hallucinates PDF table data PDF.js limitations: When to upgrade to a commercial PDF SDK
How to implement text search in PDF.js with PDFFindContro...
Austin Nguyen · 2026-06-11 · via Inside Nutrient

Table of contents

    How to implement text search in PDF.js with PDFFindController

    TL;DR

    PDF.js’s PDFFindController handles full-document text search. You control it entirely through EventBus events — never direct method calls. The wiring has three parts:

    • Setup — Construct PDFFindController({ linkService, eventBus }) and call findController.setDocument(pdfDocument) after the document loads.
    • Dispatch — Call eventBus.dispatch("find", { type, query, caseSensitive, entireWord, highlightAll, matchDiacritics, findPrevious }) to trigger or update a search. type: "" starts a new search; type: "again" walks to the next or previous match.
    • Listen — Subscribe to updatefindmatchescount for running totals and updatefindcontrolstate for the final FindState (FOUND, NOT_FOUND, PENDING).

    The controller scrolls to and highlights the active match for you. If you’d rather skip the EventBus dance, Nutrient Web SDK exposes instance.search() and a built-in search UI.

    PDF.js includes a built-in PDFFindController that handles text search across all pages. This guide shows how to wire it up with a custom React search UI, handle match counts, and navigate between results.

    Setup

    The PDFFindController is created during viewer initialization and needs both the EventBus and PDFLinkService:

    const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");

    const eventBus = new pdfjs.EventBus();

    const linkService = new pdfjs.PDFLinkService({ eventBus });

    const findController = new pdfjs.PDFFindController({ linkService, eventBus });

    // After document loads:

    findController.setDocument(pdfDocument);

    Search is controlled entirely through EventBus find events. You never call methods on the find controller directly — you dispatch events:

    eventBus.dispatch("find", {

    type: "", // "" for new search, "again" for next/prev

    query: "neural network",

    caseSensitive: false,

    entireWord: false,

    highlightAll: true,

    matchDiacritics: false,

    findPrevious: false, // true = search backwards

    });

    The type parameter

    ValuePurpose
    ""Start a new search
    "again"Navigate to next/previous result
    "casesensitivitychange"Re-search with changed case sensitivity
    "entirewordchange"Re-search with changed whole-word setting
    "highlightallchange"Toggle highlight-all
    "diacriticmatchingchange"Toggle diacritics matching

    Listening for results

    Subscribe to two events to track search state:

    // Match count updates.

    eventBus.on("updatefindmatchescount", (evt) => {

    console.log(`Result ${evt.matchesCount.current} of ${evt.matchesCount.total}`);

    });

    // Find state changes.

    eventBus.on("updatefindcontrolstate", (evt) => {

    // evt.state is a FindState enum value

    // evt.matchesCount = { current, total }

    switch (evt.state) {

    case pdfjs.FindState.FOUND:

    // Match found.

    break;

    case pdfjs.FindState.NOT_FOUND:

    // No matches.

    break;

    case pdfjs.FindState.PENDING:

    // Still searching...

    break;

    }

    });

    Complete React search component

    import { useCallback, useContext, useEffect, useRef, useState } from "react";

    import { FindState } from "pdfjs-dist/web/pdf_viewer.mjs";

    import { PDFContext } from "./PDFContext";

    function SearchToolbar() {

    const { eventBus } = useContext(PDFContext);

    const [query, setQuery] = useState("");

    const [matchCount, setMatchCount] = useState({ current: 0, total: 0 });

    const [findState, setFindState] = useState(null);

    // Search options.

    const [caseSensitive, setCaseSensitive] = useState(false);

    const [entireWord, setEntireWord] = useState(false);

    const [highlightAll, setHighlightAll] = useState(false);

    const [matchDiacritics, setMatchDiacritics] = useState(false);

    // Track previous values to determine which option changed.

    const prevQuery = useRef("");

    const prevCaseSensitive = useRef(false);

    const prevEntireWord = useRef(false);

    const prevHighlightAll = useRef(false);

    const prevMatchDiacritics = useRef(false);

    const dispatchFind = useCallback(

    (type, findPrevious = false) => {

    eventBus.current?.dispatch("find", {

    type,

    query,

    caseSensitive,

    entireWord,

    highlightAll,

    matchDiacritics,

    findPrevious,

    });

    },

    [eventBus, query, caseSensitive, entireWord, highlightAll, matchDiacritics],

    );

    // Dispatch appropriate event when any search parameter changes.

    useEffect(() => {

    if (query !== prevQuery.current) {

    dispatchFind("");

    } else if (caseSensitive !== prevCaseSensitive.current) {

    dispatchFind("casesensitivitychange");

    } else if (entireWord !== prevEntireWord.current) {

    dispatchFind("entirewordchange");

    } else if (highlightAll !== prevHighlightAll.current) {

    dispatchFind("highlightallchange");

    } else if (matchDiacritics !== prevMatchDiacritics.current) {

    dispatchFind("diacriticmatchingchange");

    }

    prevQuery.current = query;

    prevCaseSensitive.current = caseSensitive;

    prevEntireWord.current = entireWord;

    prevHighlightAll.current = highlightAll;

    prevMatchDiacritics.current = matchDiacritics;

    }, [query, caseSensitive, entireWord, highlightAll, matchDiacritics, dispatchFind]);

    // Listen for results.

    useEffect(() => {

    const onMatchCount = (evt) => setMatchCount(evt.matchesCount);

    const onState = (evt) => {

    setFindState(evt.state);

    setMatchCount(evt.matchesCount);

    };

    eventBus.current?.on("updatefindmatchescount", onMatchCount);

    eventBus.current?.on("updatefindcontrolstate", onState);

    return () => {

    eventBus.current?.off("updatefindmatchescount", onMatchCount);

    eventBus.current?.off("updatefindcontrolstate", onState);

    };

    }, [eventBus]);

    return (

    <div>

    <input

    value={query}

    onChange={(e) => setQuery(e.target.value)}

    placeholder="Search..."

    />

    <span>

    {findState === FindState.NOT_FOUND

    ? "No results"

    : `${matchCount.current} of ${matchCount.total}`}

    </span>

    <button onClick={() => dispatchFind("again", true)}>Previous</button>

    <button onClick={() => dispatchFind("again", false)}>Next</button>

    <label>

    <input

    type="checkbox"

    checked={caseSensitive}

    onChange={() => setCaseSensitive(!caseSensitive)}

    />

    Case sensitive

    </label>

    <label>

    <input

    type="checkbox"

    checked={entireWord}

    onChange={() => setEntireWord(!entireWord)}

    />

    Whole word

    </label>

    <label>

    <input

    type="checkbox"

    checked={highlightAll}

    onChange={() => setHighlightAll(!highlightAll)}

    />

    Highlight all

    </label>

    </div>

    );

    }

    How it works under the hood

    1. You dispatch a find event on the EventBus
    2. PDFFindController receives it and searches the text layer of each page
    3. It dispatches updatefindmatchescount with running totals
    4. It dispatches updatefindcontrolstate with the final state
    5. It automatically scrolls to and highlights the current match
    6. Built-in CSS classes (.highlight, .highlight.selected) style the matches

    Key points

    • All search interaction goes through EventBus events, never direct method calls
    • Use type: "" for new searches, and type: "again" for next/previous navigation
    • The findPrevious Boolean controls search direction when using type: "again"
    • highlightAll: true highlights all matches on visible pages, not just the current one
    • Track previous option values to dispatch the correct change event type
    • The find controller handles page-by-page searching automatically — no manual page iteration needed

    FAQ

    PDF.js was designed around its viewer layer, which is fully event-driven. The find controller listens for find events on the EventBus, so the same code path serves the built-in PDF.js toolbar, your custom React UI, and any other client. Calling methods directly would skip the controller’s internal queuing and result tracking, which is why PDF.js doesn’t expose them.

    type: "" starts a fresh search — the controller rebuilds its internal match list from the current query and options. type: "again" navigates within the existing match list using the findPrevious flag (false for next, true for previous). If you dispatch "again" without first running a "" search, it’s a no-op.

    PDF.js needs to know which option changed so it can decide whether to rerun the full search or just refilter existing matches. Toggling highlightAll only redraws — no re-search needed — while changing caseSensitive invalidates the match list and forces a rerun. The granular type values let the controller pick the cheapest path.

    The controller searches asynchronously. updatefindmatchescount fires repeatedly as new pages finish indexing, so you can show a running total. updatefindcontrolstate fires once with the final FindState (FOUND, NOT_FOUND, PENDING, or WRAPPED) and the definitive match count. Most UIs listen to both — totals to the count event, state to the control event.

    That’s the default. PDFFindController walks every page’s text content, not just the rendered ones. You don’t need to scroll the viewer or preload pages. The catch: If you want match counts to update incrementally, leave highlightAll: true so PDF.js paints found matches as soon as a page is indexed.

    Nutrient exposes instance.search(query) returning an array of matches with page indexes and bounding rectangles. Combined with instance.setSearchState(), you can drive the built-in search UI directly without managing EventBus state. The SDK also indexes annotations and form fields, so searches return hits that PDF.js’s text-layer search would miss.

    How Nutrient Web SDK handles this

    Two lines replace the entire PDFFindController setup, EventBus dispatch, and result listener wiring shown above:

    // Search across all pages.

    const results = await instance.search("neural network");

    // Display the results in the built-in search UI.

    instance.setSearchState((state) => state.set("results", results));

    The built-in search UI also provides match counts, case sensitivity, and result navigation.


    See Nutrient Web SDK for a built-in search API and UI, or follow the migration guide to switch from PDF.js. Talk to Sales about your requirements.

    Explore related topics

    Try for free Ready to get started?

    Related SDK articles

    Explore more