W.I.P. of implementing OpenOffice documents viewer
577
ViewerJS/compatibility.js
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
/* globals VBArray, PDFJS */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Initializing PDFJS global object here, it case if we need to change/disable
|
||||||
|
// some PDF.js features, e.g. range requests
|
||||||
|
if (typeof PDFJS === 'undefined') {
|
||||||
|
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking if the typed arrays are supported
|
||||||
|
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
|
||||||
|
(function checkTypedArrayCompatibility() {
|
||||||
|
if (typeof Uint8Array !== 'undefined') {
|
||||||
|
// Support: iOS<6.0
|
||||||
|
if (typeof Uint8Array.prototype.subarray === 'undefined') {
|
||||||
|
Uint8Array.prototype.subarray = function subarray(start, end) {
|
||||||
|
return new Uint8Array(this.slice(start, end));
|
||||||
|
};
|
||||||
|
Float32Array.prototype.subarray = function subarray(start, end) {
|
||||||
|
return new Float32Array(this.slice(start, end));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support: Android<4.1
|
||||||
|
if (typeof Float64Array === 'undefined') {
|
||||||
|
window.Float64Array = Float32Array;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function subarray(start, end) {
|
||||||
|
return new TypedArray(this.slice(start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setArrayOffset(array, offset) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
for (var i = 0, n = array.length; i < n; ++i, ++offset) {
|
||||||
|
this[offset] = array[i] & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TypedArray(arg1) {
|
||||||
|
var result, i, n;
|
||||||
|
if (typeof arg1 === 'number') {
|
||||||
|
result = [];
|
||||||
|
for (i = 0; i < arg1; ++i) {
|
||||||
|
result[i] = 0;
|
||||||
|
}
|
||||||
|
} else if ('slice' in arg1) {
|
||||||
|
result = arg1.slice(0);
|
||||||
|
} else {
|
||||||
|
result = [];
|
||||||
|
for (i = 0, n = arg1.length; i < n; ++i) {
|
||||||
|
result[i] = arg1[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.subarray = subarray;
|
||||||
|
result.buffer = result;
|
||||||
|
result.byteLength = result.length;
|
||||||
|
result.set = setArrayOffset;
|
||||||
|
|
||||||
|
if (typeof arg1 === 'object' && arg1.buffer) {
|
||||||
|
result.buffer = arg1.buffer;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Uint8Array = TypedArray;
|
||||||
|
window.Int8Array = TypedArray;
|
||||||
|
|
||||||
|
// we don't need support for set, byteLength for 32-bit array
|
||||||
|
// so we can use the TypedArray as well
|
||||||
|
window.Uint32Array = TypedArray;
|
||||||
|
window.Int32Array = TypedArray;
|
||||||
|
window.Uint16Array = TypedArray;
|
||||||
|
window.Float32Array = TypedArray;
|
||||||
|
window.Float64Array = TypedArray;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// URL = URL || webkitURL
|
||||||
|
// Support: Safari<7, Android 4.2+
|
||||||
|
(function normalizeURLObject() {
|
||||||
|
if (!window.URL) {
|
||||||
|
window.URL = window.webkitURL;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Object.defineProperty()?
|
||||||
|
// Support: Android<4.0, Safari<5.1
|
||||||
|
(function checkObjectDefinePropertyCompatibility() {
|
||||||
|
if (typeof Object.defineProperty !== 'undefined') {
|
||||||
|
var definePropertyPossible = true;
|
||||||
|
try {
|
||||||
|
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||||
|
// and thus the native version is not sufficient
|
||||||
|
Object.defineProperty(new Image(), 'id', { value: 'test' });
|
||||||
|
// ... another test for android gb browser for non-DOM objects
|
||||||
|
var Test = function Test() {};
|
||||||
|
Test.prototype = { get id() { } };
|
||||||
|
Object.defineProperty(new Test(), 'id',
|
||||||
|
{ value: '', configurable: true, enumerable: true, writable: false });
|
||||||
|
} catch (e) {
|
||||||
|
definePropertyPossible = false;
|
||||||
|
}
|
||||||
|
if (definePropertyPossible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty = function objectDefineProperty(obj, name, def) {
|
||||||
|
delete obj[name];
|
||||||
|
if ('get' in def) {
|
||||||
|
obj.__defineGetter__(name, def['get']);
|
||||||
|
}
|
||||||
|
if ('set' in def) {
|
||||||
|
obj.__defineSetter__(name, def['set']);
|
||||||
|
}
|
||||||
|
if ('value' in def) {
|
||||||
|
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
|
||||||
|
this.__defineGetter__(name, function objectDefinePropertyGetter() {
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
obj[name] = def.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// No XMLHttpRequest#response?
|
||||||
|
// Support: IE<11, Android <4.0
|
||||||
|
(function checkXMLHttpRequestResponseCompatibility() {
|
||||||
|
var xhrPrototype = XMLHttpRequest.prototype;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
if (!('overrideMimeType' in xhr)) {
|
||||||
|
// IE10 might have response, but not overrideMimeType
|
||||||
|
// Support: IE10
|
||||||
|
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||||
|
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ('responseType' in xhr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The worker will be using XHR, so we can save time and disable worker.
|
||||||
|
PDFJS.disableWorker = true;
|
||||||
|
|
||||||
|
Object.defineProperty(xhrPrototype, 'responseType', {
|
||||||
|
get: function xmlHttpRequestGetResponseType() {
|
||||||
|
return this._responseType || 'text';
|
||||||
|
},
|
||||||
|
set: function xmlHttpRequestSetResponseType(value) {
|
||||||
|
if (value === 'text' || value === 'arraybuffer') {
|
||||||
|
this._responseType = value;
|
||||||
|
if (value === 'arraybuffer' &&
|
||||||
|
typeof this.overrideMimeType === 'function') {
|
||||||
|
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Support: IE9
|
||||||
|
if (typeof VBArray !== 'undefined') {
|
||||||
|
Object.defineProperty(xhrPrototype, 'response', {
|
||||||
|
get: function xmlHttpRequestResponseGet() {
|
||||||
|
if (this.responseType === 'arraybuffer') {
|
||||||
|
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||||
|
} else {
|
||||||
|
return this.responseText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(xhrPrototype, 'response', {
|
||||||
|
get: function xmlHttpRequestResponseGet() {
|
||||||
|
if (this.responseType !== 'arraybuffer') {
|
||||||
|
return this.responseText;
|
||||||
|
}
|
||||||
|
var text = this.responseText;
|
||||||
|
var i, n = text.length;
|
||||||
|
var result = new Uint8Array(n);
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
result[i] = text.charCodeAt(i) & 0xFF;
|
||||||
|
}
|
||||||
|
return result.buffer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// window.btoa (base64 encode function) ?
|
||||||
|
// Support: IE<10
|
||||||
|
(function checkWindowBtoaCompatibility() {
|
||||||
|
if ('btoa' in window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var digits =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||||
|
|
||||||
|
window.btoa = function windowBtoa(chars) {
|
||||||
|
var buffer = '';
|
||||||
|
var i, n;
|
||||||
|
for (i = 0, n = chars.length; i < n; i += 3) {
|
||||||
|
var b1 = chars.charCodeAt(i) & 0xFF;
|
||||||
|
var b2 = chars.charCodeAt(i + 1) & 0xFF;
|
||||||
|
var b3 = chars.charCodeAt(i + 2) & 0xFF;
|
||||||
|
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
|
||||||
|
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
|
||||||
|
var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
|
||||||
|
buffer += (digits.charAt(d1) + digits.charAt(d2) +
|
||||||
|
digits.charAt(d3) + digits.charAt(d4));
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// window.atob (base64 encode function)?
|
||||||
|
// Support: IE<10
|
||||||
|
(function checkWindowAtobCompatibility() {
|
||||||
|
if ('atob' in window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/davidchambers/Base64.js
|
||||||
|
var digits =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||||
|
window.atob = function (input) {
|
||||||
|
input = input.replace(/=+$/, '');
|
||||||
|
if (input.length % 4 === 1) {
|
||||||
|
throw new Error('bad atob input');
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
// initialize result and counters
|
||||||
|
var bc = 0, bs, buffer, idx = 0, output = '';
|
||||||
|
// get next character
|
||||||
|
buffer = input.charAt(idx++);
|
||||||
|
// character found in table?
|
||||||
|
// initialize bit storage and add its ascii value
|
||||||
|
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
|
||||||
|
// and if not first of each 4 characters,
|
||||||
|
// convert the first 8 bits to one ascii character
|
||||||
|
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
|
||||||
|
) {
|
||||||
|
// try to find character in table (0-63, not found => -1)
|
||||||
|
buffer = digits.indexOf(buffer);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Function.prototype.bind?
|
||||||
|
// Support: Android<4.0, iOS<6.0
|
||||||
|
(function checkFunctionPrototypeBindCompatibility() {
|
||||||
|
if (typeof Function.prototype.bind !== 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function.prototype.bind = function functionPrototypeBind(obj) {
|
||||||
|
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
|
||||||
|
var bound = function functionPrototypeBindBound() {
|
||||||
|
var args = headArgs.concat(Array.prototype.slice.call(arguments));
|
||||||
|
return fn.apply(obj, args);
|
||||||
|
};
|
||||||
|
return bound;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// HTMLElement dataset property
|
||||||
|
// Support: IE<11, Safari<5.1, Android<4.0
|
||||||
|
(function checkDatasetProperty() {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
if ('dataset' in div) {
|
||||||
|
return; // dataset property exists
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(HTMLElement.prototype, 'dataset', {
|
||||||
|
get: function() {
|
||||||
|
if (this._dataset) {
|
||||||
|
return this._dataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataset = {};
|
||||||
|
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
|
||||||
|
var attribute = this.attributes[j];
|
||||||
|
if (attribute.name.substring(0, 5) !== 'data-') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
|
||||||
|
function(all, ch) {
|
||||||
|
return ch.toUpperCase();
|
||||||
|
});
|
||||||
|
dataset[key] = attribute.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(this, '_dataset', {
|
||||||
|
value: dataset,
|
||||||
|
writable: false,
|
||||||
|
enumerable: false
|
||||||
|
});
|
||||||
|
return dataset;
|
||||||
|
},
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// HTMLElement classList property
|
||||||
|
// Support: IE<10, Android<4.0, iOS<5.0
|
||||||
|
(function checkClassListProperty() {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
if ('classList' in div) {
|
||||||
|
return; // classList property exists
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeList(element, itemName, add, remove) {
|
||||||
|
var s = element.className || '';
|
||||||
|
var list = s.split(/\s+/g);
|
||||||
|
if (list[0] === '') {
|
||||||
|
list.shift();
|
||||||
|
}
|
||||||
|
var index = list.indexOf(itemName);
|
||||||
|
if (index < 0 && add) {
|
||||||
|
list.push(itemName);
|
||||||
|
}
|
||||||
|
if (index >= 0 && remove) {
|
||||||
|
list.splice(index, 1);
|
||||||
|
}
|
||||||
|
element.className = list.join(' ');
|
||||||
|
return (index >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var classListPrototype = {
|
||||||
|
add: function(name) {
|
||||||
|
changeList(this.element, name, true, false);
|
||||||
|
},
|
||||||
|
contains: function(name) {
|
||||||
|
return changeList(this.element, name, false, false);
|
||||||
|
},
|
||||||
|
remove: function(name) {
|
||||||
|
changeList(this.element, name, false, true);
|
||||||
|
},
|
||||||
|
toggle: function(name) {
|
||||||
|
changeList(this.element, name, true, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(HTMLElement.prototype, 'classList', {
|
||||||
|
get: function() {
|
||||||
|
if (this._classList) {
|
||||||
|
return this._classList;
|
||||||
|
}
|
||||||
|
|
||||||
|
var classList = Object.create(classListPrototype, {
|
||||||
|
element: {
|
||||||
|
value: this,
|
||||||
|
writable: false,
|
||||||
|
enumerable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, '_classList', {
|
||||||
|
value: classList,
|
||||||
|
writable: false,
|
||||||
|
enumerable: false
|
||||||
|
});
|
||||||
|
return classList;
|
||||||
|
},
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Check console compatibility
|
||||||
|
// In older IE versions the console object is not available
|
||||||
|
// unless console is open.
|
||||||
|
// Support: IE<10
|
||||||
|
(function checkConsoleCompatibility() {
|
||||||
|
if (!('console' in window)) {
|
||||||
|
window.console = {
|
||||||
|
log: function() {},
|
||||||
|
error: function() {},
|
||||||
|
warn: function() {}
|
||||||
|
};
|
||||||
|
} else if (!('bind' in console.log)) {
|
||||||
|
// native functions in IE9 might not have bind
|
||||||
|
console.log = (function(fn) {
|
||||||
|
return function(msg) { return fn(msg); };
|
||||||
|
})(console.log);
|
||||||
|
console.error = (function(fn) {
|
||||||
|
return function(msg) { return fn(msg); };
|
||||||
|
})(console.error);
|
||||||
|
console.warn = (function(fn) {
|
||||||
|
return function(msg) { return fn(msg); };
|
||||||
|
})(console.warn);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Check onclick compatibility in Opera
|
||||||
|
// Support: Opera<15
|
||||||
|
(function checkOnClickCompatibility() {
|
||||||
|
// workaround for reported Opera bug DSK-354448:
|
||||||
|
// onclick fires on disabled buttons with opaque content
|
||||||
|
function ignoreIfTargetDisabled(event) {
|
||||||
|
if (isDisabled(event.target)) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isDisabled(node) {
|
||||||
|
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
|
||||||
|
}
|
||||||
|
if (navigator.userAgent.indexOf('Opera') !== -1) {
|
||||||
|
// use browser detection since we cannot feature-check this bug
|
||||||
|
document.addEventListener('click', ignoreIfTargetDisabled, true);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Checks if possible to use URL.createObjectURL()
|
||||||
|
// Support: IE
|
||||||
|
(function checkOnBlobSupport() {
|
||||||
|
// sometimes IE loosing the data created with createObjectURL(), see #3977
|
||||||
|
if (navigator.userAgent.indexOf('Trident') >= 0) {
|
||||||
|
PDFJS.disableCreateObjectURL = true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Checks if navigator.language is supported
|
||||||
|
(function checkNavigatorLanguage() {
|
||||||
|
if ('language' in navigator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PDFJS.locale = navigator.userLanguage || 'en-US';
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function checkRangeRequests() {
|
||||||
|
// Safari has issues with cached range requests see:
|
||||||
|
// https://github.com/mozilla/pdf.js/issues/3260
|
||||||
|
// Last tested with version 6.0.4.
|
||||||
|
// Support: Safari 6.0+
|
||||||
|
var isSafari = Object.prototype.toString.call(
|
||||||
|
window.HTMLElement).indexOf('Constructor') > 0;
|
||||||
|
|
||||||
|
// Older versions of Android (pre 3.0) has issues with range requests, see:
|
||||||
|
// https://github.com/mozilla/pdf.js/issues/3381.
|
||||||
|
// Make sure that we only match webkit-based Android browsers,
|
||||||
|
// since Firefox/Fennec works as expected.
|
||||||
|
// Support: Android<3.0
|
||||||
|
var regex = /Android\s[0-2][^\d]/;
|
||||||
|
var isOldAndroid = regex.test(navigator.userAgent);
|
||||||
|
|
||||||
|
// Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
|
||||||
|
var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent);
|
||||||
|
|
||||||
|
if (isSafari || isOldAndroid || isChromeWithRangeBug) {
|
||||||
|
PDFJS.disableRange = true;
|
||||||
|
PDFJS.disableStream = true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Check if the browser supports manipulation of the history.
|
||||||
|
// Support: IE<10, Android<4.2
|
||||||
|
(function checkHistoryManipulation() {
|
||||||
|
// Android 2.x has so buggy pushState support that it was removed in
|
||||||
|
// Android 3.0 and restored as late as in Android 4.2.
|
||||||
|
// Support: Android 2.x
|
||||||
|
if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
|
||||||
|
PDFJS.disableHistory = true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Support: IE<11, Chrome<21, Android<4.4, Safari<6
|
||||||
|
(function checkSetPresenceInImageData() {
|
||||||
|
// IE < 11 will use window.CanvasPixelArray which lacks set function.
|
||||||
|
if (window.CanvasPixelArray) {
|
||||||
|
if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
|
||||||
|
window.CanvasPixelArray.prototype.set = function(arr) {
|
||||||
|
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||||
|
this[i] = arr[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
|
||||||
|
// Because we cannot feature detect it, we rely on user agent parsing.
|
||||||
|
var polyfill = false, versionMatch;
|
||||||
|
if (navigator.userAgent.indexOf('Chrom') >= 0) {
|
||||||
|
versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||||
|
// Chrome < 21 lacks the set function.
|
||||||
|
polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
|
||||||
|
} else if (navigator.userAgent.indexOf('Android') >= 0) {
|
||||||
|
// Android < 4.4 lacks the set function.
|
||||||
|
// Android >= 4.4 will contain Chrome in the user agent,
|
||||||
|
// thus pass the Chrome check above and not reach this block.
|
||||||
|
polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
|
||||||
|
} else if (navigator.userAgent.indexOf('Safari') >= 0) {
|
||||||
|
versionMatch = navigator.userAgent.
|
||||||
|
match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
|
||||||
|
// Safari < 6 lacks the set function.
|
||||||
|
polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (polyfill) {
|
||||||
|
var contextPrototype = window.CanvasRenderingContext2D.prototype;
|
||||||
|
contextPrototype._createImageData = contextPrototype.createImageData;
|
||||||
|
contextPrototype.createImageData = function(w, h) {
|
||||||
|
var imageData = this._createImageData(w, h);
|
||||||
|
imageData.data.set = function(arr) {
|
||||||
|
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||||
|
this[i] = arr[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return imageData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Support: IE<10, Android<4.0, iOS
|
||||||
|
(function checkRequestAnimationFrame() {
|
||||||
|
function fakeRequestAnimationFrame(callback) {
|
||||||
|
window.setTimeout(callback, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||||
|
if (isIOS) {
|
||||||
|
// requestAnimationFrame on iOS is broken, replacing with fake one.
|
||||||
|
window.requestAnimationFrame = fakeRequestAnimationFrame;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('requestAnimationFrame' in window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame =
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
fakeRequestAnimationFrame;
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function checkCanvasSizeLimitation() {
|
||||||
|
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||||
|
var isAndroid = /Android/g.test(navigator.userAgent);
|
||||||
|
if (isIOS || isAndroid) {
|
||||||
|
// 5MP
|
||||||
|
PDFJS.maxCanvasPixels = 5242880;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Disable fullscreen support for certain problematic configurations.
|
||||||
|
// Support: IE11+ (when embedded).
|
||||||
|
(function checkFullscreenSupport() {
|
||||||
|
var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 &&
|
||||||
|
window.parent !== window);
|
||||||
|
if (isEmbeddedIE) {
|
||||||
|
PDFJS.disableFullscreen = true;
|
||||||
|
}
|
||||||
|
})();
|
27
ViewerJS/example.local.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* This is just a sample file with CSS rules. You should write your own @font-face declarations
|
||||||
|
* to add support for your desired fonts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Novecentowide Book';
|
||||||
|
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot");
|
||||||
|
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"),
|
||||||
|
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"),
|
||||||
|
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"),
|
||||||
|
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'exotica';
|
||||||
|
src: url('/ViewerJS/fonts/Exotica-webfont.eot');
|
||||||
|
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
BIN
ViewerJS/images/kogmbh.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
ViewerJS/images/nlnet.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
ViewerJS/images/texture.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
ViewerJS/images/toolbarButton-download.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
ViewerJS/images/toolbarButton-fullscreen.png
Normal file
After Width: | Height: | Size: 491 B |
BIN
ViewerJS/images/toolbarButton-menuArrows.png
Normal file
After Width: | Height: | Size: 237 B |
BIN
ViewerJS/images/toolbarButton-pageDown.png
Normal file
After Width: | Height: | Size: 353 B |
BIN
ViewerJS/images/toolbarButton-pageUp.png
Normal file
After Width: | Height: | Size: 344 B |
BIN
ViewerJS/images/toolbarButton-presentation.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
ViewerJS/images/toolbarButton-zoomIn.png
Normal file
After Width: | Height: | Size: 228 B |
BIN
ViewerJS/images/toolbarButton-zoomOut.png
Normal file
After Width: | Height: | Size: 143 B |
145
ViewerJS/index.html
Normal file
8052
ViewerJS/pdf.js
Normal file
39353
ViewerJS/pdf.worker.js
vendored
Normal file
1
ViewerJS/pdfjsversion.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
var /**@const{!string}*/pdfjs_version = "v1.1.114";
|
419
ViewerJS/text_layer_builder.js
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
/* globals CustomStyle, PDFJS */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MAX_TEXT_DIVS_TO_RENDER = 100000;
|
||||||
|
|
||||||
|
var NonWhitespaceRegexp = /\S/;
|
||||||
|
|
||||||
|
function isAllWhitespace(str) {
|
||||||
|
return !NonWhitespaceRegexp.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} TextLayerBuilderOptions
|
||||||
|
* @property {HTMLDivElement} textLayerDiv - The text layer container.
|
||||||
|
* @property {number} pageIndex - The page index.
|
||||||
|
* @property {PageViewport} viewport - The viewport of the text layer.
|
||||||
|
* @property {PDFFindController} findController
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.renderingDone = false;
|
||||||
|
this.divContentDone = false;
|
||||||
|
this.pageIdx = options.pageIndex;
|
||||||
|
this.pageNumber = this.pageIdx + 1;
|
||||||
|
this.matches = [];
|
||||||
|
this.viewport = options.viewport;
|
||||||
|
this.textDivs = [];
|
||||||
|
this.findController = options.findController || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextLayerBuilder.prototype = {
|
||||||
|
_finishRendering: function TextLayerBuilder_finishRendering() {
|
||||||
|
this.renderingDone = true;
|
||||||
|
|
||||||
|
var event = document.createEvent('CustomEvent');
|
||||||
|
event.initCustomEvent('textlayerrendered', true, true, {
|
||||||
|
pageNumber: this.pageNumber
|
||||||
|
});
|
||||||
|
this.textLayerDiv.dispatchEvent(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderLayer: function TextLayerBuilder_renderLayer() {
|
||||||
|
var textLayerFrag = document.createDocumentFragment();
|
||||||
|
var textDivs = this.textDivs;
|
||||||
|
var textDivsLength = textDivs.length;
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// No point in rendering many divs as it would make the browser
|
||||||
|
// unusable even after the divs are rendered.
|
||||||
|
if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
|
||||||
|
this._finishRendering();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastFontSize;
|
||||||
|
var lastFontFamily;
|
||||||
|
for (var i = 0; i < textDivsLength; i++) {
|
||||||
|
var textDiv = textDivs[i];
|
||||||
|
if (textDiv.dataset.isWhitespace !== undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fontSize = textDiv.style.fontSize;
|
||||||
|
var fontFamily = textDiv.style.fontFamily;
|
||||||
|
|
||||||
|
// Only build font string and set to context if different from last.
|
||||||
|
if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
|
||||||
|
ctx.font = fontSize + ' ' + fontFamily;
|
||||||
|
lastFontSize = fontSize;
|
||||||
|
lastFontFamily = fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = ctx.measureText(textDiv.textContent).width;
|
||||||
|
if (width > 0) {
|
||||||
|
textLayerFrag.appendChild(textDiv);
|
||||||
|
var transform;
|
||||||
|
if (textDiv.dataset.canvasWidth !== undefined) {
|
||||||
|
// Dataset values come of type string.
|
||||||
|
var textScale = textDiv.dataset.canvasWidth / width;
|
||||||
|
transform = 'scaleX(' + textScale + ')';
|
||||||
|
} else {
|
||||||
|
transform = '';
|
||||||
|
}
|
||||||
|
var rotation = textDiv.dataset.angle;
|
||||||
|
if (rotation) {
|
||||||
|
transform = 'rotate(' + rotation + 'deg) ' + transform;
|
||||||
|
}
|
||||||
|
if (transform) {
|
||||||
|
CustomStyle.setProp('transform' , textDiv, transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.textLayerDiv.appendChild(textLayerFrag);
|
||||||
|
this._finishRendering();
|
||||||
|
this.updateMatches();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.divContentDone || this.renderingDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.renderTimer) {
|
||||||
|
clearTimeout(this.renderTimer);
|
||||||
|
this.renderTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timeout) { // Render right away
|
||||||
|
this.renderLayer();
|
||||||
|
} else { // Schedule
|
||||||
|
var self = this;
|
||||||
|
this.renderTimer = setTimeout(function() {
|
||||||
|
self.renderLayer();
|
||||||
|
self.renderTimer = null;
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
appendText: function TextLayerBuilder_appendText(geom, styles) {
|
||||||
|
var style = styles[geom.fontName];
|
||||||
|
var textDiv = document.createElement('div');
|
||||||
|
this.textDivs.push(textDiv);
|
||||||
|
if (isAllWhitespace(geom.str)) {
|
||||||
|
textDiv.dataset.isWhitespace = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
|
||||||
|
var angle = Math.atan2(tx[1], tx[0]);
|
||||||
|
if (style.vertical) {
|
||||||
|
angle += Math.PI / 2;
|
||||||
|
}
|
||||||
|
var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
|
||||||
|
var fontAscent = fontHeight;
|
||||||
|
if (style.ascent) {
|
||||||
|
fontAscent = style.ascent * fontAscent;
|
||||||
|
} else if (style.descent) {
|
||||||
|
fontAscent = (1 + style.descent) * fontAscent;
|
||||||
|
}
|
||||||
|
|
||||||
|
var left;
|
||||||
|
var top;
|
||||||
|
if (angle === 0) {
|
||||||
|
left = tx[4];
|
||||||
|
top = tx[5] - fontAscent;
|
||||||
|
} else {
|
||||||
|
left = tx[4] + (fontAscent * Math.sin(angle));
|
||||||
|
top = tx[5] - (fontAscent * Math.cos(angle));
|
||||||
|
}
|
||||||
|
textDiv.style.left = left + 'px';
|
||||||
|
textDiv.style.top = top + 'px';
|
||||||
|
textDiv.style.fontSize = fontHeight + 'px';
|
||||||
|
textDiv.style.fontFamily = style.fontFamily;
|
||||||
|
|
||||||
|
textDiv.textContent = geom.str;
|
||||||
|
// |fontName| is only used by the Font Inspector. This test will succeed
|
||||||
|
// when e.g. the Font Inspector is off but the Stepper is on, but it's
|
||||||
|
// not worth the effort to do a more accurate test.
|
||||||
|
if (PDFJS.pdfBug) {
|
||||||
|
textDiv.dataset.fontName = geom.fontName;
|
||||||
|
}
|
||||||
|
// Storing into dataset will convert number into string.
|
||||||
|
if (angle !== 0) {
|
||||||
|
textDiv.dataset.angle = angle * (180 / Math.PI);
|
||||||
|
}
|
||||||
|
// We don't bother scaling single-char text divs, because it has very
|
||||||
|
// little effect on text highlighting. This makes scrolling on docs with
|
||||||
|
// lots of such divs a lot faster.
|
||||||
|
if (textDiv.textContent.length > 1) {
|
||||||
|
if (style.vertical) {
|
||||||
|
textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
|
||||||
|
} else {
|
||||||
|
textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setTextContent: function TextLayerBuilder_setTextContent(textContent) {
|
||||||
|
this.textContent = textContent;
|
||||||
|
|
||||||
|
var textItems = textContent.items;
|
||||||
|
for (var i = 0, len = textItems.length; i < len; i++) {
|
||||||
|
this.appendText(textItems[i], textContent.styles);
|
||||||
|
}
|
||||||
|
this.divContentDone = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
convertMatches: function TextLayerBuilder_convertMatches(matches) {
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
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.
|
||||||
|
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, end.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.
|
||||||
|
this.matches = this.convertMatches(this.findController === null ?
|
||||||
|
[] : (this.findController.pageMatches[this.pageIdx] || []));
|
||||||
|
this.renderMatches(this.matches);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return TextLayerBuilder;
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @implements IPDFTextLayerFactory
|
||||||
|
*/
|
||||||
|
function DefaultTextLayerFactory() {}
|
||||||
|
DefaultTextLayerFactory.prototype = {
|
||||||
|
/**
|
||||||
|
* @param {HTMLDivElement} textLayerDiv
|
||||||
|
* @param {number} pageIndex
|
||||||
|
* @param {PageViewport} viewport
|
||||||
|
* @returns {TextLayerBuilder}
|
||||||
|
*/
|
||||||
|
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
|
||||||
|
return new TextLayerBuilder({
|
||||||
|
textLayerDiv: textLayerDiv,
|
||||||
|
pageIndex: pageIndex,
|
||||||
|
viewport: viewport
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
394
ViewerJS/ui_utils.js
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* 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';
|
||||||
|
|
||||||
|
var CSS_UNITS = 96.0 / 72.0;
|
||||||
|
var DEFAULT_SCALE = 'auto';
|
||||||
|
var UNKNOWN_SCALE = 0;
|
||||||
|
var MAX_AUTO_SCALE = 1.25;
|
||||||
|
var SCROLLBAR_PADDING = 40;
|
||||||
|
var VERTICAL_PADDING = 5;
|
||||||
|
|
||||||
|
// optimised CSS custom property getter/setter
|
||||||
|
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;
|
||||||
|
})();
|
||||||
|
|
||||||
|
function getFileName(url) {
|
||||||
|
var anchor = url.indexOf('#');
|
||||||
|
var query = url.indexOf('?');
|
||||||
|
var end = Math.min(
|
||||||
|
anchor > 0 ? anchor : url.length,
|
||||||
|
query > 0 ? query : url.length);
|
||||||
|
return url.substring(url.lastIndexOf('/', end) + 1, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
var devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
|
var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
|
||||||
|
ctx.mozBackingStorePixelRatio ||
|
||||||
|
ctx.msBackingStorePixelRatio ||
|
||||||
|
ctx.oBackingStorePixelRatio ||
|
||||||
|
ctx.backingStorePixelRatio || 1;
|
||||||
|
var pixelRatio = devicePixelRatio / backingStoreRatio;
|
||||||
|
return {
|
||||||
|
sx: pixelRatio,
|
||||||
|
sy: pixelRatio,
|
||||||
|
scaled: pixelRatio !== 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls specified element into view of its parent.
|
||||||
|
* element {Object} The element to be visible.
|
||||||
|
* spot {Object} An object with optional top and left properties,
|
||||||
|
* specifying the offset from the top left edge.
|
||||||
|
*/
|
||||||
|
function scrollIntoView(element, spot) {
|
||||||
|
// 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 animationStartedClosure.
|
||||||
|
var parent = element.offsetParent;
|
||||||
|
var offsetY = element.offsetTop + element.clientTop;
|
||||||
|
var offsetX = element.offsetLeft + element.clientLeft;
|
||||||
|
if (!parent) {
|
||||||
|
console.error('offsetParent is not set -- cannot scroll');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (parent.clientHeight === parent.scrollHeight) {
|
||||||
|
if (parent.dataset._scaleY) {
|
||||||
|
offsetY /= parent.dataset._scaleY;
|
||||||
|
offsetX /= parent.dataset._scaleX;
|
||||||
|
}
|
||||||
|
offsetY += parent.offsetTop;
|
||||||
|
offsetX += parent.offsetLeft;
|
||||||
|
parent = parent.offsetParent;
|
||||||
|
if (!parent) {
|
||||||
|
return; // no need to scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spot) {
|
||||||
|
if (spot.top !== undefined) {
|
||||||
|
offsetY += spot.top;
|
||||||
|
}
|
||||||
|
if (spot.left !== undefined) {
|
||||||
|
offsetX += spot.left;
|
||||||
|
parent.scrollLeft = offsetX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
var debounceScroll = function debounceScroll(evt) {
|
||||||
|
if (rAF) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// schedule an invocation of scroll for next animation frame.
|
||||||
|
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
|
||||||
|
rAF = null;
|
||||||
|
|
||||||
|
var currentY = viewAreaElement.scrollTop;
|
||||||
|
var lastY = state.lastY;
|
||||||
|
if (currentY !== lastY) {
|
||||||
|
state.down = currentY > lastY;
|
||||||
|
}
|
||||||
|
state.lastY = currentY;
|
||||||
|
callback(state);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
down: true,
|
||||||
|
lastY: viewAreaElement.scrollTop,
|
||||||
|
_eventHandler: debounceScroll
|
||||||
|
};
|
||||||
|
|
||||||
|
var rAF = null;
|
||||||
|
viewAreaElement.addEventListener('scroll', debounceScroll, true);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
var minIndex = 0;
|
||||||
|
var maxIndex = items.length - 1;
|
||||||
|
|
||||||
|
if (items.length === 0 || !condition(items[maxIndex])) {
|
||||||
|
return items.length;
|
||||||
|
}
|
||||||
|
if (condition(items[minIndex])) {
|
||||||
|
return minIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (minIndex < maxIndex) {
|
||||||
|
var currentIndex = (minIndex + maxIndex) >> 1;
|
||||||
|
var currentItem = items[currentIndex];
|
||||||
|
if (condition(currentItem)) {
|
||||||
|
maxIndex = currentIndex;
|
||||||
|
} else {
|
||||||
|
minIndex = currentIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minIndex; /* === maxIndex */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic helper to find out what elements are visible within a scroll pane.
|
||||||
|
*/
|
||||||
|
function getVisibleElements(scrollEl, views, sortByVisibility) {
|
||||||
|
var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
|
||||||
|
var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
|
||||||
|
|
||||||
|
function isElementBottomBelowViewTop(view) {
|
||||||
|
var element = view.div;
|
||||||
|
var elementBottom =
|
||||||
|
element.offsetTop + element.clientTop + element.clientHeight;
|
||||||
|
return elementBottom > top;
|
||||||
|
}
|
||||||
|
|
||||||
|
var visible = [], view, element;
|
||||||
|
var currentHeight, viewHeight, hiddenHeight, percentHeight;
|
||||||
|
var currentWidth, viewWidth;
|
||||||
|
var firstVisibleElementInd = (views.length === 0) ? 0 :
|
||||||
|
binarySearchFirstItem(views, isElementBottomBelowViewTop);
|
||||||
|
|
||||||
|
for (var 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: view,
|
||||||
|
percent: percentHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = visible[0];
|
||||||
|
var last = visible[visible.length - 1];
|
||||||
|
|
||||||
|
if (sortByVisibility) {
|
||||||
|
visible.sort(function(a, b) {
|
||||||
|
var pc = a.percent - b.percent;
|
||||||
|
if (Math.abs(pc) > 0.001) {
|
||||||
|
return -pc;
|
||||||
|
}
|
||||||
|
return a.id - b.id; // ensure stability
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {first: first, last: last, views: visible};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler to suppress context menu.
|
||||||
|
*/
|
||||||
|
function noContextMenuHandler(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the filename or guessed filename from the url (see issue 3455).
|
||||||
|
* url {String} The original PDF location.
|
||||||
|
* @return {String} Guessed PDF file name.
|
||||||
|
*/
|
||||||
|
function getPDFFileNameFromURL(url) {
|
||||||
|
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
|
||||||
|
// SCHEME HOST 1.PATH 2.QUERY 3.REF
|
||||||
|
// Pattern to get last matching NAME.pdf
|
||||||
|
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
|
||||||
|
var splitURI = reURI.exec(url);
|
||||||
|
var suggestedFilename = reFilename.exec(splitURI[1]) ||
|
||||||
|
reFilename.exec(splitURI[2]) ||
|
||||||
|
reFilename.exec(splitURI[3]);
|
||||||
|
if (suggestedFilename) {
|
||||||
|
suggestedFilename = suggestedFilename[0];
|
||||||
|
if (suggestedFilename.indexOf('%') !== -1) {
|
||||||
|
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
|
||||||
|
try {
|
||||||
|
suggestedFilename =
|
||||||
|
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
|
||||||
|
} catch(e) { // Possible (extremely rare) errors:
|
||||||
|
// URIError "Malformed URI", e.g. for "%AA.pdf"
|
||||||
|
// TypeError "null has no properties", e.g. for "%2F.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return suggestedFilename || 'document.pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
var ProgressBar = (function ProgressBarClosure() {
|
||||||
|
|
||||||
|
function clamp(v, min, max) {
|
||||||
|
return Math.min(Math.max(v, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProgressBar(id, opts) {
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
// Fetch the sub-elements for later.
|
||||||
|
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 = opts.height || 100;
|
||||||
|
this.width = opts.width || 100;
|
||||||
|
this.units = opts.units || '%';
|
||||||
|
|
||||||
|
// Initialize heights.
|
||||||
|
this.div.style.height = this.height + this.units;
|
||||||
|
this.percent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar.prototype = {
|
||||||
|
|
||||||
|
updateBar: function ProgressBar_updateBar() {
|
||||||
|
if (this._indeterminate) {
|
||||||
|
this.div.classList.add('indeterminate');
|
||||||
|
this.div.style.width = this.width + this.units;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.div.classList.remove('indeterminate');
|
||||||
|
var progressSize = this.width * this._percent / 100;
|
||||||
|
this.div.style.width = progressSize + this.units;
|
||||||
|
},
|
||||||
|
|
||||||
|
get percent() {
|
||||||
|
return this._percent;
|
||||||
|
},
|
||||||
|
|
||||||
|
set percent(val) {
|
||||||
|
this._indeterminate = isNaN(val);
|
||||||
|
this._percent = clamp(val, 0, 100);
|
||||||
|
this.updateBar();
|
||||||
|
},
|
||||||
|
|
||||||
|
setWidth: function ProgressBar_setWidth(viewer) {
|
||||||
|
if (viewer) {
|
||||||
|
var container = viewer.parentNode;
|
||||||
|
var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
|
||||||
|
if (scrollbarWidth > 0) {
|
||||||
|
this.bar.setAttribute('style', 'width: calc(100% - ' +
|
||||||
|
scrollbarWidth + 'px);');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function ProgressBar_hide() {
|
||||||
|
if (!this.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.visible = false;
|
||||||
|
this.bar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('loadingInProgress');
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function ProgressBar_show() {
|
||||||
|
if (this.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.visible = true;
|
||||||
|
document.body.classList.add('loadingInProgress');
|
||||||
|
this.bar.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProgressBar;
|
||||||
|
})();
|
936
ViewerJS/webodf.js
Normal file
@ -766,7 +766,10 @@ app.classes.filemanager = AppJS.extend(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
egw.open({path: path, type: data.data.mime, download_url: data.data.download_url}, 'file','view',null,'_browser');
|
// Build ViewerJS url
|
||||||
|
if (data.data.mime.match(/application\/vnd\.oasis\.opendocument/)) var url = '/ViewerJS/#..' + data.data.download_url;
|
||||||
|
|
||||||
|
egw.open({path: path, type: data.data.mime, download_url: url}, 'file','view',null,'_browser');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|