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

files_reader: PDF support, basic functions work (i.e. is usable as reader)

This commit is contained in:
frankdelange 2017-03-24 01:34:01 +01:00
parent 0340300ba0
commit 5da12f92e5
18 changed files with 1730 additions and 138 deletions

View file

@ -1,5 +1,9 @@
## UNRELEASED
###
### Added
- Reader now supports PDF
- PDF should work more or less like EPUB, ie. double page spreads are supported
- optional double-buffering for faster rendering, can be disabled for low-memory devices
- optional selectable text layer, can be disabled for low-memory devices
## 1.0.0 - 2017-03-15
### Added

View file

@ -1,6 +1,5 @@
- add hook to delete book info when books are deleted
- do this for files_opds as well
- for maximize page area, disable two-column when in portrait mode
- maybe always disable two-column in portrait mode
- swipe gestures for open/close sidebar, page turn
- disable wide page turn when placing markers
- index
- search
- bookmarks
- annotations
- settings

View file

@ -121,7 +121,7 @@ The same Android device showing a zoomed-in part of a page|![The same Android de
[SS18]: https://github.com/Yetangitu/owncloud-apps/blob/master/screenshots/photo_2017-03-15_18-28-56.jpg?raw=true "The same Android device showing a zoomed-in part of a page"
]]>
</description>
<version>1.0.0</version>
<version>1.0.1</version>
<licence>AGPL</licence>
<author>Frank de Lange</author>
<documentation>

View file

@ -46,6 +46,8 @@
hideControls: function() {
$('#app-content #controls').hide();
// and, for NC12...
$('#app-navigation').css("display", "none");
},
hide: function() {
@ -54,6 +56,8 @@
}
$("#controls").show();
$('#app-content #controls').removeClass('hidden');
// NC12...
$('#app-navigation').css("display", "");
if ($('#isPublic').val()) {
$('#imgframe').show();
$('footer').show();

View file

@ -12,7 +12,7 @@
$preferences = $_['preferences'];
$metadata = $_['metadata'];
$annotations = $_['annotations'];
$title = htmlentities(basename($dllink));
$title = htmlentities(basename($downloadLink));
$revision = '0071';
$version = \OCP\App::getAppVersion('files_reader') . '.' . $revision;
@ -41,12 +41,14 @@
<link rel="stylesheet" href="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/css/main.css')) ?>?v=<?php p($version) ?>">
<link rel="stylesheet" href="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/css/sidebar.css')) ?>?v=<?php p($version) ?>">
<link rel="stylesheet" href="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/css/popup.css')) ?>?v=<?php p($version) ?>">
<link rel="stylesheet" href="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/css/text_layer_builder.css')) ?>?v=<?php p($version) ?>">
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/epubjs/libs/jquery.min.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/bartaz/jquery.highlight.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/jquery/put-delete.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/sindresorhus/screenfull.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/lib/pdf.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/pdf.reader.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/controllers/textlayer_controller.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/controllers/reader_controller.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/controllers/sidebar_controller.js')) ?>?v=<?php p($version) ?>"> </script>
<script type="text/javascript" nonce="<?php p($nonce) ?>" src="<?php p($urlGenerator->linkTo('files_reader', 'vendor/pdfjs/controllers/settings_controller.js')) ?>?v=<?php p($version) ?>"> </script>
@ -249,6 +251,40 @@
</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="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>
<div class="zoom_option icon-icon-fit-width" data-value="fit_width" data-class="icon-icon-fit-width" data-text=""></div>
<div class="zoom_option" data-value="0.25" data-text="25%">25%</div>
<div class="zoom_option" data-value="0.5" data-text="50%">50%</div>
<div class="zoom_option" data-value="0.75" data-text="75%">75%</div>
<div class="zoom_option" data-value="1" data-text="100%">100%</div>
<div class="zoom_option" data-value="1.25" data-text="125%">125%</div>
<div class="zoom_option" data-value="1.5" data-text="150%">150%</div>
<div class="zoom_option" data-value="2" data-text="200%">200%</div>
<div class="zoom_option" data-value="3" data-text="300%">300%</div>
<div class="zoom_option" data-value="4" data-text="400%">400%</div>
</div>
<span id="zoom_icon"></span>
<span class="controls-separator"> </span>
<a></a>
<a id="note" class="icon-comment">
</a>
<a id="bookmark" class="icon-turned_in_not">
@ -276,9 +312,9 @@
</div>
</div>
<div ID="viewer">
<canvas id="left" class="viewer"></canvas>
<canvas id="right" class="viewer"></canvas>
<div id="viewer" class="flex">
<canvas id="left" class="viewer"></canvas><div id="text_left" class="textLayer"></div>
<canvas id="right" class="viewer"></canvas><div id="text_right" class="textLayer"></div>
</div>
<div id="next" class="arrow">
<div class="translucent">

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,27 +1,45 @@
PDFJS.reader.ControlsController = function(book) {
var reader = this;
var reader = this,
settings = reader.settings;
var $store = $("#store"),
$viewer = $("#viewer"),
$fullscreen = $("#fullscreen"),
$fullscreenicon = $("#fullscreenicon"),
$cancelfullscreenicon = $("#cancelfullscreenicon"),
$slider = $("#slider"),
$main = $("#main"),
$sidebar = $("#sidebar"),
$titlebar = $("#titlebar"),
$settings = $("#setting"),
$bookmark = $("#bookmark"),
$note = $("#note");
$note = $("#note"),
$togglelayout = $("#toggle-layout"),
$zoom_icon = $("#zoom_icon"),
$zoom_options = $("#zoom_options"),
$zoom_option = $(".zoom_option"),
$page_num = $("#page_num");
var goOnline = function() {
reader.offline = false;
// $store.attr("src", $icon.data("save"));
if (reader.isMobile() === true) {
$titlebar.addClass("background_visible");
};
var goOffline = function() {
reader.offline = true;
// $store.attr("src", $icon.data("saved"));
var show = function () {
$titlebar.removeClass("hide");
};
var hide = function () {
$titlebar.addClass("hide");
};
var toggle = function () {
$titlebar.toggleClass("hide");
};
$viewer.on("touchstart", function(e) {
reader.ControlsController.toggle();
});
var fullscreen = false;
$slider.on("click", function () {
@ -81,6 +99,94 @@ 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
* 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 */
$zoom_icon.on("click", function () {
var offset = $(this).offset();
console.log(offset);
$zoom_options.css({
'left': parseInt(offset.left) + "px",
'top' : parseInt(parseInt(offset.top) + parseInt($zoom_icon.height())) + "px"
});
$zoom_options.toggleClass("hide");
});
$zoom_icon[0].className="";
var $current_zoom_option = $zoom_options.find("[data-value='" + settings.zoomLevel + "']");
if ($current_zoom_option.data("class")) {
$zoom_icon.addClass($current_zoom_option.data("class"));
} else {
$zoom_icon[0].textContent = $current_zoom_option.data("text");
}
$zoom_option.on("click", function () {
var $this = $(this);
reader.setZoom($this.data("value"));
$zoom_icon[0].className="";
$zoom_icon[0].textContent = "";
if ($this.data("class")) {
$zoom_icon.addClass($this.data("class"));
} else {
$zoom_icon[0].textContent = $this.data("text");
}
$zoom_options.addClass("hide");
});
/* end custom select */
var enterPageNum = function(e) {
var text = e.target,
page;
switch (e.keyCode) {
case 27: // escape - cancel, discard changes
$page_num[0].removeEventListener("keydown", enterPageNum, false);
$page_num.removeClass("editable");
$page_num.prop("contenteditable",false);
$page_num.text($page_num.data("content"));
break;
case 13: // enter - accept changes
$page_num[0].removeEventListener("keydown", enterPageNum, false);
$page_num.removeClass("editable");
$page_num.attr("contenteditable", false);
page = parseInt($page_num.text());
if (page > 0 && page <= reader.settings.numPages) {
reader.queuePage(page);
} else {
$page_num.text($page_num.data("content"));
}
break;
}
e.stopPropagation;
};
$page_num.on("click", function() {
$page_num.data("content", $page_num.text());
$page_num.text("");
$page_num.prop("contenteditable", true);
$page_num.addClass("editable");
$page_num[0].addEventListener("keydown", enterPageNum, false);
});
/*
book.on('renderer:locationChanged', function(cfi){
var cfiFragment = "#" + cfi;
@ -113,6 +219,8 @@ PDFJS.reader.ControlsController = function(book) {
*/
return {
"show": show,
"hide": hide,
"toggle": toggle
};
};

View file

@ -1,5 +1,6 @@
PDFJS.reader.ReaderController = function(book) {
var $main = $("#main"),
$viewer = $("#viewer"),
$divider = $("#divider"),
$loader = $("#loader"),
$next = $("#next"),
@ -84,7 +85,7 @@ PDFJS.reader.ReaderController = function(book) {
page_no = 1;
break;
case 'last':
// TODO
page_no = reader.settings.numPages;
break;
case 'annotate':
$note.click();
@ -95,6 +96,9 @@ PDFJS.reader.ReaderController = function(book) {
case 'reflow':
$sidebarReflow.click();
break;
case 'toggleTitlebar':
reader.ControlsController.toggle();
break;
case 'toggleSidebar':
reader.SidebarController.toggle();
break;
@ -115,8 +119,7 @@ PDFJS.reader.ReaderController = function(book) {
}
if (page_no) {
// TODO
reader.queuePage(page_no);
}
}

View file

@ -0,0 +1,379 @@
/* 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) {
var EXPAND_DIVS_TIMEOUT = 300; // ms
this.textLayerDiv = options.textLayerDiv;
this.eventBus = options.eventBus || null;
this.textContent = null;
this.renderingDone = false;
this.pageIdx = options.pageIndex;
this.pageNumber = this.pageIdx + 1;
this.matches = [];
this.viewport = options.viewport;
this.textDivs = [];
this.findController = options.findController || null;
this.textLayerRenderTask = null;
this.enhanceTextSelection = options.enhanceTextSelection;
this._bindMouse();
return this;
};
PDFJS.Reader.TextLayerController.prototype._finishRendering = function () {
this.renderingDone = true;
if (!this.enhanceTextSelection) {
var endOfContent = document.createElement('div');
endOfContent.className = 'endOfContent';
this.textLayerDiv.appendChild(endOfContent);
}
if (this.eventBus !== null) {
this.eventBus.dispatch('textlayerrendered', {
source: this,
pageNumber: this.pageNumber,
numTextDivs: this.textDivs.length,
});
}
};
/**
* Renders the text layer.
* @param {number} timeout (optional) if specified, the rendering waits
* for specified amount of ms.
*/
PDFJS.Reader.TextLayerController.prototype.render = function(timeout) {
if (!this.textContent || this.renderingDone) {
return;
}
this.cancel();
this.textDivs = [];
var textLayerFrag = document.createDocumentFragment();
this.textLayerRenderTask = PDFJS.renderTextLayer({
textContent: this.textContent,
container: textLayerFrag,
viewport: this.viewport,
textDivs: this.textDivs,
timeout: timeout,
enhanceTextSelection: this.enhanceTextSelection,
});
this.textLayerRenderTask.promise.then(function () {
this.textLayerDiv.appendChild(textLayerFrag);
this._finishRendering();
this.updateMatches();
}.bind(this), function (reason) {
// cancelled or failed to render text layer -- skipping errors
});
};
/**
* Cancels rendering of the text layer.
*/
PDFJS.Reader.TextLayerController.prototype.cancel = function () {
if (this.textLayerRenderTask) {
this.textLayerRenderTask.cancel();
this.textLayerRenderTask = null;
}
};
PDFJS.Reader.TextLayerController.prototype.setTextContent = function (textContent) {
this.cancel();
this.textContent = textContent;
};
PDFJS.Reader.TextLayerController.prototype.convertMatches = function(matches, matchesLength) {
var i = 0;
var iIndex = 0;
var bidiTexts = this.textContent.items;
var end = bidiTexts.length - 1;
var queryLen = (this.findController === null ?
0 : this.findController.state.query.length);
var ret = [];
if (!matches) {
return ret;
}
for (var m = 0, len = matches.length; m < len; m++) {
// Calculate the start position.
var matchIdx = matches[m];
// Loop over the divIdxs.
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
iIndex += bidiTexts[i].str.length;
i++;
}
if (i === bidiTexts.length) {
console.error('Could not find a matching mapping');
}
var match = {
begin: {
divIdx: i,
offset: matchIdx - iIndex
}
};
// Calculate the end position.
if (matchesLength) { // multiterm search
matchIdx += matchesLength[m];
} else { // phrase search
matchIdx += queryLen;
}
// Somewhat the same array as above, but use > instead of >= to get
// the end position right.
while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
iIndex += bidiTexts[i].str.length;
i++;
}
match.end = {
divIdx: i,
offset: matchIdx - iIndex
};
ret.push(match);
}
return ret;
};
PDFJS.Reader.TextLayerController.prototype.renderMatches = function (matches) {
// Early exit if there is nothing to render.
if (matches.length === 0) {
return;
}
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 infinity = {
divIdx: -1,
offset: undefined
};
function beginText(begin, className) {
var divIdx = begin.divIdx;
textDivs[divIdx].textContent = '';
appendTextToDiv(divIdx, 0, begin.offset, className);
}
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
var div = textDivs[divIdx];
var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
var node = document.createTextNode(content);
if (className) {
var span = document.createElement('span');
span.className = className;
span.appendChild(node);
div.appendChild(span);
return;
}
div.appendChild(node);
}
var i0 = selectedMatchIdx, i1 = i0 + 1;
if (highlightAll) {
i0 = 0;
i1 = matches.length;
} else if (!isSelectedPage) {
// Not highlighting all and this isn't the selected page, so do nothing.
return;
}
for (var i = i0; i < i1; i++) {
var match = matches[i];
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);
}
// Match inside new div.
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
// If there was a previous div, then add the text at the end.
if (prevEnd !== null) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
// Clear the divs and set the content until the starting point.
beginText(begin);
} else {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
}
if (begin.divIdx === end.divIdx) {
appendTextToDiv(begin.divIdx, begin.offset, end.offset,
'highlight' + highlightSuffix);
} else {
appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
'highlight begin' + highlightSuffix);
for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
textDivs[n0].className = 'highlight middle' + highlightSuffix;
}
beginText(end, 'highlight end' + highlightSuffix);
}
prevEnd = end;
}
if (prevEnd) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
};
PDFJS.Reader.TextLayerController.prototype.updateMatches = function () {
// Only show matches when all rendering is done.
if (!this.renderingDone) {
return;
}
// Clear all matches.
var matches = this.matches;
var textDivs = this.textDivs;
var bidiTexts = this.textContent.items;
var clearedUntilDivIdx = -1;
// Clear all current matches.
for (var i = 0, len = matches.length; i < len; i++) {
var match = matches[i];
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
for (var n = begin, end = match.end.divIdx; n <= end; n++) {
var div = textDivs[n];
div.textContent = bidiTexts[n].str;
div.className = '';
}
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;
}
this.matches = this.convertMatches(pageMatches, pageMatchesLength);
this.renderMatches(this.matches);
};
/**
* Fixes text selection: adds additional div where mouse was clicked.
* This reduces flickering of the content if mouse slowly dragged down/up.
* @private
*/
PDFJS.Reader.TextLayerController.prototype._bindMouse = function () {
var div = this.textLayerDiv;
var self = this;
var expandDivsTimer = null;
div.addEventListener('mousedown', function (e) {
if (self.enhanceTextSelection && self.textLayerRenderTask) {
self.textLayerRenderTask.expandTextDivs(true);
if ((typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) &&
expandDivsTimer) {
clearTimeout(expandDivsTimer);
expandDivsTimer = null;
}
return;
}
var end = div.querySelector('.endOfContent');
if (!end) {
return;
}
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
// On non-Firefox browsers, the selection will feel better if the height
// of the endOfContent div will be adjusted to start at mouse click
// location -- this will avoid flickering when selections moves up.
// However it does not work when selection started on empty space.
var adjustTop = e.target !== div;
if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
adjustTop = adjustTop && window.getComputedStyle(end).
getPropertyValue('-moz-user-select') !== 'none';
}
if (adjustTop) {
var divBounds = div.getBoundingClientRect();
var r = Math.max(0, (e.pageY - divBounds.top) / divBounds.height);
end.style.top = (r * 100).toFixed(2) + '%';
}
}
end.classList.add('active');
});
div.addEventListener('mouseup', function (e) {
if (self.enhanceTextSelection && self.textLayerRenderTask) {
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
expandDivsTimer = setTimeout(function() {
if (self.textLayerRenderTask) {
self.textLayerRenderTask.expandTextDivs(false);
}
expandDivsTimer = null;
}, 300);
} else {
self.textLayerRenderTask.expandTextDivs(false);
}
return;
}
var end = div.querySelector('.endOfContent');
if (!end) {
return;
}
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
end.style.top = '';
}
end.classList.remove('active');
});
};

View file

@ -0,0 +1,185 @@
PDFJS.reader.TextLayerController = function() {
var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) {
var textLayerFrag = document.createDocumentFragment();
this.textLayerDiv = textLayerDiv;
this.layoutDone = false;
this.divContentDone = false;
this.pageIdx = pageIdx;
this.matches = [];
this.beginLayout = function textLayerBuilderBeginLayout() {
this.textDivs = [];
this.renderingDone = false;
};
this.endLayout = function textLayerBuilderEndLayout() {
this.layoutDone = true;
this.insertDivContent();
};
this.renderLayer = function textLayerBuilderRenderLayer() {
var textDivs = this.textDivs;
var bidiTexts = this.textContent.bidiTexts;
var textLayerDiv = this.textLayerDiv;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// No point in rendering so many divs as it'd make the browser unusable
// even after the divs are rendered
var MAX_TEXT_DIVS_TO_RENDER = 100000;
if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
return;
for (var i = 0, ii = textDivs.length; i < ii; i++) {
var textDiv = textDivs[i];
if ('isWhitespace' in textDiv.dataset) {
continue;
}
textLayerFrag.appendChild(textDiv);
ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
var width = ctx.measureText(textDiv.textContent).width;
if (width > 0) {
var textScale = textDiv.dataset.canvasWidth / width;
var transform = 'scale(' + textScale + ', 1)';
if (bidiTexts[i].dir === 'ttb') {
transform = 'rotate(90deg) ' + transform;
}
CustomStyle.setProp('transform', textDiv, transform);
CustomStyle.setProp('transformOrigin', textDiv, '0% 0%');
textLayerDiv.appendChild(textDiv);
}
}
this.renderingDone = true;
textLayerDiv.appendChild(textLayerFrag);
};
this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
// Schedule renderLayout() if user has been scrolling, otherwise
// run it right away
var RENDER_DELAY = 200; // in ms
var self = this;
//0 was originally PDFView.lastScroll
if (Date.now() - 0 > RENDER_DELAY) {
// Render right away
this.renderLayer();
} else {
// Schedule
if (this.renderTimer)
clearTimeout(this.renderTimer);
this.renderTimer = setTimeout(function () {
self.setupRenderLayoutTimer();
}, RENDER_DELAY);
}
};
this.appendText = function textLayerBuilderAppendText(geom) {
var textDiv = document.createElement('div');
// vScale and hScale already contain the scaling to pixel units
var fontHeight = geom.fontSize * Math.abs(geom.vScale);
textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale;
textDiv.dataset.fontName = geom.fontName;
textDiv.style.fontSize = fontHeight + 'px';
textDiv.style.fontFamily = geom.fontFamily;
textDiv.style.left = geom.x + 'px';
textDiv.style.top = (geom.y - fontHeight) + 'px';
// The content of the div is set in the `setTextContent` function.
this.textDivs.push(textDiv);
};
this.insertDivContent = function textLayerUpdateTextContent() {
// Only set the content of the divs once layout has finished, the content
// for the divs is available and content is not yet set on the divs.
if (!this.layoutDone || this.divContentDone || !this.textContent)
return;
this.divContentDone = true;
var textDivs = this.textDivs;
var bidiTexts = this.textContent.bidiTexts;
for (var i = 0; i < bidiTexts.length; i++) {
var bidiText = bidiTexts[i];
var textDiv = textDivs[i];
if (!/\S/.test(bidiText.str)) {
textDiv.dataset.isWhitespace = true;
continue;
}
textDiv.textContent = bidiText.str;
// bidiText.dir may be 'ttb' for vertical texts.
textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr';
}
this.setupRenderLayoutTimer();
};
this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
this.textContent = textContent;
this.insertDivContent();
};
};
};
var CustomStyle = (function CustomStyleClosure() {
// As noted on: http://www.zachstronaut.com/posts/2009/02/17/
// animate-css-transforms-firefox-webkit.html
// in some versions of IE9 it is critical that ms appear in this list
// before Moz
var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
var _cache = { };
function CustomStyle() {
}
CustomStyle.getProp = function get(propName, element) {
// check cache only when no element is given
if (arguments.length == 1 && typeof _cache[propName] == 'string') {
return _cache[propName];
}
element = element || document.documentElement;
var style = element.style, prefixed, uPropName;
// test standard property first
if (typeof style[propName] == 'string') {
return (_cache[propName] = propName);
}
// capitalize
uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
// test vendor specific properties
for (var i = 0, l = prefixes.length; i < l; i++) {
prefixed = prefixes[i] + uPropName;
if (typeof style[prefixed] == 'string') {
return (_cache[propName] = prefixed);
}
}
//if all fails then set to undefined
return (_cache[propName] = 'undefined');
};
CustomStyle.setProp = function set(propName, element, str) {
var prop = this.getProp(propName);
if (prop != 'undefined')
element.style[prop] = str;
};
return CustomStyle;
})();

Binary file not shown.

View file

@ -41,6 +41,7 @@ body {
#titlebar {
padding: 0.5em;
height: 1.4em;
color: #4f4f4f;
font-weight: 100;
font-family: Georgia, "Times New Roman", Times, serif;
@ -54,7 +55,15 @@ body {
}
#titlebar:hover {
opacity: 1;
opacity: 1;
background-color: white;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
}
.background_visible {
background-color: white;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
opacity: 1 !important;
}
#titlebar a {
@ -91,12 +100,24 @@ body {
display: none;
}
.controls-separator {
width: 1em;
}
.editable {
display: inline-block;
min-width: 2em;
box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.4);
}
#title-controls,
#opener {
margin-left: 0.3em;
margin-right: 0.3em;
position: fixed;
top: 0.3em;
display: flex;
align-items: center;
}
#opener {
@ -121,25 +142,61 @@ body {
right: 0;
}
#zioom_icon {
text-align: center;
font-family: 'icomoon', 'Helvetica' !important;
font-size: 1.2em;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
border: none;
}
#zoom_icon {
padding: 0 0.5em;
}
#zoom_options {
font-family: "Lucida Console", "Monospace", "Courier New";
position: fixed;
box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.4);
background-color: white;
font-size: 1em;
padding: 0.2em 0.5em;
}
.zoom_option:hover {
background: black;
color: white;
}
#viewer {
display: flex;
justify-content: center;
/* width: 100%; */
/* height: 80%; */
height: calc(100% - 2em);
/* top: 10%; */
top: 2em;
height: 100%;
padding: 0;
margin: 0;
z-index: 2;
position: relative;
overflow: hidden;
overflow: scroll;
}
#viewer.flex {
/* top: 2em; */
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
}
#viewer canvas {
position: relative;
}
#viewer iframe {
border: none;
/* position: relative; */
/* height: auto; */
/* width: auto; */
}
#prev, #next {
@ -150,7 +207,7 @@ body {
}
.touch_nav {
width: 35%;
width: 25%;
}
.arrow div {
@ -295,11 +352,11 @@ body {
width: 80%;
margin-left: 10%;
}
*/
#viewer {
width: 95%;
}
*/
#prev {
padding-left: 0;
}

View file

@ -0,0 +1,82 @@
/* Copyright 2014 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.
*/
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1.0;
}
.textLayer > div {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-webkit-transform-origin: 0% 0%;
-moz-transform-origin: 0% 0%;
-o-transform-origin: 0% 0%;
-ms-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
.textLayer .highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
}
.textLayer .highlight.begin {
border-radius: 4px 0px 0px 4px;
}
.textLayer .highlight.end {
border-radius: 0px 4px 4px 0px;
}
.textLayer .highlight.middle {
border-radius: 0px;
}
.textLayer .highlight.selected {
background-color: rgb(0, 100, 0);
}
.textLayer ::selection { background: rgb(0,0,255); }
.textLayer ::-moz-selection { background: rgb(0,0,255); }
.textLayer .endOfContent {
display: block;
position: absolute;
left: 0px;
top: 100%;
right: 0px;
bottom: 0px;
z-index: -1;
cursor: default;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
}
.textLayer .endOfContent.active {
top: 0px;
}

View file

@ -0,0 +1,424 @@
/* 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.
*/
'use strict';
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('pdfjs-web/text_layer_builder', ['exports', 'pdfjs-web/dom_events',
'pdfjs-web/pdfjs'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports, require('./dom_events.js'), require('./pdfjs.js'));
} else {
factory((root.pdfjsWebTextLayerBuilder = {}), root.pdfjsWebDOMEvents,
root.pdfjsWebPDFJS);
}
}(this, function (exports, domEvents, pdfjsLib) {
var EXPAND_DIVS_TIMEOUT = 300; // ms
/**
* @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
*/
var TextLayerBuilder = (function TextLayerBuilderClosure() {
function TextLayerBuilder(options) {
this.textLayerDiv = options.textLayerDiv;
this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
this.textContent = null;
this.renderingDone = false;
this.pageIdx = options.pageIndex;
this.pageNumber = this.pageIdx + 1;
this.matches = [];
this.viewport = options.viewport;
this.textDivs = [];
this.findController = options.findController || null;
this.textLayerRenderTask = null;
this.enhanceTextSelection = options.enhanceTextSelection;
this._bindMouse();
}
TextLayerBuilder.prototype = {
/**
* @private
*/
_finishRendering: function TextLayerBuilder_finishRendering() {
this.renderingDone = true;
if (!this.enhanceTextSelection) {
var endOfContent = document.createElement('div');
endOfContent.className = 'endOfContent';
this.textLayerDiv.appendChild(endOfContent);
}
this.eventBus.dispatch('textlayerrendered', {
source: this,
pageNumber: this.pageNumber,
numTextDivs: this.textDivs.length,
});
},
/**
* Renders the text layer.
* @param {number} timeout (optional) if specified, the rendering waits
* for specified amount of ms.
*/
render: function TextLayerBuilder_render(timeout) {
if (!this.textContent || this.renderingDone) {
return;
}
this.cancel();
this.textDivs = [];
var textLayerFrag = document.createDocumentFragment();
this.textLayerRenderTask = pdfjsLib.renderTextLayer({
textContent: this.textContent,
container: textLayerFrag,
viewport: this.viewport,
textDivs: this.textDivs,
timeout: timeout,
enhanceTextSelection: this.enhanceTextSelection,
});
this.textLayerRenderTask.promise.then(function () {
this.textLayerDiv.appendChild(textLayerFrag);
this._finishRendering();
this.updateMatches();
}.bind(this), function (reason) {
// cancelled or failed to render text layer -- skipping errors
});
},
/**
* Cancels rendering of the text layer.
*/
cancel: function TextLayerBuilder_cancel() {
if (this.textLayerRenderTask) {
this.textLayerRenderTask.cancel();
this.textLayerRenderTask = null;
}
},
setTextContent: function TextLayerBuilder_setTextContent(textContent) {
this.cancel();
this.textContent = textContent;
},
convertMatches: function TextLayerBuilder_convertMatches(matches,
matchesLength) {
var i = 0;
var iIndex = 0;
var bidiTexts = this.textContent.items;
var end = bidiTexts.length - 1;
var queryLen = (this.findController === null ?
0 : this.findController.state.query.length);
var ret = [];
if (!matches) {
return ret;
}
for (var m = 0, len = matches.length; m < len; m++) {
// Calculate the start position.
var matchIdx = matches[m];
// Loop over the divIdxs.
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
iIndex += bidiTexts[i].str.length;
i++;
}
if (i === bidiTexts.length) {
console.error('Could not find a matching mapping');
}
var match = {
begin: {
divIdx: i,
offset: matchIdx - iIndex
}
};
// Calculate the end position.
if (matchesLength) { // multiterm search
matchIdx += matchesLength[m];
} else { // phrase search
matchIdx += queryLen;
}
// Somewhat the same array as above, but use > instead of >= to get
// the end position right.
while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
iIndex += bidiTexts[i].str.length;
i++;
}
match.end = {
divIdx: i,
offset: matchIdx - iIndex
};
ret.push(match);
}
return ret;
},
renderMatches: function TextLayerBuilder_renderMatches(matches) {
// Early exit if there is nothing to render.
if (matches.length === 0) {
return;
}
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 infinity = {
divIdx: -1,
offset: undefined
};
function beginText(begin, className) {
var divIdx = begin.divIdx;
textDivs[divIdx].textContent = '';
appendTextToDiv(divIdx, 0, begin.offset, className);
}
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
var div = textDivs[divIdx];
var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
var node = document.createTextNode(content);
if (className) {
var span = document.createElement('span');
span.className = className;
span.appendChild(node);
div.appendChild(span);
return;
}
div.appendChild(node);
}
var i0 = selectedMatchIdx, i1 = i0 + 1;
if (highlightAll) {
i0 = 0;
i1 = matches.length;
} else if (!isSelectedPage) {
// Not highlighting all and this isn't the selected page, so do nothing.
return;
}
for (var i = i0; i < i1; i++) {
var match = matches[i];
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);
}
// Match inside new div.
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
// If there was a previous div, then add the text at the end.
if (prevEnd !== null) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
// Clear the divs and set the content until the starting point.
beginText(begin);
} else {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
}
if (begin.divIdx === end.divIdx) {
appendTextToDiv(begin.divIdx, begin.offset, end.offset,
'highlight' + highlightSuffix);
} else {
appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
'highlight begin' + highlightSuffix);
for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
textDivs[n0].className = 'highlight middle' + highlightSuffix;
}
beginText(end, 'highlight end' + highlightSuffix);
}
prevEnd = end;
}
if (prevEnd) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
},
updateMatches: function TextLayerBuilder_updateMatches() {
// Only show matches when all rendering is done.
if (!this.renderingDone) {
return;
}
// Clear all matches.
var matches = this.matches;
var textDivs = this.textDivs;
var bidiTexts = this.textContent.items;
var clearedUntilDivIdx = -1;
// Clear all current matches.
for (var i = 0, len = matches.length; i < len; i++) {
var match = matches[i];
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
for (var n = begin, end = match.end.divIdx; n <= end; n++) {
var div = textDivs[n];
div.textContent = bidiTexts[n].str;
div.className = '';
}
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;
}
this.matches = this.convertMatches(pageMatches, pageMatchesLength);
this.renderMatches(this.matches);
},
/**
* Fixes text selection: adds additional div where mouse was clicked.
* This reduces flickering of the content if mouse slowly dragged down/up.
* @private
*/
_bindMouse: function TextLayerBuilder_bindMouse() {
var div = this.textLayerDiv;
var self = this;
var expandDivsTimer = null;
div.addEventListener('mousedown', function (e) {
if (self.enhanceTextSelection && self.textLayerRenderTask) {
self.textLayerRenderTask.expandTextDivs(true);
if ((typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) &&
expandDivsTimer) {
clearTimeout(expandDivsTimer);
expandDivsTimer = null;
}
return;
}
var end = div.querySelector('.endOfContent');
if (!end) {
return;
}
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
// On non-Firefox browsers, the selection will feel better if the height
// of the endOfContent div will be adjusted to start at mouse click
// location -- this will avoid flickering when selections moves up.
// However it does not work when selection started on empty space.
var adjustTop = e.target !== div;
if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
adjustTop = adjustTop && window.getComputedStyle(end).
getPropertyValue('-moz-user-select') !== 'none';
}
if (adjustTop) {
var divBounds = div.getBoundingClientRect();
var r = Math.max(0, (e.pageY - divBounds.top) / divBounds.height);
end.style.top = (r * 100).toFixed(2) + '%';
}
}
end.classList.add('active');
});
div.addEventListener('mouseup', function (e) {
if (self.enhanceTextSelection && self.textLayerRenderTask) {
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
expandDivsTimer = setTimeout(function() {
if (self.textLayerRenderTask) {
self.textLayerRenderTask.expandTextDivs(false);
}
expandDivsTimer = null;
}, EXPAND_DIVS_TIMEOUT);
} else {
self.textLayerRenderTask.expandTextDivs(false);
}
return;
}
var end = div.querySelector('.endOfContent');
if (!end) {
return;
}
if (typeof PDFJSDev === 'undefined' ||
!PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
end.style.top = '';
}
end.classList.remove('active');
});
},
};
return TextLayerBuilder;
})();
/**
* @constructor
* @implements IPDFTextLayerFactory
*/
function DefaultTextLayerFactory() {}
DefaultTextLayerFactory.prototype = {
/**
* @param {HTMLDivElement} textLayerDiv
* @param {number} pageIndex
* @param {PageViewport} viewport
* @param {boolean} enhanceTextSelection
* @returns {TextLayerBuilder}
*/
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport,
enhanceTextSelection) {
return new TextLayerBuilder({
textLayerDiv: textLayerDiv,
pageIndex: pageIndex,
viewport: viewport,
enhanceTextSelection: enhanceTextSelection
});
}
};
exports.TextLayerBuilder = TextLayerBuilder;
exports.DefaultTextLayerFactory = DefaultTextLayerFactory;
}));

View file

@ -1,6 +1,8 @@
PDFJS.reader = {};
PDFJS.reader.plugins = {};
PDFJS.TextLayerBuilder = {};
(function(root, $) {
var previousReader = root.pdfReader || {};
@ -18,13 +20,20 @@ PDFJS.Reader = function(bookPath, _options) {
$viewer = $("#viewer"),
search = window.location.search;
var TEXT_RENDER_DELAY = 200, // ms
PAGE_RENDER_DELAY = 200; // ms
this.settings = this.defaults(_options || {}, {
bookPath: bookPath,
textRenderDelay: TEXT_RENDER_DELAY,
pageRenderDelay: PAGE_RENDER_DELAY,
textSelect: true, // true || false, add selectable text layer
doubleBuffer: true, // true || false, draw to off-screen canvas
numPages: 0,
currentPage: 1,
scale: 1,
sideBySide: true, // when true, render 2 pages side-by-side
oddPageRight: true, // when true, odd pages render on the right side
zoomLevel: window.outerWidth > window.outerHeight ? "spread" : "fit_page", // spread, fit_page, fit_width, percentage
history: true,
keyboard: {
32: 'next', // space
@ -38,7 +47,7 @@ PDFJS.Reader = function(bookPath, _options) {
66: 'bookmark', // b
82: 'reflow', // r
83: 'toggleSidebar', // s
84: 'toolbar', // t
84: 'toggleTitlebar', // t
68: 'toggleDay', // d
78: 'toggleNight', // n
70: 'toggleFullscreen', // f
@ -72,15 +81,32 @@ PDFJS.Reader = function(bookPath, _options) {
this.extra = extra || null;
};
this.canvaslst = [
document.getElementById("left"),
document.getElementById("right")
this.resourcelst = [
{
canvas: document.getElementById("left"),
ctx: document.getElementById("left").getContext('2d'),
textdiv: document.getElementById("text_left"),
textLayer: null,
renderTask: null,
oscanvas: null,
osctx: null,
pageNum: null
},
{
canvas: document.getElementById("right"),
ctx: document.getElementById("right").getContext('2d'),
textdiv: document.getElementById("text_right"),
textLayer: null,
renderTask: null,
oscanvas: null,
osctx: null,
pageNum: null
}
];
this.contextlst = [
document.getElementById("left").getContext('2d'),
document.getElementById("right").getContext('2d')
];
this.cancelPage = {};
this.renderQueue = false;
// Overide options with search parameters
if(search) {
@ -93,13 +119,12 @@ PDFJS.Reader = function(bookPath, _options) {
});
}
//this.restoreDefaults(this.settings.session.defaults);
//this.restorePreferences(this.settings.session.preferences);
//this.restoreAnnotations(this.settings.session.annotations);
this.sideBarOpen = false;
this.viewerResized = false;
//this.sideBySide = window.outerWidth > window.outerHeight ? true : false;
this.sideBySide = false;
this.pageNumPending = null;
PDFJS.getDocument(reader.settings.bookPath).then(function(_book) {
@ -121,9 +146,11 @@ PDFJS.Reader = function(bookPath, _options) {
//reader.SearchController = PDFJS.reader.SearchController.call(reader, book);
//reader.MetaController = EPUBJS.reader.MetaController.call(reader, meta);
//reader.TocController = EPUBJS.reader.TocController.call(reader, toc);
//reader.TextLayerController = PDFJS.reader.TextLayerController();
//reader.queuePage(reader.settings.currentPage);
var startPage = reader.settings.oddPageRight ? 0 : 1;
console.log(reader.settings);
var startPage = (reader.settings.zoomLevel === "spread" && reader.settings.oddPageRight) ? 0 : 1;
reader.queuePage(startPage);
reader.ReaderController.hideLoader();
});
@ -131,114 +158,273 @@ PDFJS.Reader = function(bookPath, _options) {
return this;
};
PDFJS.Reader.prototype.setZoom = function(zoom) {
var reader = this,
page = reader.settings.currentPage;
reader.settings.zoomLevel = zoom;
reader.queuePage(page);
};
PDFJS.Reader.prototype.cancelRender = function (index) {
var reader = this,
resourcelst = reader.resourcelst[index];
if (resourcelst.renderTask) {
console.log("cancel render on canvas " + index + ", pageNum " + resourcelst.pageNum);
resourcelst.renderTask.cancel();
resourcelst.renderTask = resourcelst.pageNum = null;
resourcelst.oscanvas = resourcelst.osctx = null;
}
if (resourcelst.textLayer) {
resourcelst.textLayer.cancel();
resourcelst.textLayer = null;
}
};
PDFJS.Reader.prototype.renderPage = function(pageNum) {
var reader = this,
pageShift = this.settings.sideBySide ? 2 : 1,
oddPageShift = this.settings.oddPageRight ? 0 : 1,
i = (pageNum - oddPageShift) % pageShift,
canvas = this.canvaslst[i],
ctx = this.contextlst[i],
pixelratio = window.devicePixelRatio,
max_view_height = parseInt(window.outerHeight * 0.95),
max_view_width = reader.settings.sideBySide
? parseInt((window.outerWidth / 2) * 1)
: parseInt(window.outerWidth * 1),
scale,
transform,
var reader = this,
$viewer = $("#viewer"),
$page_num = document.getElementById('page_num');
if (this.settings.sideBySide) {
this.canvaslst[1].style.display = "block";
var index,
canvas, // actual canvas
ctx, // actual context
oscanvas, // off-screen canvas
osctx, // off-screen context
textdiv,
textLayer,
outputscale,
max_view_width,
max_view_height,
scale_width,
scale_height,
view_aspect,
document_aspect,
scale,
viewport,
zoom,
fraction,
offset,
renderContext,
renderTask,
resourcelst;
max_view_width = window.innerWidth;
max_view_height = window.innerHeight;
if (this.settings.zoomLevel === "spread") {
// show second canvas
reader.resourcelst[1].canvas.style.display = "block";
max_view_width /= 2;
// select canvas and ctx based on pageNum, pageShift and oddPageRight
pageShift = 2;
oddPageShift = reader.settings.oddPageRight ? 0 : 1;
index = (pageNum - oddPageShift) % pageShift;
} else {
this.canvaslst[1].style.display = "none";
index = 0;
// hide second canvas
reader.resourcelst[1].canvas.style.display = "none";
// don't try to render non-existing page 0 (which is used
// to indicate the empty left page when oddPageRight === true)
if (pageNum === 0)
pageNum++;
}
resourcelst = reader.resourcelst[index];
canvas = resourcelst.canvas;
ctx = resourcelst.ctx;
textdiv = resourcelst.textdiv;
outputscale = reader.getOutputScale(resourcelst.ctx);
fraction = reader.approximateFraction(outputscale);
if (pageNum <= this.settings.numPages && pageNum >= 1) {
this.pageRendering = true;
if (resourcelst.renderTask) {
resourcelst.renderTask.cancel();
resourcelst.renderTask = null;
}
if (resourcelst.textLayer) {
resourcelst.textLayer.cancel();
resourcelst.textLayer = null;
}
resourcelst.pageNum = pageNum;
if (reader.cancelPage[pageNum])
delete reader.cancelPage[pageNum];
this.book.getPage(pageNum).then(function(page) {
console.log(page);
//console.log(page);
var page_width = page.pageInfo.view[2];
var page_height = page.pageInfo.view[3];
document_aspect = parseFloat(page_width / page_height);
view_aspect = parseFloat(max_view_width / max_view_height);
var scale_height = parseFloat(max_view_height / page_height);
var scale_width = parseFloat(max_view_width / page_width);
var document_aspect = parseFloat(page_width / page_height);
var view_aspect = parseFloat(max_view_width / max_view_height);
scale_height = parseFloat(max_view_height / page_height);
scale_width = parseFloat(max_view_width / page_width);
console.log(max_view_width
+ " " + max_view_height
+ " " + page_width
+ " " + page_height
+ " " + document_aspect
+ " " + view_aspect
+ " " + scale_width
+ " " + scale_height
+ " " + pixelratio);
/*
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
+ " o: " + outputscale);
console.log("fraction: ");
console.log(fraction);
*/
scale = Math.min(scale_width, scale_height) / pixelratio;
switch (reader.settings.zoomLevel) {
if (scale_width < scale_height) {
canvas.width = max_view_width;
canvas.height = parseInt(page_height * scale_width);
canvas.style.width = parseInt(max_view_width / pixelratio);
scale = scale_width;
} else {
canvas.height = max_view_height;
canvas.width = parseInt(page_width * scale_height);
canvas.style.width = parseInt(max_view_width / pixelratio);
scale = scale_height;
case "spread":
// INTENTIONAL FALL-THROUGH
case "fit_page":
$viewer.addClass("flex");
if (scale_width > scale_height) {
scale = scale_height;
canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]);
canvas.width = reader.roundToDivide(parseInt(canvas.height * document_aspect), fraction[0]);
} else {
scale = scale_width;
canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0])
canvas.height = reader.roundToDivide(parseInt(canvas.width / document_aspect), fraction[0]);
}
break;
case "fit_width":
$viewer.removeClass("flex");
if (scale_width < scale_height) {
scale = scale_height;
canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]);
canvas.width = reader.roundToDivide(parseInt(canvas.height * document_aspect), fraction[0]);
} else {
scale = scale_width;
canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0])
canvas.height = reader.roundToDivide(parseInt(canvas.width / document_aspect), fraction[0]);
}
break;
default:
$viewer.removeClass("flex");
scale = parseFloat(reader.settings.zoomLevel);
canvas.width = reader.roundToDivide(parseInt(page_width * scale * outputscale), fraction[0]); ;
canvas.height = reader.roundToDivide(parseInt(page_height * scale * outputscale), fraction[0]);
break;
}
if (document_aspect < view_aspect) {
console.log("document aspect < view aspect, aspect ratio " + document_aspect);
//canvas.height = parseInt(max_view_height / pixelratio);
//canvas.width = parseInt(canvas.height * document_aspect);
//scale = parseFloat(scale_height / pixelratio);
transform = [ 1, 0, 0, 1, parseInt((canvas.width - (page_width*scale)) / 2), 0 ];
//console.log(canvas.width + " " + canvas.height);
transform = (outputscale === 1)
? null
: [outputscale, 0, 0, outputscale, 0, 0];
viewport = page.getViewport(scale);
if (outputscale !== 1) {
canvas.style.width = reader.roundToDivide(viewport.width, fraction[1]) + 'px';
canvas.style.height = reader.roundToDivide(viewport.height, fraction[1]) + 'px';
} else {
console.log("document aspect > view_aspect, aspect ratio " + document_aspect);
//canvas.height = parseInt(max_view_height / pixelratio);
//canvas.width = parseInt(canvas.height * document_aspect);
//canvas.width = parseInt(max_view_width / pixelratio);
//canvas.height = parseInt(canvas.width * document_aspect);
//scale = parseFloat(scale_width / pixelratio);
canvas.style.top = parseInt((max_view_height - canvas.height) / 2);
transform = [ 1, 0, 0, 1, 0, parseInt((canvas.height - (page_height*scale)) / 2) ];
canvas.style.width = "";
canvas.style.height = "";
}
console.log(canvas.width + " " + canvas.height);
var viewport = page.getViewport(scale);
console.log(viewport);
/* textlayer */
if (reader.settings.textSelect) {
textdiv.style.width = reader.roundToDivide(viewport.width, fraction[1]) + 'px';
textdiv.style.height = reader.roundToDivide(viewport.height, fraction[1]) + 'px';
offset = $(canvas).offset();
$(textdiv).offset({
top: offset.top,
left: offset.left
});
page.getTextContent().then(function (textContent) {
resourcelst.textLayer = textLayer = new PDFJS.Reader.TextLayerController({
textLayerDiv: textdiv,
pageIdx: pageNum - 1,
viewport: viewport,
enhanceTextSelection: true
});
textLayer.setTextContent(textContent);
});
} else {
resourcelst.textLayer = textLayer = null;
}
var renderContext = {
canvasContext: ctx,
viewport: viewport
//transform: transform
/* /textLayer */
if (reader.settings.doubleBuffer) {
resourcelst.oscanvas = oscanvas = document.createElement("canvas");
resourcelst.osctx = context = osctx = oscanvas.getContext('2d');
oscanvas.width = canvas.width;
oscanvas.height = canvas.height;
} else {
context = ctx;
}
renderContext = {
canvasContext: context,
viewport: viewport,
transform: transform,
textLayer: textLayer
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function() {
reader.pageRendering = false;
if (reader.pageNumPending !== null) {
reader.renderPage(reader.pageNumPending);
reader.pageNumPending = null;
resourcelst.renderTask = renderTask = page.render(renderContext);
renderTask.promise.then(
function pdfPageRenderCallback (something) {
if (reader.cancelPage[pageNum] === undefined) {
console.log("finished rendering page " + pageNum);
if (reader.settings.doubleBuffer)
ctx.drawImage(oscanvas, 0, 0);
if (textLayer)
textLayer.render(reader.settings.textRenderDelay);
} else {
console.log("rendering page " + pageNum + " finished but cancelled");
}
},
function pdfPageRenderError(error) {
console.log("pdfPageRenderError: " + error);
}
});
);
});
} else {
canvas.width = max_view_width;
canvas.height = max_view_height;
// clear canvas, use maximum size
canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0]);
canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]);
if (outputscale !== 1) {
canvas.style.width = reader.roundToDivide(max_view_width, fraction[1]) + 'px';
canvas.style.height = reader.roundToDivide(max_view_height, fraction[1]) + 'px';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
if (i === 0) {
/*
if (index === 0) {
if (pageNum > 0) {
$page_num.textContent = pageNum.toString();
@ -251,7 +437,7 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
$page_num.textContent = pageNum.toString();
} else {
} else if (pageNum <= reader.settings.numPages) {
var text = $page_num.textContent;
text += "-" + pageNum.toString();
@ -259,40 +445,92 @@ PDFJS.Reader.prototype.renderPage = function(pageNum) {
}
}
*/
};
PDFJS.Reader.prototype.queuePage = function(pageNum) {
PDFJS.Reader.prototype.queuePage = function(page) {
var pageShift = this.settings.sideBySide ? 2 : 1;
//var pageShift = (this.settings.zoomLevel === "spread") ? 2 : 1;
//
var reader = this,
zoom = reader.settings.zoomLevel,
oddPageRight = reader.settings.oddPageRight,
pageShift,
$page_num = document.getElementById('page_num'),
text;
if (this.pageRendering) {
this.pageNumPending = pageNum;
} else {
for (var i = 0; i < pageShift; i++) {
this.renderPage(pageNum + i);
if (zoom === "spread") {
pageShift = 2;
if (oddPageRight === true) {
page -= page % 2;
} else {
page -= (page + 1) % 2;
}
console.log("page: " + page);
if (page >= 0 && page <= reader.settings.numPages) {
if (page === reader.settings.numPages) {
text = page.toString();
} else if (page === 0) {
text = "1";
} else {
text = page.toString() + "-" + parseInt(page+1).toString();
}
$page_num.textContent = text;
}
} else {
pageShift = 1;
if (page >= 1 && page <= reader.settings.numPages)
$page_num.textContent = page.toString();
}
reader.settings.currentPage = page;
if (typeof reader.renderQueue === 'number') {
window.clearTimeout(reader.renderQueue);
reader.renderQueue = false;
}
reader.renderQueue = window.setTimeout(function queuePages() {
for (var i = 0; i < pageShift; i++) {
reader.renderPage(page + i);
}
}, reader.settings.pageRenderDelay);
};
PDFJS.Reader.prototype.prevPage = function() {
var pageShift = this.settings.sideBySide ? 2 : 1;
var reader = this;
var pageShift = (this.settings.zoomLevel === "spread") ? 2 : 1;
var oddPageShift = this.settings.oddPageRight ? 0 : 1;
if (this.settings.currentPage - pageShift < oddPageShift) {
return;
} else {
for (var i = 0; i < pageShift; i++) {
reader.cancelPage[this.settings.currentPage - i] = true;
}
this.queuePage(this.settings.currentPage - pageShift);
}
};
PDFJS.Reader.prototype.nextPage = function() {
var pageShift = this.settings.sideBySide ? 2 : 1;
var reader = this;
var pageShift = (this.settings.zoomLevel === "spread") ? 2 : 1;
if (this.settings.currentPage + pageShift > this.settings.numPages) {
return;
} else {
for (var i = 0; i < pageShift; i++) {
reader.cancelPage[this.settings.currentPage + i] = true;
}
this.queuePage(this.settings.currentPage + pageShift);
}
};
@ -310,3 +548,76 @@ PDFJS.Reader.prototype.defaults = function (obj) {
PDFJS.Reader.prototype.setScale = function (scale) {
};
PDFJS.Reader.prototype.getOutputScale = function (ctx) {
var devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1,
pixelRatio = devicePixelRatio / backingStoreRatio;
return pixelRatio;
};
PDFJS.Reader.prototype.roundToDivide = function (x, div) {
var r = x % div;
return r === 0
? x
: Math.round(x - r + div);
};
/**
* Approximates float number as a fraction using Farey sequence (max order
* of 8).
* @param {number} x - Positive float number.
* @returns {Array} Estimated fraction: the first array item is a numerator,
* the second one is a denominator.
*/
PDFJS.Reader.prototype.approximateFraction = function (x) {
// Fast paths for int numbers or their inversions.
if (Math.floor(x) === x) {
return [x, 1];
}
var xinv = 1 / x;
var limit = 8;
if (xinv > limit) {
return [1, limit];
} else if (Math.floor(xinv) === xinv) {
return [1, xinv];
}
var x_ = x > 1 ? xinv : x;
// a/b and c/d are neighbours in Farey sequence.
var a = 0, b = 1, c = 1, d = 1;
// Limiting search to order 8.
while (true) {
// Generating next term in sequence (order of q).
var p = a + c, q = b + d;
if (q > limit) {
break;
}
if (x_ <= p / q) {
c = p; d = q;
} else {
a = p; b = q;
}
}
var result;
// Select closest of the neighbours to x.
if (x_ - a / b < c / d - x_) {
result = x_ === x ? [a, b] : [b, a];
} else {
result = x_ === x ? [c, d] : [d, c];
}
return result;
};
PDFJS.Reader.prototype.isMobile = function () {
return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
};