viewerjs-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +65 -0
  6. data/Rakefile +2 -0
  7. data/app/assets/AGPL-3.0.txt +661 -0
  8. data/app/assets/images/kogmbh.png +0 -0
  9. data/app/assets/images/nlnet.png +0 -0
  10. data/app/assets/images/texture.png +0 -0
  11. data/app/assets/images/toolbarButton-download.png +0 -0
  12. data/app/assets/images/toolbarButton-fullscreen.png +0 -0
  13. data/app/assets/images/toolbarButton-menuArrows.png +0 -0
  14. data/app/assets/images/toolbarButton-pageDown.png +0 -0
  15. data/app/assets/images/toolbarButton-pageUp.png +0 -0
  16. data/app/assets/images/toolbarButton-presentation.png +0 -0
  17. data/app/assets/images/toolbarButton-zoomIn.png +0 -0
  18. data/app/assets/images/toolbarButton-zoomOut.png +0 -0
  19. data/app/assets/javascripts/pdf.worker.js +39900 -0
  20. data/app/assets/javascripts/viewerjs/ODFViewerPlugin.js +219 -0
  21. data/app/assets/javascripts/viewerjs/PDFViewerPlugin.js.erb +348 -0
  22. data/app/assets/javascripts/viewerjs/PluginLoader.js +47 -0
  23. data/app/assets/javascripts/viewerjs/compatibility.js +491 -0
  24. data/app/assets/javascripts/viewerjs/pdf.js +7651 -0
  25. data/app/assets/javascripts/viewerjs/pdf_find_bar.js +175 -0
  26. data/app/assets/javascripts/viewerjs/pdf_find_controller.js +355 -0
  27. data/app/assets/javascripts/viewerjs/pdfjsversion.js +1 -0
  28. data/app/assets/javascripts/viewerjs/text_layer_builder.js +385 -0
  29. data/app/assets/javascripts/viewerjs/ui_utils.js +270 -0
  30. data/app/assets/javascripts/viewerjs/viewer.js +230 -0
  31. data/app/assets/javascripts/viewerjs/webodf.js +631 -0
  32. data/app/assets/javascripts/viewerjs_rails.js +22 -0
  33. data/app/assets/stylesheets/ODFViewerPlugin.css.scss +29 -0
  34. data/app/assets/stylesheets/PDFViewerPlugin.css.scss +36 -0
  35. data/app/assets/stylesheets/example.local.css.scss +27 -0
  36. data/app/assets/stylesheets/viewer.css.scss +816 -0
  37. data/app/assets/stylesheets/viewerjs_rails.css +15 -0
  38. data/lib/viewerjs/rails.rb +19 -0
  39. data/lib/viewerjs/rails/version.rb +5 -0
  40. data/lib/viewerjs/view_helpers.rb +75 -0
  41. data/viewerjs-rails.gemspec +23 -0
  42. metadata +113 -0
@@ -0,0 +1 @@
1
+ var /**@const{!string}*/pdfjs_version = "d45d7bc";
@@ -0,0 +1,385 @@
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
+ /* globals CustomStyle, PDFFindController, scrollIntoView */
17
+
18
+ 'use strict';
19
+
20
+ var FIND_SCROLL_OFFSET_TOP = -50;
21
+ var FIND_SCROLL_OFFSET_LEFT = -400;
22
+
23
+ /**
24
+ * TextLayerBuilder provides text-selection
25
+ * functionality for the PDF. It does this
26
+ * by creating overlay divs over the PDF
27
+ * text. This divs contain text that matches
28
+ * the PDF text they are overlaying. This
29
+ * object also provides for a way to highlight
30
+ * text that is being searched for.
31
+ */
32
+ var TextLayerBuilder = function textLayerBuilder(options) {
33
+ var textLayerFrag = document.createDocumentFragment();
34
+
35
+ this.textLayerDiv = options.textLayerDiv;
36
+ this.layoutDone = false;
37
+ this.divContentDone = false;
38
+ this.pageIdx = options.pageIndex;
39
+ this.matches = [];
40
+ this.lastScrollSource = options.lastScrollSource;
41
+ this.viewport = options.viewport;
42
+ this.isViewerInPresentationMode = options.isViewerInPresentationMode;
43
+
44
+ if(typeof PDFFindController === 'undefined') {
45
+ window.PDFFindController = null;
46
+ }
47
+
48
+ if(typeof this.lastScrollSource === 'undefined') {
49
+ this.lastScrollSource = null;
50
+ }
51
+
52
+ this.beginLayout = function textLayerBuilderBeginLayout() {
53
+ this.textDivs = [];
54
+ this.renderingDone = false;
55
+ };
56
+
57
+ this.endLayout = function textLayerBuilderEndLayout() {
58
+ this.layoutDone = true;
59
+ this.insertDivContent();
60
+ };
61
+
62
+ this.renderLayer = function textLayerBuilderRenderLayer() {
63
+ var self = this;
64
+ var textDivs = this.textDivs;
65
+ var bidiTexts = this.textContent;
66
+ var textLayerDiv = this.textLayerDiv;
67
+ var canvas = document.createElement('canvas');
68
+ var ctx = canvas.getContext('2d');
69
+
70
+ // No point in rendering so many divs as it'd make the browser unusable
71
+ // even after the divs are rendered
72
+ var MAX_TEXT_DIVS_TO_RENDER = 100000;
73
+ if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
74
+ return;
75
+
76
+ for (var i = 0, ii = textDivs.length; i < ii; i++) {
77
+ var textDiv = textDivs[i];
78
+ if ('isWhitespace' in textDiv.dataset) {
79
+ continue;
80
+ }
81
+
82
+ ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
83
+ var width = ctx.measureText(textDiv.textContent).width;
84
+
85
+ if (width > 0) {
86
+ textLayerFrag.appendChild(textDiv);
87
+ var textScale = textDiv.dataset.canvasWidth / width;
88
+ var rotation = textDiv.dataset.angle;
89
+ var transform = 'scale(' + textScale + ', 1)';
90
+ transform = 'rotate(' + rotation + 'deg) ' + transform;
91
+ CustomStyle.setProp('transform' , textDiv, transform);
92
+ CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
93
+ }
94
+ }
95
+
96
+ textLayerDiv.appendChild(textLayerFrag);
97
+ this.renderingDone = true;
98
+ this.updateMatches();
99
+ };
100
+
101
+ this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
102
+ // Schedule renderLayout() if user has been scrolling, otherwise
103
+ // run it right away
104
+ var RENDER_DELAY = 200; // in ms
105
+ var self = this;
106
+ var lastScroll = this.lastScrollSource === null ?
107
+ 0 : this.lastScrollSource.lastScroll;
108
+
109
+ if (Date.now() - lastScroll > RENDER_DELAY) {
110
+ // Render right away
111
+ this.renderLayer();
112
+ } else {
113
+ // Schedule
114
+ if (this.renderTimer)
115
+ clearTimeout(this.renderTimer);
116
+ this.renderTimer = setTimeout(function() {
117
+ self.setupRenderLayoutTimer();
118
+ }, RENDER_DELAY);
119
+ }
120
+ };
121
+
122
+ this.appendText = function textLayerBuilderAppendText(geom) {
123
+ var textDiv = document.createElement('div');
124
+
125
+ // vScale and hScale already contain the scaling to pixel units
126
+ var fontHeight = geom.fontSize * Math.abs(geom.vScale);
127
+ textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale);
128
+ textDiv.dataset.fontName = geom.fontName;
129
+ textDiv.dataset.angle = geom.angle * (180 / Math.PI);
130
+
131
+ textDiv.style.fontSize = fontHeight + 'px';
132
+ textDiv.style.fontFamily = geom.fontFamily;
133
+ var fontAscent = geom.ascent ? geom.ascent * fontHeight :
134
+ geom.descent ? (1 + geom.descent) * fontHeight : fontHeight;
135
+ textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px';
136
+ textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px';
137
+
138
+ // The content of the div is set in the `setTextContent` function.
139
+
140
+ this.textDivs.push(textDiv);
141
+ };
142
+
143
+ this.insertDivContent = function textLayerUpdateTextContent() {
144
+ // Only set the content of the divs once layout has finished, the content
145
+ // for the divs is available and content is not yet set on the divs.
146
+ if (!this.layoutDone || this.divContentDone || !this.textContent)
147
+ return;
148
+
149
+ this.divContentDone = true;
150
+
151
+ var textDivs = this.textDivs;
152
+ var bidiTexts = this.textContent;
153
+
154
+ for (var i = 0; i < bidiTexts.length; i++) {
155
+ var bidiText = bidiTexts[i];
156
+ var textDiv = textDivs[i];
157
+ if (!/\S/.test(bidiText.str)) {
158
+ textDiv.dataset.isWhitespace = true;
159
+ continue;
160
+ }
161
+
162
+ textDiv.textContent = bidiText.str;
163
+ // TODO refactor text layer to use text content position
164
+ /**
165
+ * var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y);
166
+ * textDiv.style.left = arr[0] + 'px';
167
+ * textDiv.style.top = arr[1] + 'px';
168
+ */
169
+ // bidiText.dir may be 'ttb' for vertical texts.
170
+ textDiv.dir = bidiText.dir;
171
+ }
172
+
173
+ this.setupRenderLayoutTimer();
174
+ };
175
+
176
+ this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
177
+ this.textContent = textContent;
178
+ this.insertDivContent();
179
+ };
180
+
181
+ this.convertMatches = function textLayerBuilderConvertMatches(matches) {
182
+ var i = 0;
183
+ var iIndex = 0;
184
+ var bidiTexts = this.textContent;
185
+ var end = bidiTexts.length - 1;
186
+ var queryLen = PDFFindController === null ?
187
+ 0 : PDFFindController.state.query.length;
188
+
189
+ var lastDivIdx = -1;
190
+ var pos;
191
+
192
+ var ret = [];
193
+
194
+ // Loop over all the matches.
195
+ for (var m = 0; m < matches.length; m++) {
196
+ var matchIdx = matches[m];
197
+ // # Calculate the begin position.
198
+
199
+ // Loop over the divIdxs.
200
+ while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
201
+ iIndex += bidiTexts[i].str.length;
202
+ i++;
203
+ }
204
+
205
+ // TODO: Do proper handling here if something goes wrong.
206
+ if (i == bidiTexts.length) {
207
+ console.error('Could not find matching mapping');
208
+ }
209
+
210
+ var match = {
211
+ begin: {
212
+ divIdx: i,
213
+ offset: matchIdx - iIndex
214
+ }
215
+ };
216
+
217
+ // # Calculate the end position.
218
+ matchIdx += queryLen;
219
+
220
+ // Somewhat same array as above, but use a > instead of >= to get the end
221
+ // position right.
222
+ while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
223
+ iIndex += bidiTexts[i].str.length;
224
+ i++;
225
+ }
226
+
227
+ match.end = {
228
+ divIdx: i,
229
+ offset: matchIdx - iIndex
230
+ };
231
+ ret.push(match);
232
+ }
233
+
234
+ return ret;
235
+ };
236
+
237
+ this.renderMatches = function textLayerBuilder_renderMatches(matches) {
238
+ // Early exit if there is nothing to render.
239
+ if (matches.length === 0) {
240
+ return;
241
+ }
242
+
243
+ var bidiTexts = this.textContent;
244
+ var textDivs = this.textDivs;
245
+ var prevEnd = null;
246
+ var isSelectedPage = PDFFindController === null ?
247
+ false : (this.pageIdx === PDFFindController.selected.pageIdx);
248
+
249
+ var selectedMatchIdx = PDFFindController === null ?
250
+ -1 : PDFFindController.selected.matchIdx;
251
+
252
+ var highlightAll = PDFFindController === null ?
253
+ false : PDFFindController.state.highlightAll;
254
+
255
+ var infty = {
256
+ divIdx: -1,
257
+ offset: undefined
258
+ };
259
+
260
+ function beginText(begin, className) {
261
+ var divIdx = begin.divIdx;
262
+ var div = textDivs[divIdx];
263
+ div.textContent = '';
264
+
265
+ var content = bidiTexts[divIdx].str.substring(0, begin.offset);
266
+ var node = document.createTextNode(content);
267
+ if (className) {
268
+ var isSelected = isSelectedPage &&
269
+ divIdx === selectedMatchIdx;
270
+ var span = document.createElement('span');
271
+ span.className = className + (isSelected ? ' selected' : '');
272
+ span.appendChild(node);
273
+ div.appendChild(span);
274
+ return;
275
+ }
276
+ div.appendChild(node);
277
+ }
278
+
279
+ function appendText(from, to, className) {
280
+ var divIdx = from.divIdx;
281
+ var div = textDivs[divIdx];
282
+
283
+ var content = bidiTexts[divIdx].str.substring(from.offset, to.offset);
284
+ var node = document.createTextNode(content);
285
+ if (className) {
286
+ var span = document.createElement('span');
287
+ span.className = className;
288
+ span.appendChild(node);
289
+ div.appendChild(span);
290
+ return;
291
+ }
292
+ div.appendChild(node);
293
+ }
294
+
295
+ function highlightDiv(divIdx, className) {
296
+ textDivs[divIdx].className = className;
297
+ }
298
+
299
+ var i0 = selectedMatchIdx, i1 = i0 + 1, i;
300
+
301
+ if (highlightAll) {
302
+ i0 = 0;
303
+ i1 = matches.length;
304
+ } else if (!isSelectedPage) {
305
+ // Not highlighting all and this isn't the selected page, so do nothing.
306
+ return;
307
+ }
308
+
309
+ for (i = i0; i < i1; i++) {
310
+ var match = matches[i];
311
+ var begin = match.begin;
312
+ var end = match.end;
313
+
314
+ var isSelected = isSelectedPage && i === selectedMatchIdx;
315
+ var highlightSuffix = (isSelected ? ' selected' : '');
316
+ if (isSelected && !this.isViewerInPresentationMode) {
317
+ scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP,
318
+ left: FIND_SCROLL_OFFSET_LEFT });
319
+ }
320
+
321
+ // Match inside new div.
322
+ if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
323
+ // If there was a previous div, then add the text at the end
324
+ if (prevEnd !== null) {
325
+ appendText(prevEnd, infty);
326
+ }
327
+ // clears the divs and set the content until the begin point.
328
+ beginText(begin);
329
+ } else {
330
+ appendText(prevEnd, begin);
331
+ }
332
+
333
+ if (begin.divIdx === end.divIdx) {
334
+ appendText(begin, end, 'highlight' + highlightSuffix);
335
+ } else {
336
+ appendText(begin, infty, 'highlight begin' + highlightSuffix);
337
+ for (var n = begin.divIdx + 1; n < end.divIdx; n++) {
338
+ highlightDiv(n, 'highlight middle' + highlightSuffix);
339
+ }
340
+ beginText(end, 'highlight end' + highlightSuffix);
341
+ }
342
+ prevEnd = end;
343
+ }
344
+
345
+ if (prevEnd) {
346
+ appendText(prevEnd, infty);
347
+ }
348
+ };
349
+
350
+ this.updateMatches = function textLayerUpdateMatches() {
351
+ // Only show matches, once all rendering is done.
352
+ if (!this.renderingDone)
353
+ return;
354
+
355
+ // Clear out all matches.
356
+ var matches = this.matches;
357
+ var textDivs = this.textDivs;
358
+ var bidiTexts = this.textContent;
359
+ var clearedUntilDivIdx = -1;
360
+
361
+ // Clear out all current matches.
362
+ for (var i = 0; i < matches.length; i++) {
363
+ var match = matches[i];
364
+ var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
365
+ for (var n = begin; n <= match.end.divIdx; n++) {
366
+ var div = textDivs[n];
367
+ div.textContent = bidiTexts[n].str;
368
+ div.className = '';
369
+ }
370
+ clearedUntilDivIdx = match.end.divIdx + 1;
371
+ }
372
+
373
+ if (PDFFindController === null || !PDFFindController.active)
374
+ return;
375
+
376
+ // Convert the matches on the page controller into the match format used
377
+ // for the textLayer.
378
+ this.matches = matches =
379
+ this.convertMatches(PDFFindController === null ?
380
+ [] : (PDFFindController.pageMatches[this.pageIdx] || []));
381
+
382
+ this.renderMatches(this.matches);
383
+ };
384
+ };
385
+
@@ -0,0 +1,270 @@
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
+ // optimised CSS custom property getter/setter
20
+ var CustomStyle = (function CustomStyleClosure() {
21
+
22
+ // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
23
+ // animate-css-transforms-firefox-webkit.html
24
+ // in some versions of IE9 it is critical that ms appear in this list
25
+ // before Moz
26
+ var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
27
+ var _cache = { };
28
+
29
+ function CustomStyle() {
30
+ }
31
+
32
+ CustomStyle.getProp = function get(propName, element) {
33
+ // check cache only when no element is given
34
+ if (arguments.length == 1 && typeof _cache[propName] == 'string') {
35
+ return _cache[propName];
36
+ }
37
+
38
+ element = element || document.documentElement;
39
+ var style = element.style, prefixed, uPropName;
40
+
41
+ // test standard property first
42
+ if (typeof style[propName] == 'string') {
43
+ return (_cache[propName] = propName);
44
+ }
45
+
46
+ // capitalize
47
+ uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
48
+
49
+ // test vendor specific properties
50
+ for (var i = 0, l = prefixes.length; i < l; i++) {
51
+ prefixed = prefixes[i] + uPropName;
52
+ if (typeof style[prefixed] == 'string') {
53
+ return (_cache[propName] = prefixed);
54
+ }
55
+ }
56
+
57
+ //if all fails then set to undefined
58
+ return (_cache[propName] = 'undefined');
59
+ };
60
+
61
+ CustomStyle.setProp = function set(propName, element, str) {
62
+ var prop = this.getProp(propName);
63
+ if (prop != 'undefined')
64
+ element.style[prop] = str;
65
+ };
66
+
67
+ return CustomStyle;
68
+ })();
69
+
70
+ function getFileName(url) {
71
+ var anchor = url.indexOf('#');
72
+ var query = url.indexOf('?');
73
+ var end = Math.min(
74
+ anchor > 0 ? anchor : url.length,
75
+ query > 0 ? query : url.length);
76
+ return url.substring(url.lastIndexOf('/', end) + 1, end);
77
+ }
78
+
79
+ /**
80
+ * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
81
+ * @return {Object} The object with horizontal (sx) and vertical (sy)
82
+ scales. The scaled property is set to false if scaling is
83
+ not required, true otherwise.
84
+ */
85
+ function getOutputScale(ctx) {
86
+ var devicePixelRatio = window.devicePixelRatio || 1;
87
+ var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
88
+ ctx.mozBackingStorePixelRatio ||
89
+ ctx.msBackingStorePixelRatio ||
90
+ ctx.oBackingStorePixelRatio ||
91
+ ctx.backingStorePixelRatio || 1;
92
+ var pixelRatio = devicePixelRatio / backingStoreRatio;
93
+ return {
94
+ sx: pixelRatio,
95
+ sy: pixelRatio,
96
+ scaled: pixelRatio != 1
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Scrolls specified element into view of its parent.
102
+ * element {Object} The element to be visible.
103
+ * spot {Object} An object with optional top and left properties,
104
+ * specifying the offset from the top left edge.
105
+ */
106
+ function scrollIntoView(element, spot) {
107
+ // Assuming offsetParent is available (it's not available when viewer is in
108
+ // hidden iframe or object). We have to scroll: if the offsetParent is not set
109
+ // producing the error. See also animationStartedClosure.
110
+ var parent = element.offsetParent;
111
+ var offsetY = element.offsetTop + element.clientTop;
112
+ var offsetX = element.offsetLeft + element.clientLeft;
113
+ if (!parent) {
114
+ console.error('offsetParent is not set -- cannot scroll');
115
+ return;
116
+ }
117
+ while (parent.clientHeight === parent.scrollHeight) {
118
+ if (parent.dataset._scaleY) {
119
+ offsetY /= parent.dataset._scaleY;
120
+ offsetX /= parent.dataset._scaleX;
121
+ }
122
+ offsetY += parent.offsetTop;
123
+ offsetX += parent.offsetLeft;
124
+ parent = parent.offsetParent;
125
+ if (!parent) {
126
+ return; // no need to scroll
127
+ }
128
+ }
129
+ if (spot) {
130
+ if (spot.top !== undefined) {
131
+ offsetY += spot.top;
132
+ }
133
+ if (spot.left !== undefined) {
134
+ offsetX += spot.left;
135
+ parent.scrollLeft = offsetX;
136
+ }
137
+ }
138
+ parent.scrollTop = offsetY;
139
+ }
140
+
141
+ /**
142
+ * Event handler to suppress context menu.
143
+ */
144
+ function noContextMenuHandler(e) {
145
+ e.preventDefault();
146
+ }
147
+
148
+ /**
149
+ * Returns the filename or guessed filename from the url (see issue 3455).
150
+ * url {String} The original PDF location.
151
+ * @return {String} Guessed PDF file name.
152
+ */
153
+ function getPDFFileNameFromURL(url) {
154
+ var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
155
+ // SCHEME HOST 1.PATH 2.QUERY 3.REF
156
+ // Pattern to get last matching NAME.pdf
157
+ var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
158
+ var splitURI = reURI.exec(url);
159
+ var suggestedFilename = reFilename.exec(splitURI[1]) ||
160
+ reFilename.exec(splitURI[2]) ||
161
+ reFilename.exec(splitURI[3]);
162
+ if (suggestedFilename) {
163
+ suggestedFilename = suggestedFilename[0];
164
+ if (suggestedFilename.indexOf('%') != -1) {
165
+ // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
166
+ try {
167
+ suggestedFilename =
168
+ reFilename.exec(decodeURIComponent(suggestedFilename))[0];
169
+ } catch(e) { // Possible (extremely rare) errors:
170
+ // URIError "Malformed URI", e.g. for "%AA.pdf"
171
+ // TypeError "null has no properties", e.g. for "%2F.pdf"
172
+ }
173
+ }
174
+ }
175
+ return suggestedFilename || 'document.pdf';
176
+ }
177
+
178
+ var ProgressBar = (function ProgressBarClosure() {
179
+
180
+ function clamp(v, min, max) {
181
+ return Math.min(Math.max(v, min), max);
182
+ }
183
+
184
+ function ProgressBar(id, opts) {
185
+
186
+ // Fetch the sub-elements for later.
187
+ this.div = document.querySelector(id + ' .progress');
188
+
189
+ // Get the loading bar element, so it can be resized to fit the viewer.
190
+ this.bar = this.div.parentNode;
191
+
192
+ // Get options, with sensible defaults.
193
+ this.height = opts.height || 100;
194
+ this.width = opts.width || 100;
195
+ this.units = opts.units || '%';
196
+
197
+ // Initialize heights.
198
+ this.div.style.height = this.height + this.units;
199
+ this.percent = 0;
200
+ }
201
+
202
+ ProgressBar.prototype = {
203
+
204
+ updateBar: function ProgressBar_updateBar() {
205
+ if (this._indeterminate) {
206
+ this.div.classList.add('indeterminate');
207
+ this.div.style.width = this.width + this.units;
208
+ return;
209
+ }
210
+
211
+ this.div.classList.remove('indeterminate');
212
+ var progressSize = this.width * this._percent / 100;
213
+ this.div.style.width = progressSize + this.units;
214
+ },
215
+
216
+ get percent() {
217
+ return this._percent;
218
+ },
219
+
220
+ set percent(val) {
221
+ this._indeterminate = isNaN(val);
222
+ this._percent = clamp(val, 0, 100);
223
+ this.updateBar();
224
+ },
225
+
226
+ setWidth: function ProgressBar_setWidth(viewer) {
227
+ if (viewer) {
228
+ var container = viewer.parentNode;
229
+ var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
230
+ if (scrollbarWidth > 0) {
231
+ this.bar.setAttribute('style', 'width: calc(100% - ' +
232
+ scrollbarWidth + 'px);');
233
+ }
234
+ }
235
+ },
236
+
237
+ hide: function ProgressBar_hide() {
238
+ this.bar.classList.add('hidden');
239
+ this.bar.removeAttribute('style');
240
+ }
241
+ };
242
+
243
+ return ProgressBar;
244
+ })();
245
+
246
+ var Cache = function cacheCache(size) {
247
+ var data = [];
248
+ this.push = function cachePush(view) {
249
+ var i = data.indexOf(view);
250
+ if (i >= 0)
251
+ data.splice(i);
252
+ data.push(view);
253
+ if (data.length > size)
254
+ data.shift().destroy();
255
+ };
256
+ };
257
+
258
+ //#if !(FIREFOX || MOZCENTRAL || B2G)
259
+ var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() {
260
+ // Feature test as per http://diveintohtml5.info/storage.html
261
+ // The additional localStorage call is to get around a FF quirk, see
262
+ // bug #495747 in bugzilla
263
+ try {
264
+ return ('localStorage' in window && window['localStorage'] !== null &&
265
+ localStorage);
266
+ } catch (e) {
267
+ return false;
268
+ }
269
+ })();
270
+ //#endif