PluginProbe
Ebook Store / 6.25
Ebook Store v6.25
6.25 6.24 6.23 6.22 6.21 6.20 trunk
ebook-store / includes / ViewerJS / ui_utils.js

ui_utils.js in Ebook Store 6.25, at includes/ViewerJS/ui_utils.js

395 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* Copyright 2012 Mozilla Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 'use strict';
18
19 var CSS_UNITS = 96.0 / 72.0;
20 var DEFAULT_SCALE = 'auto';
21 var UNKNOWN_SCALE = 0;
22 var MAX_AUTO_SCALE = 1.25;
23 var SCROLLBAR_PADDING = 40;
24 var VERTICAL_PADDING = 5;
25
26 // optimised CSS custom property getter/setter
27 var CustomStyle = (function CustomStyleClosure() {
28
29 // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
30 // animate-css-transforms-firefox-webkit.html
31 // in some versions of IE9 it is critical that ms appear in this list
32 // before Moz
33 var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
34 var _cache = {};
35
36 function CustomStyle() {}
37
38 CustomStyle.getProp = function get(propName, element) {
39 // check cache only when no element is given
40 if (arguments.length === 1 && typeof _cache[propName] === 'string') {
41 return _cache[propName];
42 }
43
44 element = element || document.documentElement;
45 var style = element.style, prefixed, uPropName;
46
47 // test standard property first
48 if (typeof style[propName] === 'string') {
49 return (_cache[propName] = propName);
50 }
51
52 // capitalize
53 uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
54
55 // test vendor specific properties
56 for (var i = 0, l = prefixes.length; i < l; i++) {
57 prefixed = prefixes[i] + uPropName;
58 if (typeof style[prefixed] === 'string') {
59 return (_cache[propName] = prefixed);
60 }
61 }
62
63 //if all fails then set to undefined
64 return (_cache[propName] = 'undefined');
65 };
66
67 CustomStyle.setProp = function set(propName, element, str) {
68 var prop = this.getProp(propName);
69 if (prop !== 'undefined') {
70 element.style[prop] = str;
71 }
72 };
73
74 return CustomStyle;
75 })();
76
77 function getFileName(url) {
78 var anchor = url.indexOf('#');
79 var query = url.indexOf('?');
80 var end = Math.min(
81 anchor > 0 ? anchor : url.length,
82 query > 0 ? query : url.length);
83 return url.substring(url.lastIndexOf('/', end) + 1, end);
84 }
85
86 /**
87 * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
88 * @return {Object} The object with horizontal (sx) and vertical (sy)
89 scales. The scaled property is set to false if scaling is
90 not required, true otherwise.
91 */
92 function getOutputScale(ctx) {
93 var devicePixelRatio = window.devicePixelRatio || 1;
94 var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
95 ctx.mozBackingStorePixelRatio ||
96 ctx.msBackingStorePixelRatio ||
97 ctx.oBackingStorePixelRatio ||
98 ctx.backingStorePixelRatio || 1;
99 var pixelRatio = devicePixelRatio / backingStoreRatio;
100 return {
101 sx: pixelRatio,
102 sy: pixelRatio,
103 scaled: pixelRatio !== 1
104 };
105 }
106
107 /**
108 * Scrolls specified element into view of its parent.
109 * element {Object} The element to be visible.
110 * spot {Object} An object with optional top and left properties,
111 * specifying the offset from the top left edge.
112 */
113 function scrollIntoView(element, spot) {
114 // Assuming offsetParent is available (it's not available when viewer is in
115 // hidden iframe or object). We have to scroll: if the offsetParent is not set
116 // producing the error. See also animationStartedClosure.
117 var parent = element.offsetParent;
118 var offsetY = element.offsetTop + element.clientTop;
119 var offsetX = element.offsetLeft + element.clientLeft;
120 if (!parent) {
121 console.error('offsetParent is not set -- cannot scroll');
122 return;
123 }
124 while (parent.clientHeight === parent.scrollHeight) {
125 if (parent.dataset._scaleY) {
126 offsetY /= parent.dataset._scaleY;
127 offsetX /= parent.dataset._scaleX;
128 }
129 offsetY += parent.offsetTop;
130 offsetX += parent.offsetLeft;
131 parent = parent.offsetParent;
132 if (!parent) {
133 return; // no need to scroll
134 }
135 }
136 if (spot) {
137 if (spot.top !== undefined) {
138 offsetY += spot.top;
139 }
140 if (spot.left !== undefined) {
141 offsetX += spot.left;
142 parent.scrollLeft = offsetX;
143 }
144 }
145 parent.scrollTop = offsetY;
146 }
147
148 /**
149 * Helper function to start monitoring the scroll event and converting them into
150 * PDF.js friendly one: with scroll debounce and scroll direction.
151 */
152 function watchScroll(viewAreaElement, callback) {
153 var debounceScroll = function debounceScroll(evt) {
154 if (rAF) {
155 return;
156 }
157 // schedule an invocation of scroll for next animation frame.
158 rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
159 rAF = null;
160
161 var currentY = viewAreaElement.scrollTop;
162 var lastY = state.lastY;
163 if (currentY !== lastY) {
164 state.down = currentY > lastY;
165 }
166 state.lastY = currentY;
167 callback(state);
168 });
169 };
170
171 var state = {
172 down: true,
173 lastY: viewAreaElement.scrollTop,
174 _eventHandler: debounceScroll
175 };
176
177 var rAF = null;
178 viewAreaElement.addEventListener('scroll', debounceScroll, true);
179 return state;
180 }
181
182 /**
183 * Use binary search to find the index of the first item in a given array which
184 * passes a given condition. The items are expected to be sorted in the sense
185 * that if the condition is true for one item in the array, then it is also true
186 * for all following items.
187 *
188 * @returns {Number} Index of the first array element to pass the test,
189 * or |items.length| if no such element exists.
190 */
191 function binarySearchFirstItem(items, condition) {
192 var minIndex = 0;
193 var maxIndex = items.length - 1;
194
195 if (items.length === 0 || !condition(items[maxIndex])) {
196 return items.length;
197 }
198 if (condition(items[minIndex])) {
199 return minIndex;
200 }
201
202 while (minIndex < maxIndex) {
203 var currentIndex = (minIndex + maxIndex) >> 1;
204 var currentItem = items[currentIndex];
205 if (condition(currentItem)) {
206 maxIndex = currentIndex;
207 } else {
208 minIndex = currentIndex + 1;
209 }
210 }
211 return minIndex; /* === maxIndex */
212 }
213
214 /**
215 * Generic helper to find out what elements are visible within a scroll pane.
216 */
217 function getVisibleElements(scrollEl, views, sortByVisibility) {
218 var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
219 var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
220
221 function isElementBottomBelowViewTop(view) {
222 var element = view.div;
223 var elementBottom =
224 element.offsetTop + element.clientTop + element.clientHeight;
225 return elementBottom > top;
226 }
227
228 var visible = [], view, element;
229 var currentHeight, viewHeight, hiddenHeight, percentHeight;
230 var currentWidth, viewWidth;
231 var firstVisibleElementInd = (views.length === 0) ? 0 :
232 binarySearchFirstItem(views, isElementBottomBelowViewTop);
233
234 for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
235 view = views[i];
236 element = view.div;
237 currentHeight = element.offsetTop + element.clientTop;
238 viewHeight = element.clientHeight;
239
240 if (currentHeight > bottom) {
241 break;
242 }
243
244 currentWidth = element.offsetLeft + element.clientLeft;
245 viewWidth = element.clientWidth;
246 if (currentWidth + viewWidth < left || currentWidth > right) {
247 continue;
248 }
249 hiddenHeight = Math.max(0, top - currentHeight) +
250 Math.max(0, currentHeight + viewHeight - bottom);
251 percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
252
253 visible.push({
254 id: view.id,
255 x: currentWidth,
256 y: currentHeight,
257 view: view,
258 percent: percentHeight
259 });
260 }
261
262 var first = visible[0];
263 var last = visible[visible.length - 1];
264
265 if (sortByVisibility) {
266 visible.sort(function(a, b) {
267 var pc = a.percent - b.percent;
268 if (Math.abs(pc) > 0.001) {
269 return -pc;
270 }
271 return a.id - b.id; // ensure stability
272 });
273 }
274 return {first: first, last: last, views: visible};
275 }
276
277 /**
278 * Event handler to suppress context menu.
279 */
280 function noContextMenuHandler(e) {
281 e.preventDefault();
282 }
283
284 /**
285 * Returns the filename or guessed filename from the url (see issue 3455).
286 * url {String} The original PDF location.
287 * @return {String} Guessed PDF file name.
288 */
289 function getPDFFileNameFromURL(url) {
290 var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
291 // SCHEME HOST 1.PATH 2.QUERY 3.REF
292 // Pattern to get last matching NAME.pdf
293 var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
294 var splitURI = reURI.exec(url);
295 var suggestedFilename = reFilename.exec(splitURI[1]) ||
296 reFilename.exec(splitURI[2]) ||
297 reFilename.exec(splitURI[3]);
298 if (suggestedFilename) {
299 suggestedFilename = suggestedFilename[0];
300 if (suggestedFilename.indexOf('%') !== -1) {
301 // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
302 try {
303 suggestedFilename =
304 reFilename.exec(decodeURIComponent(suggestedFilename))[0];
305 } catch(e) { // Possible (extremely rare) errors:
306 // URIError "Malformed URI", e.g. for "%AA.pdf"
307 // TypeError "null has no properties", e.g. for "%2F.pdf"
308 }
309 }
310 }
311 return suggestedFilename || 'document.pdf';
312 }
313
314 var ProgressBar = (function ProgressBarClosure() {
315
316 function clamp(v, min, max) {
317 return Math.min(Math.max(v, min), max);
318 }
319
320 function ProgressBar(id, opts) {
321 this.visible = true;
322
323 // Fetch the sub-elements for later.
324 this.div = document.querySelector(id + ' .progress');
325
326 // Get the loading bar element, so it can be resized to fit the viewer.
327 this.bar = this.div.parentNode;
328
329 // Get options, with sensible defaults.
330 this.height = opts.height || 100;
331 this.width = opts.width || 100;
332 this.units = opts.units || '%';
333
334 // Initialize heights.
335 this.div.style.height = this.height + this.units;
336 this.percent = 0;
337 }
338
339 ProgressBar.prototype = {
340
341 updateBar: function ProgressBar_updateBar() {
342 if (this._indeterminate) {
343 this.div.classList.add('indeterminate');
344 this.div.style.width = this.width + this.units;
345 return;
346 }
347
348 this.div.classList.remove('indeterminate');
349 var progressSize = this.width * this._percent / 100;
350 this.div.style.width = progressSize + this.units;
351 },
352
353 get percent() {
354 return this._percent;
355 },
356
357 set percent(val) {
358 this._indeterminate = isNaN(val);
359 this._percent = clamp(val, 0, 100);
360 this.updateBar();
361 },
362
363 setWidth: function ProgressBar_setWidth(viewer) {
364 if (viewer) {
365 var container = viewer.parentNode;
366 var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
367 if (scrollbarWidth > 0) {
368 this.bar.setAttribute('style', 'width: calc(100% - ' +
369 scrollbarWidth + 'px);');
370 }
371 }
372 },
373
374 hide: function ProgressBar_hide() {
375 if (!this.visible) {
376 return;
377 }
378 this.visible = false;
379 this.bar.classList.add('hidden');
380 document.body.classList.remove('loadingInProgress');
381 },
382
383 show: function ProgressBar_show() {
384 if (this.visible) {
385 return;
386 }
387 this.visible = true;
388 document.body.classList.add('loadingInProgress');
389 this.bar.classList.remove('hidden');
390 }
391 };
392
393 return ProgressBar;
394 })();
395