Text Search

To search text inside a document create an instance of TextSearch, passing in the loaded PdfDocument via its constructor. The simplest way to retrieve search results is by calling TextSearch#performSearch(String) which will return a list of found SearchResult objects. If a search has no results, this method will return an empty list.

Copy
1
2
3
val textSearch = TextSearch(context, document)
val query = "Weather reports"
val searchResults : List<SearchResult> = textSearch.performSearch(query)
Copy
1
2
3
final TextSearch textSearch = new TextSearch(context, document);
final String query = "Weather reports";
final List<SearchResult> searchResults = textSearch.performSearch(query);

Note: #performSearch is a blocking call and should thus only be used on a background thread.

Asynchronous search

To keep the main thread operational and to avoid ANR dialogs while performing a search, you can use the non-blocking #performSearchAsync method which will return an Flowable<SearchResult>. The flowable emits search results as soon as they are available – you can already use them during the ongoing search, e.g. to populate your search UI.

Copy
1
2
3
4
5
6
7
8
// Perform an async search on a computation thread and update the UI on the main thread.
val searchSubscription = search.performSearchAsync(query)
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { nextResult ->
        // This will be called once for every SearchResult object.
        // Put your search result handling here.
    }
Copy
1
2
3
4
5
6
7
8
9
10
// Perform an async search on a computation thread and update the UI on the main thread.
final Disposable searchSubscription = textSearch.performSearchAsync(query)
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Consumer<SearchResult>() {
        @Override public void accept(SearchResult nextResult) throws Exception {
            // This will be called once for every SearchResult object.
            // Put your search result handling here.
        }
    })

Tip: You can use Flowable#toList to collect all search results if you want to handle them at once.

Specify search options

Both methods #performSearch and #performSearchAsync offer overloaded versions that take SearchOptions for specifying search options. Use this to limit search results, or search result snippet length.

Copy
1
2
3
4
5
6
val options = SearchOptions.Builder(context)
    .maxSearchResults(100)
    .snippetLength(40)
    .build()

val results = textSearch.performSearch(query, options)
Copy
1
2
3
4
5
6
final SearchOptions options = new SearchOptions.Builder(context)
    .maxSearchResults(100)
    .snippetLength(40)
    .build();

final List<SearchResult> results = textSearch.performSearch(query, options);

Note: Calling #performSearch(String) without options will use default options, which does the same like providing a SearchOptions object without calling any of its setters.

Using search results

Every SearchResult consist of the pageNumber, a textBlock containing the PDF coordinates on the page, and an optional snippet containing a preview text for search user interfaces. For example, if you would like to zoom to a search result, you can do like this:

Copy
1
2
3
4
// The search result consists of at least one text rect. This will create a union of all of its rects.
val searchResultRect: RectF = PdfUtils.createPdfRectUnion(searchResult.textBlock.pageRects)
// This will also switch to the correct page if it is not active.
fragment.zoomTo(searchResultRect, searchResult.pageNumber, 200)
Copy
1
2
3
4
// The search result consists of at least one text rect. This will create a union of all of its rects.
final RectF searchResultRect = PdfUtils.createPdfRectUnion(searchResult.textBlock.pageRects);
// This will also switch to the correct page if it is not active.
fragment.zoomTo(searchResultRect, searchResult.pageNumber, 200);

SearchResult#snippet can be used to present an upfront preview of the search result, for example inside a search result list. The snippet consists of the text, and the rangeInSnippet which gives the position of the actual result inside the snippet.

Copy
MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Use the synthetic property for accessing a view from the layout.
import kotlinx.android.synthetic.main.activity_main.previewText

/**
 * Sets the search result snippet to a TextView highlighting the actual search term inside the result.
 */
private fun showSearchResultText(result: SearchResult) {
    // If you deactivated snippet extraction, it may be null.
    val snippet = result.snippet ?: return

    // This is the location of the search term inside the snippet.
    val highlightStart = snippet.rangeInSnippet.startPosition
    val highlightEnd = snippet.rangeInSnippet.endPosition

    val previewText = SpannableString(snippet.text)
    val resultHighlight = BackgroundColorSpan(Color.YELLOW)
    previewText.setSpan(resultHighlight, highlightStart, highlightEnd, 0)
}
Copy
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// This has to be inflated from your layout before using it.
private TextView previewText;

/**
 * Sets the search result snippet to a TextView highlighting the actual search term inside the result.
 */
private void showSearchResultText(@NonNull final SearchResult result) {
    // If you deactivated snippet extraction, it may be null.
    if(result.snippet == null) return;

    // This is the location of the search term inside the snippet.
    final int highlightStart = result.snippet.rangeInSnippet.getStartPosition();
    final int highlightEnd = result.snippet.rangeInSnippet.getEndPosition();

    final SpannableString previewText = new SpannableString(result.snippet.text);
    final BackgroundColorSpan resultHighlight = new BackgroundColorSpan(Color.YELLOW);
    previewText.setSpan(resultHighlight, highlightStart, highlightEnd, 0);
}

Note: The SearchResult#snippet may be null if you deactivated snippet extraction by setting the SearchOptions.Builder#snippetLenght to 0.

Highlighting search results

To display search results on the PdfFragment create a SearchResultHighlighter and register it with your fragment. Once you have a list of search results, you can start highlighting them by calling #setSearchResults on the highlighter.

Copy
MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lateinit var highlighter : SearchResultHighlighter

/** Holds your PdfFragment. You don't need to define this when using PdfActivity. */
val fragment: PdfFragment

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // The context is used to retrieve the highlighter color from the theme.
    highlighter = SearchResultHighlighter(context)
    fragment.addDrawableProvider(highlighter)
}

/**
 * Called by your application once a search was finished.
 * The highlighter will immediately start highlighting the given results.
 */
fun onSearchFinished(searchResults : List<SearchResult>) = highlighter.setSearchResults(searchResults)
Copy
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private SearchResultHighlighter highlighter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // This is possible in all PdfActivity subclasses.
    // If you aren't using PdfActivity, replace this with your own code.
    final PdfFragment fragment = getPdfFragment();

    // The context is used to retrieve the highlighter color from the theme.
    highlighter = new SearchResultHighlighter(context);
    fragment.addDrawableProvider(highlighter);

    // ...
}

/**
 * Called by your application once a search was finished.
 */
private void onSearchFinished(@NonNull final List<SearchResult> searchResults) {
    // The highlighter will immediately start highlighting the given results.
    highlighter.setSearchResults(searchResults);
}

Emphasizing the selected result

You can give emphasis to a single, selected search result, which will reveal itself with a pop-out animation and will receive an additional border. Note that you can only select a search result that was previously set on the highlighter using #setSearchResults(List<SearchResult>). Setting a search result that was not previously added will raise an exception.

Copy
MainActivity.kt
1
2
3
4
5
/**
 * Called by your application, e.g. if the user taps a search result inside a list.
 */
fun onSearchResultSelected(selectedResult : SearchResult) =
    highlighter.setSelectedSearchResult(selectedResult)
Copy
MainActivity.java
1
2
3
4
5
6
/**
 * Called by your application, e.g. if the user taps a search result inside a list.
 */
private void onSearchResultSelected(@NonNull final SearchResult selectedResult) {
    highlighter.setSelectedSearchResult(selectedResult);
}

Note: SearchResultHighlighter#setSelectedSearchResult also allows null as argument, which will then clear any previously selected result.

Highlighter customization

The SearchResultHighlighter uses pre-defined colors and styles. If you want to change the appearance, you can apply a custom style to the pspdf__searchResultHighlighterStyle attribute of your activity theme.

Copy
app/res/values/styles.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- This is the theme you set to your activity inside AndroidManifest.xml -->
<style name="MyApp_Theme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="pspdf__searchResultHighlighterStyle">@style/MyApp_Theme_SearchResultHighlighter</item>
</style>

<!-- This is a custom style for search result highlights. -->
<style name="MyApp_Theme_SearchResultHighlighter" parent="PSPDFKit.SearchResultHighlighter">
    <item name="pspdf__searchResultBackgroundColor">#FF009999</item>
    <item name="pspdf__searchResultBorderColor">#FF888888</item>
    <item name="pspdf__searchResultBorderWidth">2px</item>
    <item name="pspdf__searchResultPadding">2dp</item>
    <item name="pspdf__searchResultAnnotationPadding">3dp</item>
    <item name="pspdf__searchResultAnimationPadding">8dp</item>
    <item name="pspdf__searchResultCornerRadiusToHeightRatio">0.1</item>
    <item name="pspdf__searchResultCornerRadiusMin">2dp</item>
    <item name="pspdf__searchResultCornerRadiusMax">10dp</item>
</style>

💡 Tip: Have a look at the DarkThemeExample which explains how to apply custom themes including a custom search result highlighter style.

Here's a complete list of the style attributes you can define for the pspdf__searchResultHighlighterStyle:

Attribute Description
pspdf__searchResultBackgroundColor Background color of the highlighted search result.
pspdf__searchResultBorderColor Border color of the highlighted search result.
pspdf__searchResultBorderWidth Border width of the highlighted search result.
pspdf__searchResultPadding Padding of the of the highlighted search result used when highlighting document text.
pspdf__searchResultAnnotationPadding Padding of the highlighted search result used when highlighting annotations.
pspdf__searchResultAnimationPadding Padding of the of the highlighted search result used when pop-out animating the currently selected search result.
pspdf__searchResultCornerRadiusToHeightRatio Ratio between corner radius of the search result rectangle and its height.
pspdf__searchResultCornerRadiusMin Minimal corner radius of the highlighted search result.
pspdf__searchResultCornerRadiusMax Maximal corner radius of the highlighted search result.