Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
oorrja
GitHub Repository: oorrja/learntosolveit
Path: blob/master/source/_static/ViewerJS/ui_utils.js
1237 views
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
396