1. 程式人生 > >pdf.js實現多個不同詞的高亮顯示

pdf.js實現多個不同詞的高亮顯示

目標:實現多個詞的高亮顯示

原理:通過對pdf.js本身提供的搜尋功能(即ctrl+f)進行修改來達到多個詞的高亮顯示

注意:經過實踐證明,多個詞的高亮我所採用的這種方法只能在文件載入的進行一次性高亮,由於我的方法是在搜尋功能上進行修改的,故有很多限制,理論上可以自己寫一個擴充套件類來實現這個功能,但難度比較大,我是個小白寫不出來。

web目錄下是很多擴充套件功能的js檔案,我們主要是修改裡面的js檔案:

       view.js是入口檔案,即整個pdf文件載入的入口js檔案

       viewer.html 即展示頁面

        app_option.js 即整個的引數配置檔案

        app.js 即整個程式所有函式的執行處,類似於java中的main函式處

其他的js檔案主要就是pdf.js所提供的一些擴充套件功能,如搜尋功能,下載功能等等,而我需要修改的就是這些檔案的其中一小部分

這裡我主要關注三個檔案:app.js 、pdf_find_controller.js 

實現過程概述:pdf.js將文字層轉為html後,這樣所有的文字就被div包裹起來了,這些div在也頁面中都是有序的。它首先將這些文字從div中提取出來進行整合成一個字串,通過字串匹配得到關鍵詞的位置,然後將這些位置放在一個數組中,然後通過text_layer_builder.js中的convertMatches函式,將該位置進行轉化成包含有div的字串中的位置:比如:這個關鍵詞的首位置在第幾個div偏移多少等,這樣就可以根據這個轉化的位置進行新增span標籤和類名,來對關鍵詞背景進行高亮。

原本pdf.js只是計算了單個詞位置,而我可以通過迴圈計算多個不同詞的位置,存到數組裡,然後轉化,就可以對多個不同的詞進行高亮

注意:我這是壓縮版的pdf.js實際執行特別慢,所以你需要在壓縮版的view.js(即官網提供的demo)找到對應的函式進行修改。另外最好把把pdf.js提供的搜尋功能遮蔽掉,通過display=none即可,因為多個詞的高亮和該搜尋功能,不能同時使用,除非你自己單獨寫一個擴充套件類

1。首先:在app.js中,建立一個函式如:

function wordHighLight(hightLightWords) { // 目前只能匹配一個,不能全部高亮
  let evt = {
    // source: PDFFindBar, // PDFFindBar的例項,不確定是幹嘛用的?
    type: '',  // 這裡預設應該是空的
    // 這裡能預設跳轉到query的位置,剛好能滿足要求
    query: hightLightWords, // 高亮的關鍵詞
    phraseSearch: false, // 支援整段文字匹配,如果時多個詞的匹配只能是false
    caseSensitive: false, // 預設為false,搜尋時忽略大小寫
    highlightAll: true, // 設為true即關鍵詞全部高亮
    // findPrevious: true,
  };
  PDFViewerApplication.findController.executeCommand('find' + evt.type, {//搜尋執行函式
    query: evt.query,
    phraseSearch: evt.phraseSearch,
    caseSensitive: evt.caseSensitive,
    highlightAll: evt.highlightAll,
    findPrevious: evt.findPrevious,
  });
}

2. wordHighLight函式的呼叫

找到:PDFFindBar類(即搜尋功能類)例項化處,在下面呼叫wordHighLight

     this.findBar = new PDFFindBar(findBarConfig, this.l10n);// 例項化PDFFindBar
      // 這時我要是時點選keydown就會觸發查詢事件
      // 同樣的,我可以在這裡直接觸發查詢函式
      let highLightWords = ['雲林街菜鳥物流園職工,非人大代表或政協委員,租住雄楚市雄楚區金港一號小區11棟504室', '李毅', '張昌', '聊城', '犯罪經歷', '犯罪嫌疑人張昌'];
       wordHighLight(highLightWords);

3.因為executeComand函式是在pdf_find_controller.js中執行,

在pdf_find_controller.js中,找到executeCommand(cmd, state)——》_nextMatch()——》calculateMatch(pageIndex)函式,由於query以前是單個詞搜尋(pdf.js提供的功能),這裡我需要對涉及到query的做一些迴圈處理如下(我只是做了一些迴圈處理,請根據具體程式碼進行修改):

  _calculateMatch(pageIndex) {  // 計算結果都返回到了物件的屬性裡,所以不需要通過
    // return 來獲取想要的值
    // _normalize應該是規範化的意思
    let pageContent = this._normalize(this.pageContents[pageIndex]);
    let query_words = this.state.query;
    for (let i = 0; i < query_words.length; i++) {
      query_words[i] = this._normalize(query_words[i]);
      let caseSensitive = this.state.caseSensitive;

      if (!caseSensitive) { // 判斷是否對大小寫敏感
        // 如果不區分大小寫,就把頁面內容全部轉為小寫
        // 這裡pagecont
        pageContent = pageContent.toLowerCase();
        query_words[i] = query_words[i].toLowerCase();
      }
    }
    // let query = this._normalize(this.state.query); // 這裡只傳只傳了一個詞,我需要傳多個詞
    let phraseSearch = this.state.phraseSearch;
    // let queryLen = query.length;
    // 查詢內容為空,返回
    if (query_words.length === 0) {
      // Do nothing: the matches should be wiped out already.
      return;
    }
    // 不區分大小寫的話
    // 以上內容規範了當前頁和查詢內容的規範化
    // 以下為真正的匹配的內容
    if (phraseSearch) {  // 若為true則可匹配整段文字
      this._calculatePhraseMatch(query_words, pageIndex, pageContent); // 片語匹配功能
    } else { // 若為false則只能匹配單個的詞,特徵:單個的詞一般 兩邊有空格
      this._calculateWordMatch(query_words, pageIndex, pageContent); // 單詞匹配功能
    }
    // 將計算的匹配結果,用來更新匹配結果
    this._updatePage(pageIndex); // 清除以前的匹配結果,渲染最新的匹配結果
    if (this.resumePageIdx === pageIndex) { // 如果恢復的頁面索引等於當前索引
      this.resumePageIdx = null; // 將恢復頁面的索引設為空
      this._nextPageMatch(); //
    }

    // Update the match count.
    // 更新匹配個數
    // this.pageMatches[pageIndex]裡記錄了第pageIndex頁匹配的關鍵詞的位置
    // 通過下面這樣就可以獲得匹配的個數了
    // 問題:我在搜尋時,沒有全部高亮時,就可以顯示全部匹配的單詞個數
    // 那我為什麼不能全部高亮呢
    if (this.pageMatches[pageIndex].length > 0) {
      this.matchCount += this.pageMatches[pageIndex].length;
      this._updateUIResultsCount();
    }
  }

然後切換到——》_calculateWordMatch()函式中做一些迴圈處理:

_calculateWordMatch(query, pageIndex, pageContent) { // 單詞匹配
    let matchesWithLength = [];
    // Divide the query into pieces and search for text in each piece.
    // 把搜尋的詞分開,然後搜尋分開的詞,最小單位為字母
    // query.match 清除了搜尋值的空格,返回了一個有著被搜尋詞的陣列
    // 例:hello world -->"hello","world"
    let test_query = query;

    for (let x = 0; x < test_query.length; x++) {
      // 只有在這裡才可以給段落設定一個sign,後面我才能進行匹配,滾動到該段落位置
      let queryArray = test_query[x].match(/\S+/g); // '/S':任何一個非空白字元
      for (let i = 0, len = queryArray.length; i < len; i++) {
        // 若果是英文單詞,就是單個字母的迴圈
        let subquery = queryArray[i];
        let subqueryLen = subquery.length;
        let matchIdx = -subqueryLen; // 這裡設為負數是什麼意思
        while (true) {
          // matchInd+subqueryLen  == 0:為開始查詢的位置
          // matchIdx 為返回的匹配的的位置
          matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
          if (matchIdx === -1) { // 說明沒有匹配到
            break;
          }
          // Other searches do not, so we store the length.
          matchesWithLength.push({ // 將搜尋的索引存到 matchesWithLength裡
            match: matchIdx,
            matchLength: subqueryLen,
            skipped: false,
          });
        }
      }
    }

    // Prepare arrays for storing the matches.
    if (!this.pageMatchesLength) {
      this.pageMatchesLength = []; // 清空這個要儲存的陣列
    }
    this.pageMatchesLength[pageIndex] = []; // 某一頁的匹配結果,存到每一頁上
    this.pageMatches[pageIndex] = [];

    // Sort `matchesWithLength`, remove intersecting terms and put the result
    // into the two arrays.
    // this.pageMatches[pageIndex] 存索引
    // this.pageMatchesLength[pageIndex] 存對應的長度
    // 還是和phrase一樣,pageMatches用來存索引
    this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex],
      this.pageMatchesLength[pageIndex]);
  }

效果截圖展示: