liutian 1 год назад
Родитель
Сommit
b37da420b3

+ 103 - 0
packages/core/src/TextSearch.ts

@@ -0,0 +1,103 @@
+type TextSearchResult = {
+  bottom: number
+  content: string
+  left: number
+  pageNum: number
+  right: number
+  searchValue: string
+  top: number
+}
+
+export default class TextSearch {
+  searchContainer: HTMLDivElement | null = null
+  activeIndex = 0
+  activeSearchResult: TextSearchResult | null = null
+  container: HTMLDivElement | null = null
+  viewport: any
+  scale: number = 0
+  pageIndex: number = 0
+  _searchResults: TextSearchResult[] = []
+
+  constructor({
+    container,
+    viewport,
+    scale,
+    pageIndex,
+    results,
+  }: {
+    container: HTMLDivElement,
+    viewport: any,
+    scale: number,
+    pageIndex: number,
+    results: TextSearchResult[]
+  }) {
+    this.container = container
+    this.viewport = viewport
+    this.scale = scale
+    this.pageIndex = pageIndex
+    this._searchResults = results
+    if (results && results.length) {
+      this.renderSearchResults()
+    }
+  }
+
+  get searchResults() {
+    return this._searchResults
+  }
+
+  set searchResults(results) {
+    this._searchResults = results
+    this.renderSearchResults()
+  }
+
+  setActiveSearchResult(search: TextSearchResult) {
+    const { pageNum, left, top, right, bottom} = search
+    const activeSearchResult = this.activeSearchResult
+    if (search && ((activeSearchResult && left !== activeSearchResult.left && top !== activeSearchResult.top && right !== activeSearchResult.right && bottom !== activeSearchResult.bottom) || !activeSearchResult)) {
+      if (!activeSearchResult) {
+
+      }
+      this.activeSearchResult = search
+      this.activeIndex = pageNum - 1
+    }
+  }
+
+  renderSearchResults() {
+    const scale = this.scale
+    if (!this.searchContainer) {
+      const searchContainer = document.createElement('div')
+      searchContainer.className = 'searchContainer'
+      this.container!.appendChild(searchContainer)
+      this.searchContainer = searchContainer
+    }
+    this.searchContainer.textContent = ''
+    for (let i = 0; i < this.searchResults.length; i++) {
+      if (this.searchResults[i].pageNum !== this.pageIndex + 1) {
+        continue
+      }
+      const result = this.searchResults[i]
+      const { left, top, right, bottom } = result
+      const bounds = {
+        left: left * scale,
+        top: top * scale,
+        width: right * scale - left * scale,
+        height: bottom * scale - top * scale
+      }
+      const div = document.createElement('div')
+      div.className = 'highlight'
+      div.style.position = 'absolute'
+      div.style.left = `${bounds.left}px`
+      div.style.top = `${bounds.top}px`
+      div.style.width = `${bounds.width}px`
+      div.style.height = `${bounds.height}px`
+      this.searchContainer!.appendChild(div)
+    }
+  }
+
+  destroy() {
+    if (this.searchContainer) {
+      this.searchContainer.parentNode!.removeChild(this.searchContainer)
+      this.searchContainer = null
+    }
+  }
+}

+ 1 - 1
packages/core/src/TextSelection.ts

@@ -12,7 +12,7 @@ type PagePtr = {
   pagePtr: number
   textPtr: number
 }
-export class TextSelection {
+export default class TextSelection {
   _selection: string | null
   #pagePtr: number | null
   #textPtr: number | null

+ 16 - 11
packages/core/src/index.js

@@ -27,6 +27,7 @@ const CMAP_URL = './cmaps/'
 class ComPDFKitViewer {
   #pwd = ''
   #oldPwd = ''
+  #textSearch = null
   constructor(options) {
     this.config = options
     this.viewerContainer = null
@@ -377,7 +378,6 @@ class ComPDFKitViewer {
           //   });
         })
         await this.load(pdfDocument);
-        this.search('PDF')
         return { pwd: !!this.#pwd };
       },
       reason => {
@@ -435,10 +435,21 @@ class ComPDFKitViewer {
     })
 
     this.searchResults = searchResults
-
+    this.eventBus.dispatch('search', searchResults)
     return searchResults
   }
 
+  setActiveSearchResult() {
+    
+  }
+
+  clearSearchResults() {
+    for (let i = 0; i < this.pdfViewer._pages.length; i++) {
+      const _page = this.pdfViewer._pages[i]
+      _page.clearSearchResults()
+    }
+  }
+
   async getAnnotations() {
     this.pagesPtr = []
     const annotations = []
@@ -1491,16 +1502,10 @@ class ComPDFKitViewer {
           // Unable to write to storage.
         });
     }
-    const href = this.pdfLinkService.getAnchorUrl(
-      location.pdfOpenParams
-    );
 
-    // Show/hide the loading indicator in the page number input element.
-    const currentPage = this.pdfViewer.getPageView(
-      /* index = */ this.page - 1
-    );
-    const loading = currentPage?.renderingState !== RenderingStates.FINISHED;
-    // this.toolbar.updateLoadingIndicatorState(loading);
+    if (this.#textSearch) {
+      this.#textSearch.updateMatch()
+    }
   }
 
   webViewerScrollModeChanged(evt) {

+ 39 - 1
packages/core/src/pdf_page_view.js

@@ -23,7 +23,8 @@ import { PDFAnnotationLayer } from './pdf_annotation_layer.js'
 import { TextLayerBuilder } from "./text_layer_builder.js";
 import ComPDFAnnotationLayer from './annotation/layer.js';
 import { ContentContainer } from "./editor/content_container.js";
-import { TextSelection } from "./TextSelection";
+import TextSelection from "./TextSelection";
+import TextSearch from './TextSearch'
 
 import AnnotationManager from "./annotations";
 import { v4 as uuidv4 } from 'uuid';
@@ -153,6 +154,8 @@ class PDFPageView {
     this.structTreeLayer = null;
     this.contentContainer = null;
     this.textSelection = null
+    this.textSearch = null
+    this.searchResults = null
 
     const div = document.createElement("div");
     div.className = "page page-number" + this.id;
@@ -171,6 +174,7 @@ class PDFPageView {
 
     this.eventBus._on('toolChanged', this.handleTool.bind(this))
     this.eventBus._on('toolModeChanged', this.handleToolMode.bind(this))
+    this.eventBus._on('search', this.handleSearch.bind(this))
 
     this.mode = null
 
@@ -294,6 +298,13 @@ class PDFPageView {
     if (this.compdfAnnotationLayer) return
   }
 
+  handleSearch(searchResults) {
+    this.searchResults = searchResults
+    if (this.textSearch) {
+      this.textSearch.searchResults = searchResults
+    }
+  }
+
   get annotations() {
     return this._annotations
   }
@@ -402,6 +413,12 @@ class PDFPageView {
     );
   }
 
+  clearSearchResults() {
+    if (this.textSearch) {
+      this.textSearch.destroy()
+    }
+  }
+
   /**
    * @private
    */
@@ -809,6 +826,11 @@ class PDFPageView {
       this.textSelection.destroy()
       this.textSelection = null
     }
+
+    if (this.textSearch) {
+      this.textSearch.destroy()
+      this.textSearch = null
+    }
   }
 
   cssTransform({
@@ -1077,6 +1099,16 @@ class PDFPageView {
             pageViewer: this
           })
         }
+
+        if (!this.textSearch) {
+          this.textSearch = new TextSearch({
+            viewport: this.viewport,
+            scale: this.scale,
+            pageIndex: this.pageIndex,
+            container: div,
+            results: this.searchResults,
+          })
+        }
       },
       function (reason) {
         return finishPaintTask(reason);
@@ -1106,6 +1138,12 @@ class PDFPageView {
     return resultPromise;
   }
 
+  setActiveSearchResult(search) {
+    if (this.textSearch) {
+      this.textSearch.setActiveResult(search)
+    }
+  }
+
   async paintOnCanvas(canvasWrapper) {
     const renderCapability = createPromiseCapability();
     const result = {

+ 8 - 6
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -551,18 +551,20 @@ onMounted(async () => {
   transform-origin: 0% 0%;
 }
 
-.textLayer .highlight {
+.searchContainer {
+  position: relative;
+  z-index: 3;
+  pointer-events: none;
+}
+.searchContainer .highlight {
+  position: absolute;
   background-color: rgba(255, 255, 0, 0.25);
 }
 
-.textLayer .highlight.selected {
+.searchContainer .highlight.selected {
   background-color: rgba(255, 255, 0, 0.7);
 }
 
-.textLayer .highlight.appended {
-  position: initial;
-}
-
 .findbar {
   padding: 8px;
   background-color: var(--c-findbar-bg);

+ 5 - 5
packages/webview/src/components/SearchContainer/SearchContent.vue

@@ -1,7 +1,7 @@
 <template>
   <div v-show="searchResults.length" class="search-number"><div id="findResultsCount" class="toolbarLabel"></div><div>{{ $t('leftPanel.page') }}</div></div>
   <div class="search-container">
-    <template v-if="searchResults.length" v-for="(result, currentIndex) in searchResults">
+    <template v-if="searchResults.length" v-for="(result, currentIndex) in searchResults" :key="result">
       <SearchItem :searchResults="searchResults" :result="result" :currentIndex="currentIndex" />
     </template>
     <div v-else class="no-annotations">{{ $t('leftPanel.noResults') }}</div>
@@ -9,12 +9,12 @@
 </template>
 
 <script setup>
-  import { computed } from 'vue';
-  import { useDocumentStore } from '@/stores/modules/document'
+import { computed } from 'vue';
+import { useDocumentStore } from '@/stores/modules/document'
 
-  const useDocument = useDocumentStore()
+const useDocument = useDocumentStore()
 
-  const searchResults = computed(() => useDocument.getSearchResults)
+const searchResults = computed(() => useDocument.getSearchResults)
 </script>
 
 <style lang="scss">

+ 2 - 2
packages/webview/src/components/SearchContainer/SearchHeader.vue

@@ -40,13 +40,13 @@
     isSearching.value = false
     searchValue.value = ''
     useDocument.setSearchResults([])
+    core.clearSearchResults()
   }
   const debouncedSearch = debounce(async (searchValue) => {
     if (searchValue && searchValue.length > 0) {
       isSearching.value = true
       const searchResults = await core.search(searchValue)
-      console.log(...searchResults)
-      useDocument.setSearchResults([...searchResults])
+      useDocument.setSearchResults(searchResults)
     } else {
       clearSearchResults()
     }

+ 2 - 5
packages/webview/src/components/SearchContainer/SearchItem.vue

@@ -4,10 +4,8 @@
 </template>
 
 <script setup>
-import { getCurrentInstance } from 'vue';
 const {searchResults, result, currentIndex} = defineProps(['searchResults', 'result', 'currentIndex'])
-const instance = getCurrentInstance();
-instance?.proxy?.$forceUpdate();
+
 const previousIndex = currentIndex === 0 ? currentIndex : currentIndex - 1
 const currentItem = searchResults[currentIndex]
 const previousItem = searchResults[previousIndex]
@@ -15,10 +13,9 @@ const isFirstItem = previousItem === currentItem
 const isDifferentPage = previousItem.pageNum !== currentItem.pageNum
 const regex = new RegExp(result.searchValue, "gi")
 const content = result.content.replace(regex,  function (match, capture) {
-  console.log(match, capture)
   return `<span class="highlight">${match}</span>`
 })
-console.log(searchResults)
+
 const goToPage = (page) => {
   core.pageNumberChanged({
     value: (page * 1 + 1).toString()

+ 3 - 0
packages/webview/src/core/clearSearchResults.js

@@ -0,0 +1,3 @@
+import core from '@/core'
+
+export default (docNum) => core.getDocumentViewer(docNum = 1).clearSearchResults()

+ 3 - 1
packages/webview/src/core/index.js

@@ -46,6 +46,7 @@ import triggerPrinting from './triggerPrinting'
 import setTextProperty from './setTextProperty'
 import getOutlines from './getOutlines'
 import search from './search'
+import clearSearchResults from './clearSearchResults'
 
 export default {
   getDocumentViewer,
@@ -98,5 +99,6 @@ export default {
   triggerPrinting,
   setTextProperty,
   getOutlines,
-  search
+  search,
+  clearSearchResults
 }