pdf.js/web/ui_utils.js

674 lines
18 KiB
JavaScript
Raw Normal View History

2013-06-19 01:05:55 +09:00
/* 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.
*/
import { createPromiseCapability } from 'pdfjs-lib';
const CSS_UNITS = 96.0 / 72.0;
const DEFAULT_SCALE_VALUE = 'auto';
const DEFAULT_SCALE = 1.0;
const MIN_SCALE = 0.10;
const MAX_SCALE = 10.0;
const UNKNOWN_SCALE = 0;
const MAX_AUTO_SCALE = 1.25;
const SCROLLBAR_PADDING = 40;
const VERTICAL_PADDING = 5;
const PresentationModeState = {
UNKNOWN: 0,
NORMAL: 1,
CHANGING: 2,
FULLSCREEN: 3,
};
const RendererType = {
CANVAS: 'canvas',
SVG: 'svg',
};
const TextLayerMode = {
DISABLE: 0,
ENABLE: 1,
ENABLE_ENHANCE: 2,
};
// Replaces {{arguments}} with their values.
function formatL10nValue(text, args) {
if (!args) {
return text;
}
return text.replace(/\{\{\s*(\w+)\s*\}\}/g, (all, name) => {
return (name in args ? args[name] : '{{' + name + '}}');
});
}
/**
* No-op implemetation of the localization service.
* @implements {IL10n}
*/
let NullL10n = {
Implement sidebar resizing for modern browsers, by utilizing CSS variables (issue 2072) By making use of modern CSS features, in this case [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables), implementing sidebar resizing is actually quite simple. Not only will the amount of added code be fairly small, but it should also be easy to maintain since there's no need for complicated JavaScript hacks in order to update the CSS. Another benefit is that the JavaScript code doesn't need to make detailed assumptions about the exact structure of the HTML/CSS code. Obviously this will not work in older browsers, such as IE, that lack support for CSS variables. In those cases sidebar resizing is simply disabled (via feature detection), and the resizing DOM element hidden, and the behaviour is thus *identical* to the current (fixed-width) sidebar. However, considering the simplicity of the implementation, I really don't see why limiting this feature to "modern" browsers is a problem. Finally, note that a few edge-cases meant that the patch is a bit larger than what the basic functionality would dictate. Among those is first of all proper RTL support, and secondly (automatic) resizing of the sidebar when the width of the *entire* viewer changes. Another, pre-existing, issue fixed here is the incomplete interface of `NullL10n`. *Please note:* This patch has been successfully tested in both LTR and RTL viewer locales, in recent versions of Firefox and Chrome. Fixes 2072.
2017-10-10 23:16:05 +09:00
getDirection() {
return Promise.resolve('ltr');
},
get(property, args, fallback) {
return Promise.resolve(formatL10nValue(fallback, args));
},
translate(element) {
return Promise.resolve();
Fix inconsistent spacing and trailing commas in objects in `web/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. *Please note:* This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 002dbf29..1de4e530 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -420,8 +420,8 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { setPageLabel: function PDFThumbnailView_setPageLabel(label) { this.pageLabel = (typeof label === 'string' ? label : null); - this.l10n.get('thumb_page_title', { page: this.pageId, }, 'Page {{page}}'). - then((msg) => { + this.l10n.get('thumb_page_title', { page: this.pageId, }, + 'Page {{page}}').then((msg) => { this.anchor.title = msg; }); diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 160e0410..6495fc5e 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -65,7 +65,8 @@ class SecondaryToolbar { { element: options.printButton, eventName: 'print', close: true, }, { element: options.downloadButton, eventName: 'download', close: true, }, { element: options.viewBookmarkButton, eventName: null, close: true, }, - { element: options.firstPageButton, eventName: 'firstpage', close: true, }, + { element: options.firstPageButton, eventName: 'firstpage', + close: true, }, { element: options.lastPageButton, eventName: 'lastpage', close: true, }, { element: options.pageRotateCwButton, eventName: 'rotatecw', close: false, }, @@ -76,7 +77,7 @@ class SecondaryToolbar { { element: options.cursorHandToolButton, eventName: 'switchcursortool', eventDetails: { tool: CursorTool.HAND, }, close: true, }, { element: options.documentPropertiesButton, - eventName: 'documentproperties', close: true, } + eventName: 'documentproperties', close: true, }, ]; this.items = { firstPage: options.firstPageButton, ```
2017-06-01 19:46:12 +09:00
},
};
2013-06-19 01:05:55 +09:00
/**
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
* @return {Object} The object with horizontal (sx) and vertical (sy)
scales. The scaled property is set to false if scaling is
not required, true otherwise.
*/
function getOutputScale(ctx) {
let devicePixelRatio = window.devicePixelRatio || 1;
let backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
let pixelRatio = devicePixelRatio / backingStoreRatio;
2013-06-19 01:05:55 +09:00
return {
sx: pixelRatio,
sy: pixelRatio,
Fix inconsistent spacing and trailing commas in objects in `web/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. *Please note:* This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 002dbf29..1de4e530 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -420,8 +420,8 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { setPageLabel: function PDFThumbnailView_setPageLabel(label) { this.pageLabel = (typeof label === 'string' ? label : null); - this.l10n.get('thumb_page_title', { page: this.pageId, }, 'Page {{page}}'). - then((msg) => { + this.l10n.get('thumb_page_title', { page: this.pageId, }, + 'Page {{page}}').then((msg) => { this.anchor.title = msg; }); diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 160e0410..6495fc5e 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -65,7 +65,8 @@ class SecondaryToolbar { { element: options.printButton, eventName: 'print', close: true, }, { element: options.downloadButton, eventName: 'download', close: true, }, { element: options.viewBookmarkButton, eventName: null, close: true, }, - { element: options.firstPageButton, eventName: 'firstpage', close: true, }, + { element: options.firstPageButton, eventName: 'firstpage', + close: true, }, { element: options.lastPageButton, eventName: 'lastpage', close: true, }, { element: options.pageRotateCwButton, eventName: 'rotatecw', close: false, }, @@ -76,7 +77,7 @@ class SecondaryToolbar { { element: options.cursorHandToolButton, eventName: 'switchcursortool', eventDetails: { tool: CursorTool.HAND, }, close: true, }, { element: options.documentPropertiesButton, - eventName: 'documentproperties', close: true, } + eventName: 'documentproperties', close: true, }, ]; this.items = { firstPage: options.firstPageButton, ```
2017-06-01 19:46:12 +09:00
scaled: pixelRatio !== 1,
2013-06-19 01:05:55 +09:00
};
}
2013-07-13 03:14:13 +09:00
/**
* Scrolls specified element into view of its parent.
* @param {Object} element - The element to be visible.
* @param {Object} spot - An object with optional top and left properties,
* specifying the offset from the top left edge.
* @param {boolean} skipOverflowHiddenElements - Ignore elements that have
* the CSS rule `overflow: hidden;` set. The default is false.
2013-07-13 03:14:13 +09:00
*/
function scrollIntoView(element, spot, skipOverflowHiddenElements = false) {
2013-07-13 03:14:13 +09:00
// Assuming offsetParent is available (it's not available when viewer is in
// hidden iframe or object). We have to scroll: if the offsetParent is not set
// producing the error. See also animationStarted.
let parent = element.offsetParent;
2013-07-13 03:14:13 +09:00
if (!parent) {
console.error('offsetParent is not set -- cannot scroll');
return;
}
let offsetY = element.offsetTop + element.clientTop;
let offsetX = element.offsetLeft + element.clientLeft;
while (parent.clientHeight === parent.scrollHeight ||
(skipOverflowHiddenElements &&
getComputedStyle(parent).overflow === 'hidden')) {
2013-11-25 23:54:47 +09:00
if (parent.dataset._scaleY) {
offsetY /= parent.dataset._scaleY;
offsetX /= parent.dataset._scaleX;
2013-11-25 23:54:47 +09:00
}
2013-07-13 03:14:13 +09:00
offsetY += parent.offsetTop;
offsetX += parent.offsetLeft;
2013-07-13 03:14:13 +09:00
parent = parent.offsetParent;
if (!parent) {
2013-07-13 03:14:13 +09:00
return; // no need to scroll
}
}
if (spot) {
if (spot.top !== undefined) {
offsetY += spot.top;
}
if (spot.left !== undefined) {
offsetX += spot.left;
parent.scrollLeft = offsetX;
}
2013-07-13 03:14:13 +09:00
}
parent.scrollTop = offsetY;
}
/**
* Helper function to start monitoring the scroll event and converting them into
* PDF.js friendly one: with scroll debounce and scroll direction.
*/
function watchScroll(viewAreaElement, callback) {
let debounceScroll = function(evt) {
if (rAF) {
return;
}
// schedule an invocation of scroll for next animation frame.
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
rAF = null;
let currentY = viewAreaElement.scrollTop;
let lastY = state.lastY;
if (currentY !== lastY) {
state.down = currentY > lastY;
}
state.lastY = currentY;
callback(state);
});
};
let state = {
down: true,
lastY: viewAreaElement.scrollTop,
Fix inconsistent spacing and trailing commas in objects in `web/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. *Please note:* This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 002dbf29..1de4e530 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -420,8 +420,8 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { setPageLabel: function PDFThumbnailView_setPageLabel(label) { this.pageLabel = (typeof label === 'string' ? label : null); - this.l10n.get('thumb_page_title', { page: this.pageId, }, 'Page {{page}}'). - then((msg) => { + this.l10n.get('thumb_page_title', { page: this.pageId, }, + 'Page {{page}}').then((msg) => { this.anchor.title = msg; }); diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 160e0410..6495fc5e 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -65,7 +65,8 @@ class SecondaryToolbar { { element: options.printButton, eventName: 'print', close: true, }, { element: options.downloadButton, eventName: 'download', close: true, }, { element: options.viewBookmarkButton, eventName: null, close: true, }, - { element: options.firstPageButton, eventName: 'firstpage', close: true, }, + { element: options.firstPageButton, eventName: 'firstpage', + close: true, }, { element: options.lastPageButton, eventName: 'lastpage', close: true, }, { element: options.pageRotateCwButton, eventName: 'rotatecw', close: false, }, @@ -76,7 +77,7 @@ class SecondaryToolbar { { element: options.cursorHandToolButton, eventName: 'switchcursortool', eventDetails: { tool: CursorTool.HAND, }, close: true, }, { element: options.documentPropertiesButton, - eventName: 'documentproperties', close: true, } + eventName: 'documentproperties', close: true, }, ]; this.items = { firstPage: options.firstPageButton, ```
2017-06-01 19:46:12 +09:00
_eventHandler: debounceScroll,
};
let rAF = null;
viewAreaElement.addEventListener('scroll', debounceScroll, true);
return state;
}
/**
* Helper function to parse query string (e.g. ?param1=value&parm2=...).
*/
function parseQueryString(query) {
let parts = query.split('&');
let params = Object.create(null);
for (let i = 0, ii = parts.length; i < ii; ++i) {
let param = parts[i].split('=');
let key = param[0].toLowerCase();
let value = param.length > 1 ? param[1] : null;
params[decodeURIComponent(key)] = decodeURIComponent(value);
}
return params;
}
/**
* Use binary search to find the index of the first item in a given array which
* passes a given condition. The items are expected to be sorted in the sense
* that if the condition is true for one item in the array, then it is also true
* for all following items.
*
* @returns {Number} Index of the first array element to pass the test,
* or |items.length| if no such element exists.
*/
function binarySearchFirstItem(items, condition) {
let minIndex = 0;
let maxIndex = items.length - 1;
if (items.length === 0 || !condition(items[maxIndex])) {
return items.length;
}
if (condition(items[minIndex])) {
return minIndex;
}
while (minIndex < maxIndex) {
let currentIndex = (minIndex + maxIndex) >> 1;
let currentItem = items[currentIndex];
if (condition(currentItem)) {
maxIndex = currentIndex;
} else {
minIndex = currentIndex + 1;
}
}
return minIndex; /* === maxIndex */
}
/**
* 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.
*/
function approximateFraction(x) {
// Fast paths for int numbers or their inversions.
if (Math.floor(x) === x) {
return [x, 1];
}
let xinv = 1 / x;
let limit = 8;
if (xinv > limit) {
return [1, limit];
} else if (Math.floor(xinv) === xinv) {
return [1, xinv];
}
let x_ = x > 1 ? xinv : x;
// a/b and c/d are neighbours in Farey sequence.
let a = 0, b = 1, c = 1, d = 1;
// Limiting search to order 8.
while (true) {
// Generating next term in sequence (order of q).
let p = a + c, q = b + d;
if (q > limit) {
break;
}
if (x_ <= p / q) {
c = p; d = q;
} else {
a = p; b = q;
}
}
let 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;
}
function roundToDivide(x, div) {
let r = x % div;
return r === 0 ? x : Math.round(x - r + div);
}
/**
* Gets the size of the specified page, converted from PDF units to inches.
* @param {Object} An Object containing the properties: {Array} `view`,
* {number} `userUnit`, and {number} `rotate`.
* @return {Object} An Object containing the properties: {number} `width`
* and {number} `height`, given in inches.
*/
function getPageSizeInches({ view, userUnit, rotate, }) {
const [x1, y1, x2, y2] = view;
// We need to take the page rotation into account as well.
const changeOrientation = rotate % 180 !== 0;
const width = (x2 - x1) / 72 * userUnit;
const height = (y2 - y1) / 72 * userUnit;
return {
width: (changeOrientation ? height : width),
height: (changeOrientation ? width : height),
};
}
/**
* Generic helper to find out what elements are visible within a scroll pane.
*/
function getVisibleElements(scrollEl, views, sortByVisibility = false) {
let top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
let left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
function isElementBottomBelowViewTop(view) {
let element = view.div;
let elementBottom =
element.offsetTop + element.clientTop + element.clientHeight;
return elementBottom > top;
}
let visible = [], view, element;
let currentHeight, viewHeight, hiddenHeight, percentHeight;
let currentWidth, viewWidth;
let firstVisibleElementInd = views.length === 0 ? 0 :
binarySearchFirstItem(views, isElementBottomBelowViewTop);
for (let i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
view = views[i];
element = view.div;
currentHeight = element.offsetTop + element.clientTop;
viewHeight = element.clientHeight;
if (currentHeight > bottom) {
break;
}
currentWidth = element.offsetLeft + element.clientLeft;
viewWidth = element.clientWidth;
if (currentWidth + viewWidth < left || currentWidth > right) {
continue;
}
hiddenHeight = Math.max(0, top - currentHeight) +
Math.max(0, currentHeight + viewHeight - bottom);
percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
visible.push({
id: view.id,
x: currentWidth,
y: currentHeight,
view,
Fix inconsistent spacing and trailing commas in objects in `web/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. *Please note:* This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 002dbf29..1de4e530 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -420,8 +420,8 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { setPageLabel: function PDFThumbnailView_setPageLabel(label) { this.pageLabel = (typeof label === 'string' ? label : null); - this.l10n.get('thumb_page_title', { page: this.pageId, }, 'Page {{page}}'). - then((msg) => { + this.l10n.get('thumb_page_title', { page: this.pageId, }, + 'Page {{page}}').then((msg) => { this.anchor.title = msg; }); diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 160e0410..6495fc5e 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -65,7 +65,8 @@ class SecondaryToolbar { { element: options.printButton, eventName: 'print', close: true, }, { element: options.downloadButton, eventName: 'download', close: true, }, { element: options.viewBookmarkButton, eventName: null, close: true, }, - { element: options.firstPageButton, eventName: 'firstpage', close: true, }, + { element: options.firstPageButton, eventName: 'firstpage', + close: true, }, { element: options.lastPageButton, eventName: 'lastpage', close: true, }, { element: options.pageRotateCwButton, eventName: 'rotatecw', close: false, }, @@ -76,7 +77,7 @@ class SecondaryToolbar { { element: options.cursorHandToolButton, eventName: 'switchcursortool', eventDetails: { tool: CursorTool.HAND, }, close: true, }, { element: options.documentPropertiesButton, - eventName: 'documentproperties', close: true, } + eventName: 'documentproperties', close: true, }, ]; this.items = { firstPage: options.firstPageButton, ```
2017-06-01 19:46:12 +09:00
percent: percentHeight,
});
}
let first = visible[0];
let last = visible[visible.length - 1];
if (sortByVisibility) {
visible.sort(function(a, b) {
let pc = a.percent - b.percent;
if (Math.abs(pc) > 0.001) {
return -pc;
}
return a.id - b.id; // ensure stability
});
}
return { first, last, views: visible, };
}
/**
* Event handler to suppress context menu.
*/
function noContextMenuHandler(evt) {
evt.preventDefault();
}
function isFileSchema(url) {
let i = 0, ii = url.length;
while (i < ii && url[i].trim() === '') {
i++;
}
return url.substr(i, 7).toLowerCase() === 'file://';
}
function isDataSchema(url) {
let i = 0, ii = url.length;
while (i < ii && url[i].trim() === '') {
i++;
}
return url.substr(i, 5).toLowerCase() === 'data:';
}
2013-07-13 03:14:13 +09:00
/**
* Returns the filename or guessed filename from the url (see issue 3455).
* @param {string} url - The original PDF location.
* @param {string} defaultFilename - The value returned if the filename is
* unknown, or the protocol is unsupported.
* @returns {string} Guessed PDF filename.
2013-07-13 03:14:13 +09:00
*/
function getPDFFileNameFromURL(url, defaultFilename = 'document.pdf') {
if (isDataSchema(url)) {
console.warn('getPDFFileNameFromURL: ' +
'ignoring "data:" URL for performance reasons.');
return defaultFilename;
}
const reURI = /^(?:(?:[^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
// SCHEME HOST 1.PATH 2.QUERY 3.REF
2013-07-13 03:14:13 +09:00
// Pattern to get last matching NAME.pdf
const reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
let splitURI = reURI.exec(url);
let suggestedFilename = reFilename.exec(splitURI[1]) ||
reFilename.exec(splitURI[2]) ||
reFilename.exec(splitURI[3]);
2013-07-13 03:14:13 +09:00
if (suggestedFilename) {
suggestedFilename = suggestedFilename[0];
if (suggestedFilename.includes('%')) {
2013-07-13 03:14:13 +09:00
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
try {
suggestedFilename =
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
} catch (ex) { // Possible (extremely rare) errors:
2013-07-13 03:14:13 +09:00
// URIError "Malformed URI", e.g. for "%AA.pdf"
// TypeError "null has no properties", e.g. for "%2F.pdf"
}
}
}
return suggestedFilename || defaultFilename;
2013-07-13 03:14:13 +09:00
}
2013-06-19 01:05:55 +09:00
2016-09-28 05:27:42 +09:00
function normalizeWheelEventDelta(evt) {
let delta = Math.sqrt(evt.deltaX * evt.deltaX + evt.deltaY * evt.deltaY);
let angle = Math.atan2(evt.deltaY, evt.deltaX);
2016-09-28 05:27:42 +09:00
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
// All that is left-up oriented has to change the sign.
delta = -delta;
}
const MOUSE_DOM_DELTA_PIXEL_MODE = 0;
const MOUSE_DOM_DELTA_LINE_MODE = 1;
const MOUSE_PIXELS_PER_LINE = 30;
const MOUSE_LINES_PER_PAGE = 30;
2016-09-28 05:27:42 +09:00
// Converts delta to per-page units
if (evt.deltaMode === MOUSE_DOM_DELTA_PIXEL_MODE) {
delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
} else if (evt.deltaMode === MOUSE_DOM_DELTA_LINE_MODE) {
delta /= MOUSE_LINES_PER_PAGE;
}
return delta;
}
function isValidRotation(angle) {
return Number.isInteger(angle) && angle % 90 === 0;
}
function isPortraitOrientation(size) {
return size.width <= size.height;
}
function cloneObj(obj) {
let result = Object.create(null);
for (let i in obj) {
if (Object.prototype.hasOwnProperty.call(obj, i)) {
result[i] = obj[i];
}
}
return result;
}
const WaitOnType = {
EVENT: 'event',
TIMEOUT: 'timeout',
};
/**
* @typedef {Object} WaitOnEventOrTimeoutParameters
* @property {Object} target - The event target, can for example be:
* `window`, `document`, a DOM element, or an {EventBus} instance.
* @property {string} name - The name of the event.
* @property {number} delay - The delay, in milliseconds, after which the
* timeout occurs (if the event wasn't already dispatched).
*/
/**
* Allows waiting for an event or a timeout, whichever occurs first.
* Can be used to ensure that an action always occurs, even when an event
* arrives late or not at all.
*
* @param {WaitOnEventOrTimeoutParameters}
* @returns {Promise} A promise that is resolved with a {WaitOnType} value.
*/
function waitOnEventOrTimeout({ target, name, delay = 0, }) {
if (typeof target !== 'object' || !(name && typeof name === 'string') ||
!(Number.isInteger(delay) && delay >= 0)) {
return Promise.reject(
new Error('waitOnEventOrTimeout - invalid paramaters.'));
}
let capability = createPromiseCapability();
function handler(type) {
if (target instanceof EventBus) {
target.off(name, eventHandler);
} else {
target.removeEventListener(name, eventHandler);
}
if (timeout) {
clearTimeout(timeout);
}
capability.resolve(type);
}
let eventHandler = handler.bind(null, WaitOnType.EVENT);
if (target instanceof EventBus) {
target.on(name, eventHandler);
} else {
target.addEventListener(name, eventHandler);
}
let timeoutHandler = handler.bind(null, WaitOnType.TIMEOUT);
let timeout = setTimeout(timeoutHandler, delay);
return capability.promise;
}
/**
* Promise that is resolved when DOM window becomes visible.
*/
let animationStarted = new Promise(function (resolve) {
window.requestAnimationFrame(resolve);
});
2016-04-26 07:57:15 +09:00
/**
* Simple event bus for an application. Listeners are attached using the
* `on` and `off` methods. To raise an event, the `dispatch` method shall be
* used.
*/
class EventBus {
constructor() {
2016-04-26 07:57:15 +09:00
this._listeners = Object.create(null);
}
on(eventName, listener) {
let eventListeners = this._listeners[eventName];
if (!eventListeners) {
eventListeners = [];
this._listeners[eventName] = eventListeners;
}
eventListeners.push(listener);
}
2013-06-19 01:05:55 +09:00
off(eventName, listener) {
let eventListeners = this._listeners[eventName];
let i;
if (!eventListeners || ((i = eventListeners.indexOf(listener)) < 0)) {
return;
}
eventListeners.splice(i, 1);
2013-06-19 01:05:55 +09:00
}
dispatch(eventName) {
let eventListeners = this._listeners[eventName];
if (!eventListeners || eventListeners.length === 0) {
return;
}
// Passing all arguments after the eventName to the listeners.
let args = Array.prototype.slice.call(arguments, 1);
// Making copy of the listeners array in case if it will be modified
// during dispatch.
eventListeners.slice(0).forEach(function (listener) {
listener.apply(null, args);
});
}
}
function clamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
class ProgressBar {
constructor(id, { height, width, units, } = {}) {
this.visible = true;
2013-06-19 01:05:55 +09:00
// Fetch the sub-elements for later.
2013-06-19 01:05:55 +09:00
this.div = document.querySelector(id + ' .progress');
// Get the loading bar element, so it can be resized to fit the viewer.
this.bar = this.div.parentNode;
// Get options, with sensible defaults.
this.height = height || 100;
this.width = width || 100;
this.units = units || '%';
2013-06-19 01:05:55 +09:00
// Initialize heights.
2013-06-19 01:05:55 +09:00
this.div.style.height = this.height + this.units;
this.percent = 0;
}
_updateBar() {
if (this._indeterminate) {
this.div.classList.add('indeterminate');
this.div.style.width = this.width + this.units;
return;
}
2013-06-19 01:05:55 +09:00
this.div.classList.remove('indeterminate');
let progressSize = this.width * this._percent / 100;
this.div.style.width = progressSize + this.units;
}
2013-06-19 01:05:55 +09:00
get percent() {
return this._percent;
}
set percent(val) {
this._indeterminate = isNaN(val);
this._percent = clamp(val, 0, 100);
this._updateBar();
}
setWidth(viewer) {
if (!viewer) {
return;
}
let container = viewer.parentNode;
let scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
if (scrollbarWidth > 0) {
this.bar.setAttribute('style', 'width: calc(100% - ' +
scrollbarWidth + 'px);');
}
}
hide() {
if (!this.visible) {
return;
}
this.visible = false;
this.bar.classList.add('hidden');
document.body.classList.remove('loadingInProgress');
}
2013-06-19 01:05:55 +09:00
show() {
if (this.visible) {
return;
}
this.visible = true;
document.body.classList.add('loadingInProgress');
this.bar.classList.remove('hidden');
}
}
export {
CSS_UNITS,
DEFAULT_SCALE_VALUE,
DEFAULT_SCALE,
MIN_SCALE,
MAX_SCALE,
UNKNOWN_SCALE,
MAX_AUTO_SCALE,
SCROLLBAR_PADDING,
VERTICAL_PADDING,
isValidRotation,
isPortraitOrientation,
isFileSchema,
cloneObj,
PresentationModeState,
RendererType,
TextLayerMode,
NullL10n,
EventBus,
ProgressBar,
getPDFFileNameFromURL,
noContextMenuHandler,
parseQueryString,
getVisibleElements,
roundToDivide,
getPageSizeInches,
approximateFraction,
getOutputScale,
scrollIntoView,
watchScroll,
binarySearchFirstItem,
normalizeWheelEventDelta,
animationStarted,
WaitOnType,
waitOnEventOrTimeout,
};