1
0
Fork 0
mirror of https://github.com/Yetangitu/owncloud-apps.git synced 2025-10-02 14:49:17 +02:00

files_reader: More PDF madness, next/previous search hit navigation w/highlighting, etc...

This commit is contained in:
frankdelange 2017-04-07 22:21:15 +02:00
parent 640256c49b
commit f909cd0f1b
11 changed files with 448 additions and 458 deletions

Binary file not shown.

View file

@ -1,4 +1,3 @@
- search
- bookmarks
- annotations
- settings
@ -6,5 +5,7 @@
- rtl and ltr
- test canvas size restriction
- test internal links
- add IDs to highlights so they can be marked when hovered - or when related list item is hovered
- add preload/postload of X pages around current position (optional), store resulting rendered pages in off-screen canvas?

View file

@ -258,6 +258,8 @@
<a id="slider" class="icon-menu">
<?php p($l->t("menu")); ?>
</a>
<div id="status_message_left">
</div>
</div>
<div id="metainfo" class="nightshift">
<span id="book-title">
@ -273,23 +275,11 @@
</span>
</div>
<div id="title-controls">
<!-- select works fine, except for the fact that - as usual - apple mobile does not support icons...
<label for="zoomlevel">zoom: </label>
<select id="zoomlevel">
<option value="spread" data-icon="&#xe86d;" data-text="2-page">&#xe86d;</option>
<option value="fit_page" data-icon="&#xe86e;" data-text="fit page">&#xe86e;</option>
<option value="fit_width" data-icon="&#xe85c;" data-text="fit width">&#xe85c;</option>
<option value="0.25" class="text">25%</option>
<option value="0.5" class="text">50%</option>
<option value="0.75" class="text">75%</option>
<option value="1" class="text">100%</option>
<option value="1.25" class="text">125%</option>
<option value="1.5" class="text">150%</option>
<option value="2" class="text">200%</option>
<option value="3" class="text">300%</option>
<option value="4" class="text">400%</option>
</select>
-->
<div id="status_message_right">
</div>
<div id="match_count">
</div>
<!-- select works fine, except for the fact that - as usual - apple mobile acts up... -->
<div id="zoom_options" class="hide">
<div class="zoom_option icon-double_page_mode" data-value="spread" data-class="icon-double_page_mode" data-text=""></div>
<div class="zoom_option icon-single_page_mode" data-value="fit_page" data-class="icon-single_page_mode" data-text=""></div>

View file

@ -24,11 +24,18 @@ PDFJS.reader.ControlsController = function(book) {
$rotate_left = $("#rotate_left"),
$rotate_right = $("#rotate_right"),
$page_num = $("#page_num"),
$total_pages = $("#total_pages");
$total_pages = $("#total_pages"),
$status_message_left = $("#status_message_left"),
$status_message_right = $("#status_message_right");
var STATUS_MESSAGE_LENGTH = 30,
STATUS_MESSAGE_TIMEOUT = 3000,
status_timeout_left,
status_timeout_right;
if (reader.isMobile() === true) {
$titlebar.addClass("background_visible");
};
}
var show = function () {
$titlebar.removeClass("hide");
@ -46,6 +53,31 @@ PDFJS.reader.ControlsController = function(book) {
reader.ControlsController.toggle();
});
var setStatus = function (message, right) {
$status_message = (right) ? $status_message_right : $status_message_left;
status_timeout = (right) ? status_timeout_right : status_timeout_left;
$status_message[0].textContent = reader.ellipsize(message, STATUS_MESSAGE_LENGTH);
//$status_message[0].textContent = message;
if (typeof status_timeout === "number") {
clearTimeout(status_timeout);
status_timeout = undefined;
}
status_timeout = setTimeout(function () {
$status_message[0].textContent = "";
}, STATUS_MESSAGE_TIMEOUT);
if (right) {
status_timeout_right = status_timeout;
} else {
status_timeout_left = status_timeout;
}
};
var fullscreen = false;
$slider.on("click", function () {
@ -105,21 +137,8 @@ PDFJS.reader.ControlsController = function(book) {
});
/* select works fine on most browsers, but - of course - apple mobile has 'special needs'
* in that it does not support any styling in the drop-down list, and as such can not display
* icons there. Due to the unfortunate fact that many still buy these apple-encumbered devices
/* select works fine on most browsers, but - of course - apple mobile has 'special needs' so
* a custom select is needed...
*
$zoomlevel.val(settings.zoomLevel);
$zoomlevel.on("change", function () {
reader.setZoom($(this).val());
var $option = $zoomlevel.find(":selected");
if ($option.data("icon") !== undefined)
$("#zoom_icon")[0].textContent = $option.data("icon");
});
*
*/
/* custom select, supports icons in drop-down list */
@ -245,7 +264,7 @@ PDFJS.reader.ControlsController = function(book) {
break;
}
e.stopPropagation;
e.stopPropagation();
};
@ -278,7 +297,6 @@ PDFJS.reader.ControlsController = function(book) {
} else {
page -= (page + 1) % 2;
}
}
if (page >= 0 && page <= total_pages) {
if (page === total_pages) {
@ -289,6 +307,9 @@ PDFJS.reader.ControlsController = function(book) {
text = reader.getPageLabel(page) + "-" + reader.getPageLabel(page + 1);
}
}
} else {
text = reader.getPageLabel(page);
}
$page_num[0].textContent = text;
};
@ -332,6 +353,7 @@ PDFJS.reader.ControlsController = function(book) {
"setZoomIcon": setZoomIcon,
"setRotateIcon": setRotateIcon,
"setCurrentPage": setCurrentPage,
"setPageCount": setPageCount
"setPageCount": setPageCount,
"setStatus": setStatus
};
};

View file

@ -1,4 +1,4 @@
PDFJS.reader.ReaderController = function(book) {
PDFJS.reader.ReaderController = function() {
var $main = $("#main"),
$viewer = $("#viewer"),
$divider = $("#divider"),
@ -13,7 +13,8 @@ PDFJS.reader.ReaderController = function(book) {
$bookmark = $("#bookmark"),
$note = $("#note"),
$rotate_left = $("#rotate_left"),
$rotate_right = $("#rotate_right");
$rotate_right = $("#rotate_right"),
$clear_search = $("#clear_search");
var reader = this,
book = this.book,
@ -76,6 +77,8 @@ PDFJS.reader.ReaderController = function(book) {
var page_no = false;
e.preventDefault();
switch (settings.keyboard[e.keyCode]) {
case 'previous':
$prev.click();
@ -121,6 +124,22 @@ PDFJS.reader.ReaderController = function(book) {
break;
case 'cycleZoom':
reader.cycleZoom();
break;
case 'previousMatch':
reader.SearchController.nextMatch(true);
break;
case 'nextMatch':
reader.SearchController.nextMatch(false);
break;
case 'clearSearch':
$clear_search.click();
break;
case 'search':
if (e.shiftKey) {
reader.SidebarController.changePanelTo("Search");
reader.SearchController.show();
}
break;
default:
console.log("unsupported keyCode: " + e.keyCode);
@ -129,7 +148,7 @@ PDFJS.reader.ReaderController = function(book) {
if (page_no) {
reader.queuePage(page_no);
}
}
};
document.addEventListener('keydown', keyCommands, false);

View file

@ -1,14 +1,19 @@
var FindStates = {
FIND_FOUND: 0,
FIND_NOTFOUND: 1,
FIND_WRAPPED: 2,
FIND_PENDING: 3
};
PDFJS.reader.SearchController = function () {
var FIND_SCROLL_OFFSET_TOP = -50;
var FIND_SCROLL_OFFSET_LEFT = -400;
var reader = this,
book = this.book;
var CHARACTERS_TO_NORMALIZE = {
var $searchBox = $("#searchBox"),
$clearBtn = $("#searchBox").next(),
$clear_search = $("#clear_search"),
$searchResults = $("#searchResults"),
$searchView = $("#searchView"),
$body = $("#viewer iframe").contents().find('body'),
$sidebar = $("#sidebar"),
$match_count = $("#match_count");
/* search logic, partly from Mozilla pdfViewer */
var CHARACTERS_TO_NORMALIZE = {
'\u2018': '\'', // Left single quotation mark
'\u2019': '\'', // Right single quotation mark
'\u201A': '\'', // Single low-9 quotation mark
@ -20,68 +25,37 @@ var CHARACTERS_TO_NORMALIZE = {
'\u00BC': '1/4', // Vulgar fraction one quarter
'\u00BD': '1/2', // Vulgar fraction one half
'\u00BE': '3/4', // Vulgar fraction three quarters
};
PDFJS.reader.SearchController = function () {
var reader = this,
book = this.book,
query = "";
var $searchBox = $("#searchBox"),
$clearBtn = $("#searchBox").next(),
$clear_search = $("#clear_search"),
$searchResults = $("#searchResults"),
$searchView = $("#searchView"),
$body = $("#viewer iframe").contents().find('body'),
$sidebar = $("#sidebar");
var onShow = function() {
$searchView.addClass("open");
$searchBox.focus();
};
var onHide = function() {
unhighlight();
$searchView.removeClass("open");
};
this.onUpdateResultsCount = null;
this.onUpdateState = null;
var startedTextExtraction = false,
extractTextPromises = [],
matchCount = 0,
pendingFindMatches = Object.create(null);
// Compile the regular expression for text normalization once.
var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''),
normalizationRegex = new RegExp('[' + replace + ']', 'g');
var reset = function () {
this.startedTextExtraction = false;
this.extractTextPromises = [];
this.pendingFindMatches = Object.create(null);
this.active = false; // If active, find results will be highlighted.
this.pageContents = []; // Stores the text for each page.
this.pageMatches = [];
this.pageMatchesLength = null;
this.matchCount = 0;
this.selected = { // Currently selected match.
pendingFindMatches = Object.create(null);
reader.search_active = false; // If active, find results will be highlighted.
reader.pageMatches.length = 0;
reader.pageMatchesLength = null;
reader.search_state = null;
matchCount = 0;
resetMatchCounter();
reader.selected = { // Currently selected match.
pageIdx: -1,
matchIdx: -1
matchIdx: -1,
at_start: false,
at_end: false
};
this.offset = { // Where the find algorithm currently is in the document.
pageIdx: null,
matchIdx: null
updatePage();
};
this.pagesToSearch = null;
this.resumePageIdx = null;
this.state = null;
this.dirtyMatch = false;
this.findTimeout = null;
};
reset();
var normalize = function (text) {
return text.replace(this.normalizationRegex, function (ch) {
return text.replace(normalizationRegex, function (ch) {
return CHARACTERS_TO_NORMALIZE[ch];
});
};
@ -151,7 +125,7 @@ PDFJS.reader.SearchController = function () {
}
matches.push(matchIdx);
}
this.pageMatches[pageIndex] = matches;
reader.pageMatches[pageIndex] = matches;
};
@ -179,21 +153,22 @@ PDFJS.reader.SearchController = function () {
}
}
// Prepare arrays for store the matches.
if (!this.pageMatchesLength) {
this.pageMatchesLength = [];
if (!reader.pageMatchesLength) {
reader.pageMatchesLength = [];
}
this.pageMatchesLength[pageIndex] = [];
this.pageMatches[pageIndex] = [];
reader.pageMatchesLength[pageIndex] = [];
reader.pageMatches[pageIndex] = [];
// Sort matchesWithLength, clean up intersecting terms
// and put the result into the two arrays.
_prepareMatches(matchesWithLength, this.pageMatches[pageIndex],
this.pageMatchesLength[pageIndex]);
_prepareMatches(matchesWithLength, reader.pageMatches[pageIndex],
reader.pageMatchesLength[pageIndex]);
};
var getSnippet = function (pageIndex, position) {
var ellipse = '…',
match_length = this.state.query.length,
match_length = reader.search_state.query.length,
span = '<span class="search_match">',
span_close = '</span>',
limit = 160 + span.length + span_close.length,
@ -201,10 +176,10 @@ PDFJS.reader.SearchController = function () {
trailer,
context;
leader = this.pageContents[pageIndex].substring(position - limit/2, position);
leader = reader.pageContents[pageIndex].substring(position - limit/2, position);
leader = leader.slice(leader.indexOf(" "));
trailer = this.pageContents[pageIndex].substring(position + match_length, position + limit/2 + match_length);
query = this.pageContents[pageIndex].substring(position, position + match_length);
trailer = reader.pageContents[pageIndex].substring(position + match_length, position + limit/2 + match_length);
query = reader.pageContents[pageIndex].substring(position, position + match_length);
context = ellipse + leader + span + query + span_close + trailer;
@ -215,7 +190,6 @@ PDFJS.reader.SearchController = function () {
var listitem = document.createElement("li"),
link = document.createElement("a"),
id = parseInt(pageIndex + 1) + ":" + position,
item = {
url: null,
dest: null,
@ -227,10 +201,9 @@ PDFJS.reader.SearchController = function () {
item.dest = [pageIndex,position];
//link.textContent = getSnippet(pageIndex, position);
link.innerHTML = getSnippet(pageIndex, position);
listitem.dataset.index = ++matchCount;
link.innerHTML = '<span class="match_label">' + matchCount + '</span>' + getSnippet(pageIndex, position);
listitem.classList.add("list_item");
listitem.id = "search-"+id;
listitem.dataset.position = position;
reader.bindLink(link, item);
link.classList.add("search_link");
listitem.appendChild(link);
@ -238,16 +211,40 @@ PDFJS.reader.SearchController = function () {
return listitem;
};
var createItemList = function (pageIdx) {
var currentIdx = reader.settings.currentPage - 1,
item,
i = 0;
// currentIdx can be up to 2 different from pageIdx due to oddPageFirst and spread rendering
if (Math.abs(pageIdx - currentIdx) <= 2)
updatePage(pageIdx);
var fragment = document.createDocumentFragment();
var listitem = document.createElement("li");
listitem.textContent="page " + parseInt(pageIdx + 1);
listitem.classList.add("search_page_header");
fragment.appendChild(listitem);
reader.pageMatches[pageIdx].forEach(function (match) {
item = createItem(pageIdx, match);
item.id = "match:" + pageIdx + ":" + i;
item.classList.add("match:" + pageIdx + ":" + i++);
fragment.appendChild(item);
updateMatchCounter();
});
return fragment;
};
var calcFindMatch = function (pageIndex) {
var pageContent = normalize(this.pageContents[pageIndex]);
var query = normalize(this.state.query);
var caseSensitive = this.state.caseSensitive;
var phraseSearch = this.state.phraseSearch;
var pageContent = normalize(reader.pageContents[pageIndex]);
var query = normalize(reader.search_state.query);
var caseSensitive = reader.search_state.caseSensitive;
var phraseSearch = reader.search_state.phraseSearch;
var queryLen = query.length;
if (queryLen === 0) {
// Do nothing: the matches should be wiped out already.
reset();
return;
}
@ -265,24 +262,25 @@ PDFJS.reader.SearchController = function () {
var extractText = function () {
if (this.startedTextExtraction) {
if (startedTextExtraction) {
return;
}
this.startedTextExtraction = true;
startedTextExtraction = true;
this.pageContents = [];
reader.pageContents = [];
var extractTextPromisesResolves = [];
var numPages = reader.settings.numPages;
for (var i = 0; i < numPages; i++) {
this.extractTextPromises.push(new Promise(function (resolve) {
extractTextPromises.push(new Promise(function (resolve) {
extractTextPromisesResolves.push(resolve);
}));
}
var self = this;
function extractPageText(pageIndex) {
reader.getPageTextContent(pageIndex).then(
function textContentResolved(textContent) {
reader.ControlsController.setStatus("extracting text page " + parseInt(pageIndex + 1),true);
var textItems = textContent.items;
var str = [];
@ -291,7 +289,7 @@ PDFJS.reader.SearchController = function () {
}
// Store the pageContent as a string.
self.pageContents.push(str.join(' ').replace(/\s\s+/g, ' '));
reader.pageContents.push(str.join(''));
extractTextPromisesResolves[pageIndex](pageIndex);
if ((pageIndex + 1) < reader.settings.numPages) {
@ -303,264 +301,211 @@ PDFJS.reader.SearchController = function () {
extractPageText(0);
};
var executeCommand = function (cmd, state) {
if (this.state === null || cmd !== 'findagain') {
this.dirtyMatch = true;
var updatePage = function (pageIdx) {
var pageNum = (pageIdx) ? pageIdx + 1 : null;
if (reader.resourcelst) {
reader.resourcelst.forEach(function(list) {
if (list.textLayer && (pageNum === list.pageNum || pageNum === null)) {
list.textLayer.updateMatches();
}
this.state = state;
updateUIState(FindStates.FIND_PENDING);
console.log("execute command ", cmd, " with state ", state);
reader.firstPagePromise.then(function() {
extractText();
clearTimeout(this.findTimeout);
if (cmd === 'find') {
// Only trigger the find action after 250ms of silence.
//this.findTimeout = setTimeout(nextMatch.bind(this), 250);
generateMatchList();
} else {
nextMatch();
});
}
}.bind(this));
};
var updatePage = function (index) {
var executeCommand = function (cmd, state) {
if (this.selected.pageIdx === index) {
// 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.
reader.settings.currentPage = index + 1;
reader.search_state = state;
reader.firstPagePromise.then(function() {
if (reader.pageContents.length < reader.settings.numPages)
extractText();
if (cmd === 'find') {
reader.search_active = true;
$match_count.show();
generateMatchList();
}
//var page = this.pdfViewer.getPageView(index);
//if (page.textLayer) {
// page.textLayer.updateMatches();
//}
}.bind(this));
};
var generateMatchList = function () {
var container = document.getElementById("searchResults"),
numPages = reader.settings.numPages,
self = this;
currentIdx = reader.settings.currentPage - 1,
i;
for (var i = 0; i < numPages; i++) {
//var placeholder = document.createElement("li");
//placeholder.style.display = "none";
//container.appendChild(placeholder);
if (!(i in this.pendingFindMatches)) {
this.pendingFindMatches[i] = true;
this.extractTextPromises[i].then(function(pageIdx) {
delete self.pendingFindMatches[pageIdx];
if (reader.pageContents.length !== numPages) {
extractText();
for (i = 0; i < numPages; i++) {
if (!(i in pendingFindMatches)) {
pendingFindMatches[i] = true;
extractTextPromises[i].then(function(pageIdx) {
delete pendingFindMatches[pageIdx];
calcFindMatch(pageIdx);
if (self.pageMatches[pageIdx].length > 0) {
reader.pageMatches[pageIdx] = self.pageMatches[pageIdx];
var fragment = document.createDocumentFragment();
var listitem = document.createElement("li");
listitem.textContent="page " + parseInt(pageIdx + 1);
listitem.classList.add("search_page_header");
fragment.appendChild(listitem);
self.pageMatches[pageIdx].forEach(function (match) {
fragment.appendChild(createItem(pageIdx, match));
});
container.appendChild(fragment);
if (reader.pageMatches[pageIdx].length > 0) {
container.appendChild(createItemList(pageIdx));
}
});
}
}
} else {
for (i = 0; i < numPages; i++) {
calcFindMatch(i);
if (reader.pageMatches[i].length > 0) {
container.appendChild(createItemList(i));
}
}
}
};
var nextMatch = function () {
var nextMatch = function (previous) {
var previous = this.state.findPrevious;
var currentPageIndex = reader.settings.currentPage - 1;
var numPages = reader.settings.numPages;
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 = [];
this.matchCount = 0;
this.pageMatchesLength = null;
var self = this;
for (var i = 0; i < numPages; i++) {
// Wipe out any previous highlighted matches.
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];
calcFindMatch(pageIdx);
});
}
}
}
// If there's no query there's no point in searching.
if (this.state.query === '') {
updateUIState(FindStates.FIND_FOUND);
/* don't try to follow non-existing matches */
if (!reader.search_active ||
reader.pageMatches.length === 0)
return;
}
// If we're waiting on a page, we return since we can't do anything else.
if (this.resumePageIdx) {
return;
}
var numPages = reader.settings.numPages,
selected = reader.selected,
leftIdx = idxOrNull(reader.resourcelst[0].pageNum),
rightIdx = idxOrNull(reader.resourcelst[1].pageNum),
try_match = false;
var offset = this.offset;
// Keep track of how many pages we should maximally iterate through.
this.pagesToSearch = numPages;
// 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);
updateMatch(true);
return;
}
// We went beyond the current page's matches, so we advance to
// the next page.
advanceOffsetPage(previous);
}
// Start searching through the page.
nextPageMatch();
};
/* prevent match cycling on first or last page */
if (!((previous && selected.at_start) || (!previous && selected.at_end))) {
var matchesReady = function (matches) {
var offset = this.offset;
var numMatches = matches.length;
var previous = this.state.findPrevious;
selected.at_start = selected.at_end = false;
if (numMatches) {
// There were matches for the page, so initialize the matchIdx.
this.hadMatch = true;
offset.matchIdx = (previous ? numMatches - 1 : 0);
updateMatch(true);
return true;
}
// No matches, so attempt to search the next page.
advanceOffsetPage(previous);
if (offset.wrapped) {
offset.matchIdx = null;
if (this.pagesToSearch < 0) {
// No point in wrapping again, there were no matches.
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;
};
/**
* The method is called back from the text layer when match presentation
* is updated.
* @param {number} pageIndex - page index.
* @param {number} index - match index.
* @param {Array} elements - text layer div elements array.
* @param {number} beginIdx - start index of the div array for the match.
/* when in spread view, start at left (forward search) or right (backward search) page
* if not iterating over matches on currently visible pages
*/
var updateMatchPosition = function (
pageIndex, index, elements, beginIdx) {
if (this.selected.matchIdx === index &&
this.selected.pageIdx === pageIndex) {
//var spot = {
// top: FIND_SCROLL_OFFSET_TOP,
// left: FIND_SCROLL_OFFSET_LEFT
//};
//scrollIntoView(elements[beginIdx], spot,
// /* skipOverflowHiddenElements = */ true);
if (!(selected.matchIdx !== -1 && isVisible(selected.pageIdx))) {
if (previous) {
selected.pageIdx = (typeof rightIdx === "number") ? rightIdx : leftIdx;
} else {
selected.pageIdx = (typeof leftIdx === "number") ? leftIdx : rightIdx;
}
console.log("would scroll into view here except for the fact that Reader is a non-scrolling reader...");
};
try_match = true;
var nextPageMatch = function () {
if (this.resumePageIdx !== null) {
console.error('There can only be one pending page.');
} else {
var numPageMatches = reader.pageMatches[selected.pageIdx].length;
if ((!previous && selected.matchIdx + 1 < numPageMatches) || (previous && selected.matchIdx > 0)) {
selected.matchIdx = (previous ? selected.matchIdx - 1 : selected.matchIdx + 1);
updateOrQueue();
return;
} else {
selected.pageIdx += (previous) ? -1 : 1;
try_match = true;
}
}
do {
var pageIdx = this.offset.pageIdx;
var 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 (!matchesReady(matches));
};
var advanceOffsetPage = function (previous) {
var offset = this.offset;
var numPages = this.extractTextPromises.length;
offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
offset.matchIdx = null;
if (try_match && nextPageMatch(previous)) {
updateOrQueue();
return;
} else {
if (previous) {
reader.ControlsController.setStatus("at first match", true);
selected.at_start = true;
} else {
reader.ControlsController.setStatus("at last match", true);
selected.at_end = true;
}
return;
}
this.pagesToSearch--;
function idxOrNull(num) {
if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
offset.pageIdx = (previous ? numPages - 1 : 0);
offset.wrapped = true;
if (typeof num === "number") {
return num - 1;
} else {
return null;
}
}
function isVisible (idx) {
return (idx === leftIdx || idx === rightIdx);
}
function nextPageMatch (previous) {
var i,
found;
if (previous) {
for (i = selected.pageIdx; i >= -1 && reader.pageMatches[i] === undefined; i--) {}
} else {
for (i = selected.pageIdx; i <= numPages && reader.pageMatches[i] === undefined; i++) {}
}
if (i < 0 || i >= numPages) {
i = -1;
//selected.pageIdx = selected.matchIdx = -1;
selected.matchIdx = -1;
found = false;
} else {
selected.pageIdx = i;
selected.matchIdx = (previous) ? reader.pageMatches[i].length - 1 : 0;
found = true;
}
return found;
}
function updateOrQueue() {
var root = document.getElementById("searchResults"),
item,
match,
i;
item = root.getElementsByClassName("selected");
while (item.length)
item[0].classList.remove("selected");
match = document.getElementById("match:" + selected.pageIdx + ":" + selected.matchIdx);
match.classList.add("selected");
match = document.getElementsByClassName("match:" + selected.pageIdx + ":" + selected.matchIdx);
for (i = 0; i < match.length; i++)
match[i].classList.add("selected_again");
updateMatchCounter(match.dataset.index);
if (!reader.isVisible(match))
match.scrollIntoView();
if (isVisible(selected.pageIdx)) {
[ leftIdx, rightIdx ].forEach(function (idx) {
if (typeof idx === "number") updatePage(idx);
});
} else {
reader.queuePage(selected.pageIdx + 1);
}
}
};
var updateMatch = function (found) {
var state = FindStates.FIND_NOTFOUND;
var wrapped = this.offset.wrapped;
this.offset.wrapped = false;
var updateMatchCounter = function (index) {
if (found) {
var previousPage = this.selected.pageIdx;
this.selected.pageIdx = this.offset.pageIdx;
this.selected.matchIdx = this.offset.matchIdx;
state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND);
// Update the currently selected page to wipe out any selected matches.
if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
updatePage(previousPage);
}
}
var prefix = "";
updateUIState(state, this.state.findPrevious);
if (this.selected.pageIdx !== -1) {
updatePage(this.selected.pageIdx);
}
if (index)
prefix = index + "/";
$match_count[0].textContent = prefix + matchCount;
};
var updateUIResultsCount = function () {
if (this.onUpdateResultsCount) {
onUpdateResultsCount(this.matchCount);
}
var resetMatchCounter = function () {
$match_count[0].textContent = "0";
$match_count.hide();
};
var updateUIState = function (state, previous) {
if (this.onUpdateState) {
onUpdateState(state, previous, this.matchCount);
}
};
var search = function(q) {
if (q === undefined) {
q = $searchBox.val();
@ -576,10 +521,7 @@ PDFJS.reader.SearchController = function () {
reset();
$searchResults.empty();
this.query = q;
executeCommand('find', {query: q});
highlightQuery();
};
$searchBox.on("keydown", function(e) {
@ -601,14 +543,13 @@ PDFJS.reader.SearchController = function () {
$clear_search.on("click", function () {
reset();
unhighlight();
$searchResults.empty();
$searchBox.val("");
});
var clear = function () {
reset();
unhighlight();
$searchResults.empty();
if (reader.SidebarController.getActivePanel() == "Search") {
@ -616,14 +557,24 @@ PDFJS.reader.SearchController = function () {
}
};
var highlightQuery = function(e) {
$("#text_left").contents().highlight(this.state.query, { element: 'span' });
$("#text_right").contents().highlight(this.state.query, { element: 'span' });
// initialize search
reset();
if (reader.settings.preloadTextcontent) {
reader.firstPagePromise.then(function() {
setTimeout(function() {
extractText();
}, 5000);
});
}
var onShow = function() {
$searchView.addClass("open");
$searchBox.focus();
};
var unhighlight = function(e) {
$("#text_left").unhighlight();
$("#text_right").unhighlight();
var onHide = function() {
$searchView.removeClass("open");
};
@ -632,7 +583,7 @@ PDFJS.reader.SearchController = function () {
"hide": onHide,
"search": search,
"executeCommand": executeCommand,
"highlightQuery": highlightQuery,
"unhighlight": unhighlight
"nextMatch": nextMatch
};
};

View file

@ -1,40 +1,8 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @typedef {Object} TextLayerBuilderOptions
* @property {HTMLDivElement} textLayerDiv - The text layer container.
* @property {EventBus} eventBus - The application event bus.
* @property {number} pageIndex - The page index.
* @property {PageViewport} viewport - The viewport of the text layer.
* @property {PDFFindController} findController
* @property {boolean} enhanceTextSelection - Option to turn on improved
* text selection.
*/
/**
* TextLayerBuilder provides text-selection functionality for the PDF.
* It does this by creating overlay divs over the PDF text. These divs
* contain text that matches the PDF text they are overlaying. This object
* also provides a way to highlight text that is being searched for.
* @class
*/
PDFJS.Reader.TextLayerController = function (options) {
PDFJS.Reader.TextLayerController = function (options, reader) {
var EXPAND_DIVS_TIMEOUT = 300; // ms
this.reader = reader;
this.textLayerDiv = options.textLayerDiv;
this.eventBus = options.eventBus || null;
this.textContent = null;
@ -44,7 +12,6 @@ PDFJS.Reader.TextLayerController = function (options) {
this.matches = [];
this.viewport = options.viewport;
this.textDivs = [];
this.findController = options.findController || null;
this.textLayerRenderTask = null;
this.enhanceTextSelection = options.enhanceTextSelection;
this._bindMouse();
@ -117,13 +84,14 @@ PDFJS.Reader.TextLayerController.prototype.setTextContent = function (textConten
PDFJS.Reader.TextLayerController.prototype.convertMatches = function(matches, matchesLength) {
var reader = this;
var reader = this.reader;
var i = 0;
var iIndex = 0;
var bidiTexts = this.textContent.items;
var end = bidiTexts.length - 1;
var queryLen = reader.search.query.length;
var queryLen = reader.search_state ?
reader.search_state.query.length : null;
var ret = [];
if (!matches) {
return ret;
@ -179,18 +147,15 @@ PDFJS.Reader.TextLayerController.prototype.renderMatches = function (matches) {
return;
}
var reader = this;
var reader = this.reader;
var bidiTexts = this.textContent.items;
var textDivs = this.textDivs;
var prevEnd = null;
var pageIdx = this.pageIdx;
var isSelectedPage = (this.findController === null ?
false : (pageIdx === this.findController.selected.pageIdx));
var selectedMatchIdx = (this.findController === null ?
-1 : this.findController.selected.matchIdx);
var highlightAll = (this.findController === null ?
false : this.findController.state.highlightAll);
var isSelectedPage = (pageIdx === reader.selected.pageIdx);
var selectedMatchIdx = reader.selected.matchIdx;
var highlightAll = true;
var infinity = {
divIdx: -1,
offset: undefined
@ -230,12 +195,8 @@ PDFJS.Reader.TextLayerController.prototype.renderMatches = function (matches) {
var begin = match.begin;
var end = match.end;
var isSelected = (isSelectedPage && i === selectedMatchIdx);
var highlightSuffix = (isSelected ? ' selected' : '');
if (this.findController) {
this.findController.updateMatchPosition(pageIdx, i, textDivs,
begin.divIdx);
}
var id = "match:" + pageIdx + ":" + i;
var highlightSuffix = (isSelected ? ' selected ' + id : ' ' + id);
// Match inside new div.
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
@ -274,6 +235,13 @@ PDFJS.Reader.TextLayerController.prototype.updateMatches = function () {
return;
}
var reader = this.reader;
// Only show matches when search is active
if (reader.search_active !== true) {
return;
}
// Clear all matches.
var matches = this.matches;
var textDivs = this.textDivs;
@ -292,18 +260,12 @@ PDFJS.Reader.TextLayerController.prototype.updateMatches = function () {
clearedUntilDivIdx = match.end.divIdx + 1;
}
if (this.findController === null || !this.findController.active) {
return;
}
// Convert the matches on the page controller into the match format
// used for the textLayer.
var pageMatches, pageMatchesLength;
if (this.findController !== null) {
pageMatches = this.findController.pageMatches[this.pageIdx] || null;
pageMatchesLength = (this.findController.pageMatchesLength) ?
this.findController.pageMatchesLength[this.pageIdx] || null : null;
}
var pageMatches = reader.pageMatches[this.pageIdx] || null,
pageMatchesLength = reader.pageMatchesLength ?
reader.pageMatchesLength[this.pageIdx] || null : null;
this.matches = this.convertMatches(pageMatches, pageMatchesLength);
this.renderMatches(this.matches);

View file

@ -120,7 +120,7 @@ PDFJS.reader.TocController = function() {
scale = parseFloat(settings.thumbnailWidth / width);
preloadcount = parseInt(window.innerHeight / placeholder_height) + 2;
if (preloadcount > settings.numPages)
preloadcount = numPages;
preloadcount = settings.numPages;
var _timeout = setTimeout(function () {
for (var i = 1; i <= preloadcount; i++) {

View file

@ -125,6 +125,15 @@ body {
left: 0;
}
#status_message_left {
margin-left: 1em;
}
#status_message_right,
#match_count {
margin-right: 1em;
}
#metainfo {
position: fixed;
/* width: 80%;

View file

@ -122,6 +122,10 @@
display: block;
}
.list_item.selected a {
color: #D2D2D2;
}
legend {
margin-left: 1em;
padding: 0.5em;
@ -329,6 +333,20 @@ legend {
font-decoration: underline;
}
.search_page_header {
text-align: right;
color: #DDD;
font-style: italic;
}
.match_label {
background-color: #DDD;
color: black;
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
border-radius: 3px;
padding: 0.2em;
}
.searchbox {
width: 80%;
float: left;

View file

@ -37,6 +37,7 @@ PDFJS.Reader = function(bookPath, _options) {
bookPath: bookPath,
textRenderDelay: TEXT_RENDER_DELAY,
pageRenderDelay: PAGE_RENDER_DELAY,
preloadTextcontent: true, // true || false, preload text content to speed up first full-text search operation
canvasLimit: 0,
cssZoomOnly: false, // true || false, only zoom using CSS, render document at 100% size
textSelect: true, // true || false, add selectable text layer
@ -70,9 +71,13 @@ PDFJS.Reader = function(bookPath, _options) {
83: 'toggleSidebar',// s
84: 'toggleTitlebar', // t
68: 'toggleDay', // d
78: 'toggleNight', // n
//78: 'toggleNight', // n
55: 'search', // '/'
80: 'previousMatch', // p
78: 'nextMatch', // n
70: 'toggleFullscreen', // f
27: 'closeSidebar' // esc
27: 'closeSidebar', // esc
114: 'nextMatch' // F3
},
nightMode: false,
dayMode: false,
@ -129,8 +134,17 @@ PDFJS.Reader = function(bookPath, _options) {
this.renderQueue = false;
// used for search
// used for search, textlayer, hightlight etc
this.pageContents = [];
this.pageMatches = [];
this.pageMatchesLength = null;
this.search_state = null;
this.selected = {
pageIdx: -1,
matchIdx: -1,
at_start: false,
at_end: false
};
// define which zoom states to cycle through in cycleZoom
this.zoomCycle = {
@ -184,7 +198,7 @@ PDFJS.Reader = function(bookPath, _options) {
function(_book) {
reader.book = book = _book;
console.log(book);
//console.log(book);
reader.settings.numPages = reader.book.numPages;
document.getElementById('total_pages').textContent = reader.settings.numPages;
if(!$.isEmptyObject(reader.settings.session.cursor)) {
@ -439,6 +453,8 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
reader.resourcelst[1].canvas.style.display = "none";
// clear text layer
reader.resourcelst[1].textdiv.innerHTML = "";
// clear page number
reader.resourcelst[1].pageNum = null;
// don't try to render non-existing page 0 (which is used
// to indicate the empty left page when oddPageRight === true)
@ -480,7 +496,6 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
//console.log(page);
page_rotation = page.rotate;
rotation = (page_rotation + reader.settings.rotation) % 360;
//initial_viewport = page.getViewport({scale: 1, rotation: rotation});
initial_viewport = page.getViewport(1, rotation);
page_width = initial_viewport.width;
page_height = initial_viewport.height;
@ -491,21 +506,6 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
scale_height = parseFloat(max_view_height / page_height);
scale_width = parseFloat(max_view_width / page_width);
/*
console.log("m_v_w: " + max_view_width
+ " m_v_h: " + max_view_height
+ " p_w: " + page_width
+ " p_h: " + page_height
+ " d_a: " + document_aspect
+ " v_a: " + view_aspect
+ " s_w: " + scale_width
+ " s_h: " + scale_height
+ " p_r: " + page_rotation
+ " r: " + rotation
+ " o: " + outputscale);
console.log("fraction: ", fraction);
*/
switch (reader.settings.zoomLevel) {
case "spread":
@ -604,10 +604,10 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
page.getTextContent({ normalizeWhitespace: true }).then(function (textContent) {
resourcelst.textLayer = textLayer = new PDFJS.Reader.TextLayerController({
textLayerDiv: textdiv,
pageIdx: pageNum - 1,
pageIndex: pageNum - 1,
viewport: viewport,
enhanceTextSelection: true
});
}, reader);
textLayer.setTextContent(textContent);
});
} else {
@ -661,6 +661,8 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
canvas.style.width = reader.roundToDivide(max_view_width, fraction[1]) + 'px';
canvas.style.height = reader.roundToDivide(max_view_height, fraction[1]) + 'px';
}
// reset pageNum
resourcelst.pageNum = null;
}
};
@ -1065,3 +1067,19 @@ PDFJS.Reader.prototype.ellipsize = function(str, max, opts) {
return str;
};
PDFJS.Reader.prototype.isVisible = function (element) {
var reader = this,
viewport = element.getBoundingClientRect(),
visible;
visible = (
viewport.top >= 0
&& viewport.left >= 0
&& viewport.right < window.innerWidth
&& viewport.bottom < window.innerHeight
);
return visible;
};