search method

Future<List<UDocSearchHit>> search(
  1. String query, {
  2. UDocSearchOptions options = const UDocSearchOptions(),
})

Implementation

Future<List<UDocSearchHit>> search(String query, {UDocSearchOptions options = const UDocSearchOptions()}) async {
  final UPdfDocument? document = _document;
  final String trimmed = query.trim();
  if (document == null || trimmed.isEmpty) {
    clearSearch();
    return const <UDocSearchHit>[];
  }
  final int token = ++_searchToken;
  _searching = true;
  liveHits.value = const <UDocSearchHit>[];
  emit(value.copyWith(searchQuery: trimmed, isSearching: true, searchHits: const <UDocSearchHit>[], searchHitIndex: -1));
  final String needle = options.normalizePersian ? UDocText.forSearch(trimmed) : (options.caseSensitive ? trimmed : trimmed.toLowerCase());
  final List<UDocSearchHit> hits = <UDocSearchHit>[];
  final int start = options.startPage.clamp(0, document.pageCount);
  final int end = options.endPage < 0 ? document.pageCount : options.endPage.clamp(0, document.pageCount);
  RegExp? pattern;
  if (options.regex) {
    try {
      pattern = RegExp(trimmed, caseSensitive: options.caseSensitive, multiLine: true);
    } on FormatException {
      pattern = null;
    }
  }
  for (int page = start; page < end; page++) {
    if (token != _searchToken || isDisposed) return hits;
    final UDocTextPage text = await textPage(page);
    if (text.text.isEmpty) continue;
    final UDocNormalizedText normalized = options.normalizePersian
        ? UDocNormalizedText.build(text.text)
        : UDocNormalizedText(options.caseSensitive ? text.text : text.text.toLowerCase(), List<int>.generate(text.text.length, (int i) => i));
    final List<List<int>> matches = <List<int>>[];
    if (pattern != null) {
      for (final RegExpMatch match in pattern.allMatches(text.text)) {
        matches.add(<int>[match.start, match.end]);
      }
    } else {
      for (final int index in UDocText.findAll(normalized.text, needle, wholeWord: options.wholeWord)) {
        matches.add(<int>[normalized.sourceAt(index), normalized.sourceAt(index + needle.length - 1) + 1]);
      }
    }
    for (final List<int> match in matches) {
      final int rawStart = match[0];
      final int rawEnd = match[1] > rawStart ? match[1] : rawStart + 1;
      final int snippetStart = rawStart - 40 < 0 ? 0 : rawStart - 40;
      final int snippetEnd = rawEnd + 40 > text.text.length ? text.text.length : rawEnd + 40;
      hits.add(
        UDocSearchHit(
          pageIndex: page,
          start: rawStart,
          end: rawEnd,
          snippet: text.text.substring(snippetStart, snippetEnd).replaceAll("\n", " "),
          rects: text.rectsForRange(rawStart, rawEnd),
          matchIndex: hits.length,
        ),
      );
      if (hits.length >= options.maxHits) break;
    }
    liveHits.value = List<UDocSearchHit>.from(hits);
    if (hits.length >= options.maxHits) break;
  }
  if (token != _searchToken || isDisposed) return hits;
  _searching = false;
  emit(value.copyWith(searchHits: hits, isSearching: false, searchHitIndex: hits.isEmpty ? -1 : 0, pageIndex: hits.isEmpty ? value.pageIndex : hits.first.pageIndex));
  return hits;
}