-
Notifications
You must be signed in to change notification settings - Fork 986
/
pdfobject.js
400 lines (288 loc) · 15.2 KB
/
pdfobject.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/**
* PDFObject v2.3.0
* https://github.com/pipwerks/PDFObject
* @license
* Copyright (c) 2008-2024 Philip Hutchison
* MIT-style license: http://pipwerks.mit-license.org/
* UMD module pattern from https://github.com/umdjs/umd/blob/master/templates/returnExports.js
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === "object" && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.PDFObject = factory();
}
}(this, function () {
"use strict";
//PDFObject is designed for client-side (browsers), not server-side (node)
//Will choke on undefined navigator and window vars when run on server
//Return boolean false and exit function when running server-side
if(typeof window === "undefined" || window.navigator === undefined || window.navigator.userAgent === undefined){ return false; }
let pdfobjectversion = "2.3.0";
let win = window;
let nav = win.navigator;
let ua = nav.userAgent;
let suppressConsole = false;
//Fallback validation when navigator.pdfViewerEnabled is not supported
let isModernBrowser = function (){
/*
userAgent sniffing is not the ideal path, but most browsers revoked the ability to check navigator.mimeTypes
for security purposes. As of 2023, browsers have begun implementing navigator.pdfViewerEnabled, but older versions
do not have navigator.pdfViewerEnabled or the ability to check navigator.mimeTypes. We're left with basic browser
sniffing and assumptions of PDF support based on browser vendor.
*/
//Chromium has provided native PDF support since 2011.
//Most modern browsers use Chromium under the hood: Google Chrome, Microsoft Edge, Opera, Brave, Vivaldi, Arc, and more.
//Chromium uses the PDFium rendering engine, which is based on Foxit's PDF rendering engine.
//Note that MS Edge opts to use a different PDF rendering engine. As of 2024, Edge uses a version of Adobe's Reader
let isChromium = (win.chrome !== undefined);
//Safari on macOS has provided native PDF support since 2009.
//This code snippet also detects the DuckDuckGo browser, which uses Safari/Webkit under the hood.
let isSafari = (win.safari !== undefined || (nav.vendor !== undefined && /Apple/.test(nav.vendor) && /Safari/.test(ua)));
//Firefox has provided PDF support via PDFJS since 2013.
let isFirefox = (win.Mozilla !== undefined || /irefox/.test(ua));
return isChromium || isSafari || isFirefox;
};
/*
Special handling for Internet Explorer 11.
Check for ActiveX support, then whether "AcroPDF.PDF" or "PDF.PdfCtrl" are valid.
IE11 uses ActiveX for Adobe Reader and other PDF plugins, but window.ActiveXObject will evaluate to false.
("ActiveXObject" in window) evaluates to true.
MS Edge does not support ActiveX so this test will evaluate false for MS Edge.
*/
let validateAX = function (type){
var ax = null;
try {
ax = new ActiveXObject(type);
} catch (e) {
//ensure ax remains null when ActiveXObject attempt fails
ax = null;
}
return !!ax; //convert resulting object to boolean
};
let hasActiveXPDFPlugin = function (){ return ("ActiveXObject" in win) && (validateAX("AcroPDF.PDF") || validateAX("PDF.PdfCtrl")) };
let checkSupport = function (){
//Safari on iPadOS doesn't report as 'mobile' when requesting desktop site, yet still fails to embed PDFs
let isSafariIOSDesktopMode = (nav.platform !== undefined && nav.platform === "MacIntel" && nav.maxTouchPoints !== undefined && nav.maxTouchPoints > 1);
let isMobileDevice = (isSafariIOSDesktopMode || /Mobi|Tablet|Android|iPad|iPhone/.test(ua));
//As of June 2023, no mobile browsers properly support inline PDFs. If mobile, just say no.
if(isMobileDevice){ return false; }
//Modern browsers began supporting navigator.pdfViewerEnabled in late 2022 and early 2023.
let supportsPDFVE = (typeof nav.pdfViewerEnabled === "boolean");
//If browser supports nav.pdfViewerEnabled and is explicitly saying PDFs are NOT supported (e.g. PDFJS disabled by user in Firefox), respect it.
if(supportsPDFVE && !nav.pdfViewerEnabled){ return false; }
return (supportsPDFVE && nav.pdfViewerEnabled) || isModernBrowser() || hasActiveXPDFPlugin();
};
//Determines whether PDF support is available
let supportsPDFs = checkSupport();
//Create a fragment identifier for using PDF Open parameters when embedding PDF
let buildURLFragmentString = function(pdfParams){
let string = "";
let prop;
let paramArray = [];
let fdf = "";
//The comment, viewrect, and highlight parameters require page to be set first.
//Check to ensure page is used if comment, viewrect, or highlight are specified
if(pdfParams.comment || pdfParams.viewrect || pdfParams.highlight){
if(!pdfParams.page){
//If page is not set, use the first page
pdfParams.page = 1;
//Inform user that page needs to be set properly
embedError("The comment, viewrect, and highlight parameters require a page parameter, but none was specified. Defaulting to page 1.");
}
}
//Let's go ahead and ensure page is always the first parameter.
if(pdfParams.page){
paramArray.push("page=" + encodeURIComponent(pdfParams.page));
delete pdfParams.page;
}
//FDF needs to be the last parameter in the string
if(pdfParams.fdf){
fdf = pdfParams.fdf;
delete pdfParams.fdf;
}
//Add all other parameters, as needed
if(pdfParams){
for (prop in pdfParams) {
if (pdfParams.hasOwnProperty(prop)) {
paramArray.push(encodeURIComponent(prop) + "=" + encodeURIComponent(pdfParams[prop]));
}
}
//Add fdf as the last parameter, if needed
if(fdf){
paramArray.push("fdf=" + encodeURIComponent(fdf));
}
//Join all parameters in the array into a string
string = paramArray.join("&");
//The string will be empty if no PDF Parameters were provided
//Only prepend the hash if the string is not empty
if(string){
string = "#" + string;
}
}
return string;
};
let embedError = function (msg){
if(!suppressConsole){
console.log("[PDFObject]", msg);
}
return false;
};
let emptyNodeContents = function (node){
while(node.firstChild){
node.removeChild(node.firstChild);
}
};
let getTargetElement = function (targetSelector){
//Default to body for full-browser PDF
let targetNode = document.body;
//If a targetSelector is specified, check to see whether
//it's passing a selector, jQuery object, or an HTML element
if(typeof targetSelector === "string"){
//Is CSS selector
targetNode = document.querySelector(targetSelector);
} else if (win.jQuery !== undefined && targetSelector instanceof jQuery && targetSelector.length) {
//Is jQuery element. Extract HTML node
targetNode = targetSelector.get(0);
} else if (targetSelector.nodeType !== undefined && targetSelector.nodeType === 1){
//Is HTML element
targetNode = targetSelector;
}
return targetNode;
};
let convertBase64ToDownloadableLink = function (b64, filename, targetNode, fallbackHTML) {
//IE-11 safe version. More verbose than modern fetch()
if (window.Blob && window.URL && window.URL.createObjectURL) {
var xhr = new XMLHttpRequest();
xhr.open('GET', b64, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
var blob = xhr.response;
var link = document.createElement('a');
link.innerText = "Download PDF";
link.href = URL.createObjectURL(blob);
link.setAttribute('download', filename);
targetNode.innerHTML = fallbackHTML.replace(/\[pdflink\]/g, link.outerHTML);
}
};
xhr.send();
}
};
let generatePDFObjectMarkup = function (embedType, targetNode, url, pdfOpenFragment, width, height, id, title, omitInlineStyles, customAttribute, PDFJS_URL){
//Ensure target element is empty first
emptyNodeContents(targetNode);
let source = url;
if(embedType === "pdfjs"){
//If PDFJS_URL already contains a ?, assume querystring is in place, and use an ampersand to append PDFJS's file parameter
let connector = (PDFJS_URL.indexOf("?") !== -1) ? "&" : "?";
source = PDFJS_URL + connector + "file=" + encodeURIComponent(url) + pdfOpenFragment;
} else {
source += pdfOpenFragment;
}
let el = document.createElement("iframe");
el.className = "pdfobject";
el.type = "application/pdf";
el.title = title;
el.src = source;
el.allow = "fullscreen";
el.frameborder = "0";
if(id){ el.id = id; }
if(!omitInlineStyles){
let style = "border: none;";
if(targetNode !== document.body){
//assign width and height to target node
style += "width: " + width + "; height: " + height + ";";
} else {
//this is a full-page embed, use CSS to fill the viewport
style += "position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%;";
}
el.style.cssText = style;
}
//Allow developer to insert custom attribute on iframe element, but ensure it does not conflict with attributes used by PDFObject
let reservedTokens = ["className", "type", "title", "src", "style", "id", "allow", "frameborder"];
if(customAttribute && customAttribute.key && reservedTokens.indexOf(customAttribute.key) === -1){
el.setAttribute(customAttribute.key, (typeof customAttribute.value !== "undefined") ? customAttribute.value : "");
}
targetNode.classList.add("pdfobject-container");
targetNode.appendChild(el);
return targetNode.getElementsByTagName("iframe")[0];
};
let embed = function(url, targetSelector, options){
//If targetSelector is not defined, convert to boolean
let selector = targetSelector || false;
//Ensure options object is not undefined -- enables easier error checking below
let opt = options || {};
//Get passed options, or set reasonable defaults
suppressConsole = (typeof opt.suppressConsole === "boolean") ? opt.suppressConsole : false;
let id = (typeof opt.id === "string") ? opt.id : "";
let page = opt.page || false;
let pdfOpenParams = opt.pdfOpenParams || {};
let fallbackLink = (typeof opt.fallbackLink === "string" || typeof opt.fallbackLink === "boolean") ? opt.fallbackLink : true;
let width = opt.width || "100%";
let height = opt.height || "100%";
let title = opt.title || "Embedded PDF";
let forcePDFJS = (typeof opt.forcePDFJS === "boolean") ? opt.forcePDFJS : false;
let omitInlineStyles = (typeof opt.omitInlineStyles === "boolean") ? opt.omitInlineStyles : false;
let PDFJS_URL = opt.PDFJS_URL || false;
let targetNode = getTargetElement(selector);
let pdfOpenFragment = "";
let customAttribute = opt.customAttribute || {};
let fallbackHTML_default = "<p>This browser does not support inline PDFs. Please download the PDF to view it: [pdflink]</p>";
//Ensure URL is available. If not, exit now.
if(typeof url !== "string"){ return embedError("URL is not valid"); }
//If target element is specified but is not valid, exit without doing anything
if(!targetNode){ return embedError("Target element cannot be determined"); }
//page option overrides pdfOpenParams, if found
if(page){ pdfOpenParams.page = page; }
//Stringify optional Adobe params for opening document (as fragment identifier)
pdfOpenFragment = buildURLFragmentString(pdfOpenParams);
// --== Do the dance: Embed attempt #1 ==--
//If the forcePDFJS option is invoked, skip everything else and embed as directed
if(forcePDFJS && PDFJS_URL){
return generatePDFObjectMarkup("pdfjs", targetNode, url, pdfOpenFragment, width, height, id, title, omitInlineStyles, customAttribute, PDFJS_URL);
}
// --== Embed attempt #2 ==--
//Embed PDF if support is detected, or if this is a relatively modern browser
if(supportsPDFs){
return generatePDFObjectMarkup("iframe", targetNode, url, pdfOpenFragment, width, height, id, title, omitInlineStyles, customAttribute);
}
// --== Embed attempt #3 ==--
//If everything else has failed and a PDFJS fallback is provided, try to use it
if(PDFJS_URL){
return generatePDFObjectMarkup("pdfjs", targetNode, url, pdfOpenFragment, width, height, id, title, omitInlineStyles, customAttribute, PDFJS_URL);
}
// --== PDF embed not supported! Use fallback ==--
//Display the fallback link if available
if(fallbackLink){
//If a custom fallback has been provided, handle it now
if(typeof fallbackLink === "string"){
//Ensure [url] is set in custom fallback
targetNode.innerHTML = fallbackLink.replace(/\[url\]/g, url);
} else {
//If the PDF is a base64 string, convert it to a downloadable link
if(url.indexOf("data:application/pdf;base64") !== -1){
//Asynchronously append the link to the targetNode
convertBase64ToDownloadableLink(url, "file.pdf", targetNode, fallbackHTML_default);
} else {
//Use default fallback link
let link = "<a href='" + url + "'>Download PDF</a>";
targetNode.innerHTML = fallbackHTML_default.replace(/\[pdflink\]/g, link);
}
}
}
return embedError("This browser does not support embedded PDFs");
};
return {
embed: function (a,b,c){ return embed(a,b,c); },
pdfobjectversion: (function () { return pdfobjectversion; })(),
supportsPDFs: (function (){ return supportsPDFs; })()
};
}));