/*jshint esnext:false,es3:false,esversion:6*/
/**
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* <p>This code was adapted from a Share Extras (http://share-extras.github.io/)
* project and was then integrated into Alfresco Share for version 5.0. That code was in turn
* originally copied from the Mozilla pdf.js project (https://github.com/mozilla/pdf.js).</p>
*
* <p>Provides a "search" or "find" functionality for the PDF.This object actually performs
* the search for a given string.</p>
*
* @module alfresco/preview/PdfJs/PDFFindController
* @author Dave Draper
* @author Will Abson
* @auther Peter Löfgren
* @author Kevin Roast
*/
define(["dojo/_base/declare",
"alfresco/core/topics",
"alfresco/core/Core"],
function(declare, topics, AlfCore) {
return declare([AlfCore], {
FindStates: {
FIND_FOUND: 0,
FIND_NOTFOUND: 1,
FIND_WRAPPED: 2,
FIND_PENDING: 3
},
/**
*
*
* @instance
* @type {boolean}
* @default
*/
startedTextExtraction: false,
/**
*
*
* @instance
* @type {array}
* @default []
*/
extractTextPromises: [],
/**
*
*
* @instance
* @type {object}
* @default {}
*/
pendingFindMatches: {},
/**
* If active, find results will be highlighted.
*
* @instance
* @type {boolean}
* @default
*/
active: false,
/**
* Stores the text for each page.
*
* @instance
* @type {array}
* @default []
*/
pageContents: [],
/**
*
*
* @instance
* @type {array}
* @default []
*/
pageMatches: [],
// Currently selected match.
selected: {
pageIdx: -1,
matchIdx: -1
},
// Where find algorithm currently is in the document.
offset: {
pageIdx: null,
matchIdx: null
},
/**
*
*
* @instance
* @default
*/
resumePageIdx: null,
/**
*
*
* @instance
* @default
*/
state: null,
/**
*
*
* @instance
* @type {boolean}
* @default
*/
dirtyMatch: false,
/**
*
*
* @instance
* @type {number}
* @default
*/
findTimeout: null,
/**
*
*
* @instance
* @type {object}
* @default
*/
pdfPageSource: null,
/**
*
*
* @instance
* @type {boolean}
* @default
*/
integratedFind: false,
/**
*
*
* @instance
*/
constructor: function alfresco_preview_PdfJs_PDFFindController__constructor(options) {
this.pdfPageSource = options.pdfPageSource;
this.integratedFind = options.integratedFind;
var events = [
"find",
"findagain",
"findhighlightallchange",
"findcasesensitivitychange"
];
this.firstPagePromise = new Promise(function (resolve) {
this.resolveFirstPage = resolve;
}.bind(this));
this.handleEvent = this.handleEvent.bind(this);
for (var i = 0; i < events.length; i++) {
window.addEventListener(events[i], this.handleEvent);
}
},
/**
*
*
* @instance
*/
reset: function alfresco_preview_PdfJs_PDFFindController__reset() {
this.startedTextExtraction = false;
this.extractTextPromises = [];
this.active = false;
},
/**
*
*
* @instance
*/
calcFindMatch: function alfresco_preview_PdfJs_PDFFindController__calcFindMatch(pageIndex) {
var pageContent = this.pageContents[pageIndex];
var query = this.state.query;
var caseSensitive = this.state.caseSensitive;
var queryLen = query.length;
if (queryLen === 0) {
// Do nothing the matches should be wiped out already.
return;
}
if (!caseSensitive) {
pageContent = pageContent.toLowerCase();
query = query.toLowerCase();
}
var matches = [];
var matchIdx = -queryLen;
while (true) {
matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
if (matchIdx === -1) {
break;
}
matches.push(matchIdx);
}
this.pageMatches[pageIndex] = matches;
this.updatePage(pageIndex);
if (this.resumePageIdx === pageIndex) {
this.resumePageIdx = null;
this.nextPageMatch();
}
},
/**
*
*
* @instance
*/
extractText: function alfresco_preview_PdfJs_PDFFindController__extractText() {
/*jshint loopfunc:true*/
if (this.startedTextExtraction) {
return;
}
this.startedTextExtraction = true;
this.pageContents = [];
var extractTextPromisesResolves = [];
for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
this.extractTextPromises.push(new Promise(function (resolve) {
extractTextPromisesResolves.push(resolve);
}));
}
var self = this;
function extractPageText(pageIndex) {
self.pdfPageSource.pages[pageIndex].getTextContent().then(
function textContentResolved(textContent) {
var textItems = textContent.items;
var str = "";
for (var j = 0; j < textItems.length; j++) {
str += textItems[j].str;
}
// Store the pageContent as a string.
self.pageContents.push(str);
extractTextPromisesResolves[pageIndex](pageIndex);
if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
extractPageText(pageIndex + 1);
}
}
);
}
extractPageText(0);
},
/**
*
*
* @instance
*/
handleEvent: function alfresco_preview_PdfJs_PDFFindController__handleEvent(e) {
if (this.state === null || e.type !== "findagain") {
this.dirtyMatch = true;
}
this.state = e.detail;
this.updateUIState(this.FindStates.FIND_PENDING);
this.firstPagePromise.then(function() {
this.extractText();
clearTimeout(this.findTimeout);
if (e.type === "find")
{
// Only trigger the find action after 250ms of silence.
this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
}
else
{
this.nextMatch();
}
}.bind(this));
},
/**
*
*
* @instance
*/
updatePage: function alfresco_preview_PdfJs_PDFFindController__updatePage(idx) {
var page = this.pdfPageSource.pages[idx];
if (this.selected.pageIdx === idx) {
// If the page is selected, scroll the page into view, which triggers
// rendering the page, which adds the textLayer. Once the textLayer is
// build, it will scroll onto the selected match.
page.scrollIntoView();
}
if (page.textLayer) {
page.textLayer.updateMatches();
}
},
/**
*
*
* @instance
*/
nextMatch: function alfresco_preview_PdfJs_PDFFindController__nextMatch() {
/*jshint loopfunc:true,maxstatements:false*/
var previous = this.state.findPrevious;
// ALFRESCO - changed .page to pageNum
var currentPageIndex = this.pdfPageSource.pageNum - 1;
var numPages = this.pdfPageSource.pages.length;
this.active = true;
if (this.dirtyMatch)
{
// Need to recalculate the matches, reset everything.
this.dirtyMatch = false;
this.selected.pageIdx = this.selected.matchIdx = -1;
this.offset.pageIdx = currentPageIndex;
this.offset.matchIdx = null;
this.hadMatch = false;
this.resumePageIdx = null;
this.pageMatches = [];
var self = this;
for (var i = 0; i < numPages; i++) {
// Wipe out any previous highlighted matches.
this.updatePage(i);
// As soon as the text is extracted start finding the matches.
if (!(i in this.pendingFindMatches)) {
this.pendingFindMatches[i] = true;
this.extractTextPromises[i].then(function(pageIdx) {
delete self.pendingFindMatches[pageIdx];
self.calcFindMatch(pageIdx);
});
}
}
}
// If there's no query there's no point in searching.
if (this.state.query === "") {
this.updateUIState(this.FindStates.FIND_FOUND);
return;
}
// If we're waiting on a page, we return since we can't do anything else.
if (this.resumePageIdx) {
return;
}
var offset = this.offset;
// If there's already a matchIdx that means we are iterating through a
// page's matches.
if (offset.matchIdx !== null)
{
var numPageMatches = this.pageMatches[offset.pageIdx].length;
if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
(previous && offset.matchIdx > 0))
{
// The simple case, we just have advance the matchIdx to select the next
// match on the page.
this.hadMatch = true;
offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
this.updateMatch(true);
return;
}
// We went beyond the current page's matches, so we advance to the next
// page.
this.advanceOffsetPage(previous);
}
// Start searching through the page.
this.nextPageMatch();
},
/**
*
*
* @instance
*/
matchesReady: function alfresco_preview_PdfJs_PDFFindController__matchesReady(matches) {
var offset = this.offset;
var numMatches = matches.length;
var previous = this.state.findPrevious;
if (numMatches)
{
// There were matches for the page, so initialize the matchIdx.
this.hadMatch = true;
offset.matchIdx = previous ? numMatches - 1 : 0;
this.updateMatch(true);
// matches were found
return true;
}
else
{
// No matches attempt to search the next page.
this.advanceOffsetPage(previous);
if (offset.wrapped)
{
offset.matchIdx = null;
if (!this.hadMatch) {
// No point in wrapping there were no matches.
this.updateMatch(false);
// while matches were not found, searching for a page
// with matches should nevertheless halt.
return true;
}
}
// matches were not found (and searching is not done)
return false;
}
},
/**
*
*
* @instance
*/
nextPageMatch: function alfresco_preview_PdfJs_PDFFindController__nextPageMatch() {
if (this.resumePageIdx !== null)
{
this.alfLog("error","There can only be one pending page.");
}
var matches;
do {
var pageIdx = this.offset.pageIdx;
matches = this.pageMatches[pageIdx];
if (!matches) {
// The matches don't exist yet for processing by "matchesReady",
// so set a resume point for when they do exist.
this.resumePageIdx = pageIdx;
break;
}
} while (!this.matchesReady(matches));
},
/**
*
*
* @instance
*/
advanceOffsetPage: function alfresco_preview_PdfJs_PDFFindController__advanceOffsetPage(previous) {
var offset = this.offset;
var numPages = this.extractTextPromises.length;
offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
offset.matchIdx = null;
if (offset.pageIdx >= numPages || offset.pageIdx < 0)
{
offset.pageIdx = previous ? numPages - 1 : 0;
offset.wrapped = true;
return;
}
},
/**
*
*
* @instance
*/
updateMatch: function alfresco_preview_PdfJs_PDFFindController__updateMatch(found) {
var state = this.FindStates.FIND_NOTFOUND;
var wrapped = this.offset.wrapped;
this.offset.wrapped = false;
if (found)
{
var previousPage = this.selected.pageIdx;
this.selected.pageIdx = this.offset.pageIdx;
this.selected.matchIdx = this.offset.matchIdx;
state = wrapped ? this.FindStates.FIND_WRAPPED : this.FindStates.FIND_FOUND;
// Update the currently selected page to wipe out any selected matches.
if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
this.updatePage(previousPage);
}
}
this.updateUIState(state, this.state.findPrevious);
if (this.selected.pageIdx !== -1) {
this.updatePage(this.selected.pageIdx, true);
}
},
/**
*
*
* @instance
* @fires module:alfresco/core/topics#DISPLAY_NOTIFICATION
*/
updateUIState: function alfresco_preview_PdfJs_PDFFindController__updateUIState(state, previous) {
var findMsg = "";
// var status = "";
// ALFRESCO - updateUIState method impl
// TODO: For now do not display for hits, gets very noisy when stepping.
// Possibly change color or similar in search box to indicate hit instead
// Pending ajax gif on search bar until state is found, then removed.
// See pdf.js default Implementation
if(state===this.FindStates.FIND_FOUND||state===this.FindStates.FIND_PENDING)
{
return;
}
switch (state) {
case this.FindStates.FIND_FOUND:
findMsg = this.pdfPageSource.pdfJsPlugin.previewManager.message("search.message.found");
break;
case this.FindStates.FIND_PENDING:
findMsg = this.pdfPageSource.pdfJsPlugin.previewManager.message("search.message.pending");
break;
case this.FindStates.FIND_NOTFOUND:
findMsg = this.pdfPageSource.pdfJsPlugin.previewManager.message("search.message.notfound");
break;
case this.FindStates.FIND_WRAPPED:
if (previous) {
findMsg = this.pdfPageSource.pdfJsPlugin.previewManager.message("search.message.wrapped.bottom");
} else {
findMsg = this.pdfPageSource.pdfJsPlugin.previewManager.message("search.message.wrapped.top");
}
break;
}
// This will require that the alfresco/service/NotificationService is on the page
this.alfServicePublish(topics.DISPLAY_NOTIFICATION, {
message: findMsg
}, true);
}
});
});