searchBlocks method

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

Searches block by block so every hit knows its exact block and offsets.

Implementation

Future<List<UEpubSearchHit>> searchBlocks(String query, {UDocSearchOptions options = const UDocSearchOptions()}) async {
  final UEpubBook? book = _book;
  final String trimmed = query.trim();
  if (book == null || trimmed.isEmpty) {
    clearSearch();
    return const <UEpubSearchHit>[];
  }
  final int token = ++_searchToken;
  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<UEpubSearchHit> hits = <UEpubSearchHit>[];
  for (int index = 0; index < book.chapterCount; index++) {
    if (token != _searchToken || isDisposed) return hits;
    final UEpubChapter chapter = await this.chapter(index);
    for (int blockIndex = 0; blockIndex < chapter.blocks.length; blockIndex++) {
      final UEpubBlock block = chapter.blocks[blockIndex];
      if (!block.isText) continue;
      final String text = block.text;
      final UDocNormalizedText normalized = options.normalizePersian
          ? UDocNormalizedText.build(text)
          : UDocNormalizedText(options.caseSensitive ? text : text.toLowerCase(), List<int>.generate(text.length, (int i) => i));
      for (final int position in UDocText.findAll(normalized.text, needle, wholeWord: options.wholeWord)) {
        final int start = normalized.sourceAt(position);
        final int end = normalized.sourceAt(position + needle.length - 1) + 1;
        final int snippetStart = max(0, start - 48);
        final int snippetEnd = min(text.length, end + 48);
        hits.add(UEpubSearchHit(pageIndex: index, blockIndex: blockIndex, start: start, end: end, snippet: text.substring(snippetStart, snippetEnd).replaceAll("\n", " "), matchIndex: hits.length));
        if (hits.length >= options.maxHits) break;
      }
      if (hits.length >= options.maxHits) break;
    }
    liveHits.value = List<UDocSearchHit>.from(hits);
    if (hits.length >= options.maxHits) break;
  }
  if (token != _searchToken || isDisposed) return hits;
  emit(value.copyWith(searchHits: hits, isSearching: false, searchHitIndex: hits.isEmpty ? -1 : 0));
  return hits;
}