PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.5.4
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.5.4
4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / assets / pdf / web / ep-scripts.js
embedpress / assets / pdf / web Last commit date
images 2 years ago locale 2 years ago ep-scripts.js 3 months ago ep-styles.css 1 year ago viewer.css 2 years ago viewer.html 1 year ago viewer.js 3 months ago
ep-scripts.js
652 lines
1 "use strict";
2
3 //Create theme mode function
4 const setThemeMode = (themeMode) => {
5 const htmlEL = document.getElementsByTagName("html")[0];
6 if (htmlEL) {
7 htmlEL.setAttribute('ep-data-theme', themeMode);
8 }
9 }
10
11
12 const getParamObj = (hash) => {
13
14 let paramsObj = {};
15 let colorsObj = {};
16
17 if (location.hash) {
18 let hashParams = new URLSearchParams(hash.substring(1));
19
20 if (hashParams.get('key') !== null) {
21 hashParams = '#' + atob(hashParams.get('key'));
22 hashParams = new URLSearchParams(hashParams.substring(1));
23 }
24
25 if (hashParams.get('themeMode') == 'custom') {
26 colorsObj = {
27 customColor: hashParams.get('customColor'),
28 };
29 }
30 paramsObj = {
31 themeMode: hashParams.get('themeMode'),
32 ...colorsObj,
33 presentation: hashParams.get('presentation'),
34 lazyLoad: hashParams.get('lazyLoad'),
35 copy_text: hashParams.get('copy_text'),
36 add_text: hashParams.get('add_text'),
37 draw: hashParams.get('draw'),
38 add_image: hashParams.get('add_image'),
39 position: hashParams.get('position'),
40 download: hashParams.get('download'),
41 toolbar: hashParams.get('toolbar'),
42 doc_details: hashParams.get('pdf_details'),
43 doc_rotation: hashParams.get('pdf_rotation'),
44 selection_tool: hashParams.get('selection_tool'),
45 scrolling: hashParams.get('scrolling'),
46 spreads: hashParams.get('spreads'),
47 is_pro_active: hashParams.get('is_pro_active'),
48 pageNumber: hashParams.get('pageNumber'),
49 watermark_text: decodeURIComponent(hashParams.get('watermark_text') || ''),
50 watermark_font_size: hashParams.get('watermark_font_size'),
51 watermark_color: (function() {
52 var c = hashParams.get('watermark_color');
53 if (c && c.charAt(0) !== '#') c = '#' + c;
54 return c || '#000000';
55 })(),
56 watermark_opacity: hashParams.get('watermark_opacity'),
57 watermark_style: hashParams.get('watermark_style'),
58 };
59
60
61 if (hashParams.get('download') !== 'true' && hashParams.get('download') !== 'yes') {
62 window.addEventListener('beforeunload', function (event) {
63 event.stopImmediatePropagation();
64 });
65 }
66 }
67
68 return paramsObj;
69 }
70
71 const isDisplay = (selectorName) => {
72 if (selectorName == 'false' || selectorName == false) {
73 selectorName = 'none';
74 }
75 else {
76 selectorName = 'block';
77 }
78 return selectorName;
79 }
80
81
82 const adjustHexColor = (hexColor, percentage) => {
83 if (!hexColor || typeof hexColor !== 'string' || !hexColor.startsWith('#') || hexColor.length < 4) {
84 return hexColor || '#38383d';
85 }
86 // Convert hex color to RGB values
87 const r = parseInt(hexColor.slice(1, 3), 16);
88 const g = parseInt(hexColor.slice(3, 5), 16);
89 const b = parseInt(hexColor.slice(5, 7), 16);
90
91 // Calculate adjusted RGB values
92 const adjustment = Math.round((percentage / 100) * 255);
93 const newR = Math.max(Math.min(r + adjustment, 255), 0);
94 const newG = Math.max(Math.min(g + adjustment, 255), 0);
95 const newB = Math.max(Math.min(b + adjustment, 255), 0);
96
97 // Convert adjusted RGB values back to hex color
98 const newHexColor = '#' + ((1 << 24) + (newR << 16) + (newG << 8) + newB).toString(16).slice(1);
99
100 return newHexColor;
101 }
102
103
104 const getColorBrightness = (hexColor) => {
105 if (!hexColor || typeof hexColor !== 'string' || !hexColor.startsWith('#') || hexColor.length < 4) {
106 return 50;
107 }
108 const r = parseInt(hexColor.slice(1, 3), 16);
109 const g = parseInt(hexColor.slice(3, 5), 16);
110 const b = parseInt(hexColor.slice(5, 7), 16);
111
112 // Convert the RGB color to HSL
113 const max = Math.max(r, g, b);
114 const min = Math.min(r, g, b);
115 const l = (max + min) / 2;
116
117 // Calculate the brightness position in percentage
118 const brightnessPercentage = Math.round(l / 255 * 100);
119
120 return brightnessPercentage;
121 }
122 const pdfIframeStyle = (data) => {
123
124 const isAllNull = Object.values(data).every(value => value === null);;
125
126 if (isAllNull) {
127 return false;
128 };
129
130 let settingsPos = '';
131
132 if (data.toolbar === false || data.toolbar == 'false') {
133 data.presentation = false; data.download = true; data.copy_text = true; data.add_text = true; data.draw = true, data.doc_details = false; data.doc_rotation = false, data.add_image = false;
134 }
135
136 let position = 'top';
137 let toolbar = isDisplay(data.toolbar);
138 let presentation = isDisplay(data.presentation);
139 let download = isDisplay(data.download);
140 let copy_text = isDisplay(data.copy_text);
141 let add_text = isDisplay(data.add_text);
142 let draw = isDisplay(data.draw);
143
144 if (copy_text === 'block' || copy_text == 'true' || copy_text == true) {
145 copy_text = 'text';
146 }
147
148
149 let doc_details = isDisplay(data.doc_details);
150 let doc_rotation = isDisplay(data.doc_rotation);
151 let add_image = isDisplay(data.add_image);
152
153 const otherhead = document.getElementsByTagName("head")[0];
154
155 const style = document.createElement("style");
156 style.setAttribute('id', 'EBiframeStyleID');
157
158 let pdfCustomColor = '';
159
160
161
162 if (data.themeMode == 'custom') {
163 if (!data.customColor) {
164 data.customColor = '#38383d';
165 }
166
167 let colorBrightness = getColorBrightness(data.customColor);
168
169 let iconsTextsColor = 'white';
170 if (colorBrightness > 60) {
171 iconsTextsColor = 'black';
172 }
173
174 pdfCustomColor = `
175 [ep-data-theme="custom"] {
176 --body-bg-color: ${data.customColor};
177 --toolbar-bg-color: ${adjustHexColor(data.customColor, 15)};
178 --doorhanger-bg-color: ${data.customColor};
179 --field-bg-color: ${data.customColor};
180 --dropdown-btn-bg-color: ${data.customColor};
181 --button-hover-color: ${adjustHexColor(data.customColor, 25)};
182 --toggled-btn-bg-color: ${adjustHexColor(data.customColor, 25)};
183 --doorhanger-hover-bg-color: ${adjustHexColor(data.customColor, 20)};
184 --toolbar-border-color: ${adjustHexColor(data.customColor, 10)};
185 --doorhanger-border-color: ${adjustHexColor(data.customColor, 10)};
186 --doorhanger-border-color-whcm: ${adjustHexColor(data.customColor, 10)};
187 --separator-color: ${adjustHexColor(data.customColor, 10)};
188 --doorhanger-separator-color: ${adjustHexColor(data.customColor, 15)};
189 --toolbar-icon-bg-color: ${iconsTextsColor};
190 --toolbar-icon-bg-color: ${iconsTextsColor};
191 --main-color: ${iconsTextsColor};
192 --field-color: ${iconsTextsColor};
193 --doorhanger-hover-color: ${iconsTextsColor};
194 --toolbar-icon-hover-bg-color: ${iconsTextsColor};
195 --toggled-btn-color: ${iconsTextsColor};
196
197 }`;
198 }
199
200 if (data.position === 'top') {
201 position = 'top:0;bottom:auto;'
202 settingsPos = '';
203 }
204 else {
205 position = 'bottom:0;top:auto;'
206 settingsPos = `
207 .findbar, .secondaryToolbar {
208 top: auto;bottom: 32px;
209 }
210 .doorHangerRight:after{
211 transform: rotate(180deg);
212 bottom: -16px;
213 }
214 .doorHangerRight:before {
215 transform: rotate(180deg);
216 bottom: -18px;
217 }
218
219 .findbar.doorHanger:before {
220 bottom: -18px;
221 transform: rotate(180deg);
222 }
223 .findbar.doorHanger:after {
224 bottom: -16px;
225 transform: rotate(180deg);
226 }
227
228 div#editorInkParamsToolbar, #editorFreeTextParamsToolbar {
229 bottom: 32px;
230 top: auto;
231 }
232 #mainContainer {
233 top: -40px!important;
234 }
235 `;
236 }
237
238 style.textContent = `
239 .toolbar{
240 display: ${toolbar}!important;
241 position: absolute;
242 ${position}
243 }
244 #secondaryToolbar{
245 display: ${toolbar};
246 }
247 #secondaryPresentationMode, #toolbarViewerRight #presentationMode{
248 display: ${presentation}!important;
249 }
250 #secondaryOpenFile, #toolbarViewerRight #openFile{
251 display: none!important;
252 }
253 #secondaryDownload, #secondaryPrint, #print, #download{
254 display: ${download}!important;
255 }
256 #pageRotateCw{
257 display: ${doc_rotation}!important;
258 }
259 #editorStamp{
260 display: ${add_image}!important;
261 }
262 #pageRotateCcw{
263 display: ${doc_rotation}!important;
264 }
265 #documentProperties{
266 display: ${doc_details}!important;
267 }
268 .textLayer{
269 user-select: ${copy_text}!important;
270 -webkit-user-select: ${copy_text}!important;
271 -moz-user-select: ${copy_text}!important;
272 -ms-user-select: ${copy_text}!important;
273 }
274 button#cursorSelectTool{
275 display: ${copy_text}!important;
276 }
277
278 #editorFreeText{
279 display: ${add_text}!important;
280 }
281 #editorInk{
282 display: ${draw}!important;
283 }
284
285 ${pdfCustomColor}
286
287 ${settingsPos}
288 `;
289
290 if (otherhead) {
291 if (document.getElementById("EBiframeStyleID")) {
292 document.getElementById("EBiframeStyleID").remove();
293 }
294 otherhead.appendChild(style);
295 }
296 }
297
298 const manupulatePDFIframe = (e) => {
299 let hashNewUrl = new URL(e.newURL);
300 let data = getParamObj(hashNewUrl.hash);
301 pdfIframeStyle(data);
302 setThemeMode(data.themeMode);
303
304 }
305
306 window.addEventListener('hashchange', (e) => {
307 manupulatePDFIframe(e);
308 }, false);
309
310
311 let data = getParamObj(location.hash);
312
313 pdfIframeStyle(data);
314 setThemeMode(data.themeMode);
315
316
317
318 document.querySelector(".presentationMode")?.addEventListener("click", function () {
319
320 var mainContainer = document.getElementById("mainContainer");
321 if (mainContainer && !document.fullscreenElement) {
322 mainContainer.requestFullscreen().catch(err => {
323 alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
324 });
325 } else {
326 if (document.exitFullscreen) {
327 document.exitFullscreen();
328 }
329 }
330 });
331
332 document.getElementById("viewBookmark")?.addEventListener('click', (e) => {
333 e.preventDefault();
334 const url = e.target.getAttribute('href');
335 if (url !== null) {
336 alert(`Current Page: ${url}`);
337 }
338 });
339
340
341 // Open at specific page number
342 const epPageNumber = (function() {
343 // Check for URL query parameter ?eppage=N from the top-level page
344 try {
345 const topUrl = new URL(window.top.location.href);
346 const urlPage = topUrl.searchParams.get('eppage');
347 if (urlPage && parseInt(urlPage, 10) > 0) {
348 return parseInt(urlPage, 10);
349 }
350 } catch (e) {
351 // Cross-origin access may fail, ignore
352 }
353 // Fall back to the embedded param
354 if (data.pageNumber && parseInt(data.pageNumber, 10) > 1) {
355 return parseInt(data.pageNumber, 10);
356 }
357 return null;
358 })();
359
360 if (epPageNumber) {
361 var setEpPage = function() {
362 if (typeof PDFViewerApplication !== 'undefined' && PDFViewerApplication.pdfDocument) {
363 PDFViewerApplication.page = epPageNumber;
364 } else if (typeof PDFViewerApplication !== 'undefined' && PDFViewerApplication.eventBus) {
365 PDFViewerApplication.eventBus.on('documentloaded', function() {
366 PDFViewerApplication.page = epPageNumber;
367 });
368 } else {
369 // PDFViewerApplication not ready yet, retry
370 setTimeout(setEpPage, 200);
371 }
372 };
373 setEpPage();
374 }
375
376 if (data.lazyLoad === false || data.lazyLoad == 'false') {
377 document.querySelector('html').style.opacity = '1';
378 }
379 else {
380 function updateOpacity() {
381 const pdfViewer = document.querySelector('.pdfViewer');
382
383 if (pdfViewer.innerHTML.trim()) {
384 document.querySelector('html').style.opacity = '1';
385 document.querySelector('html').style.transition = '500ms';
386 clearInterval(intervalId); // Clear the interval once opacity is set to 1
387 }
388 }
389 const intervalId = setInterval(updateOpacity, 100);
390 updateOpacity();
391 }
392
393
394 // ── Watermark: draw directly on PDF page canvas ──
395 const hexToRgba = (hex, alpha) => {
396 if (!hex || hex === 'transparent') {
397 return `rgba(0, 0, 0, ${alpha})`;
398 }
399 // Support 3 and 6 character hex
400 hex = hex.replace(/^#/, '');
401 if (hex.length === 3) {
402 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
403 }
404 if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
405 return `rgba(0, 0, 0, ${alpha})`;
406 }
407 const r = parseInt(hex.slice(0, 2), 16);
408 const g = parseInt(hex.slice(2, 4), 16);
409 const b = parseInt(hex.slice(4, 6), 16);
410 return `rgba(${r}, ${g}, ${b}, ${alpha})`;
411 };
412
413 const drawWatermarkOnCanvas = (canvas, wm) => {
414 if (!wm.text) return;
415 const ctx = canvas.getContext('2d');
416 ctx.save();
417
418 const fontSize = parseInt(wm.fontSize, 10) || 48;
419 const opacity = (parseInt(wm.opacity, 10) || 15) / 100;
420 const color = wm.color || '#000000';
421
422 ctx.fillStyle = hexToRgba(color, opacity);
423 ctx.font = `bold ${fontSize}px sans-serif`;
424 ctx.textAlign = 'center';
425 ctx.textBaseline = 'middle';
426
427 if (wm.style === 'tiled') {
428 // Tiled: repeated watermark across the page
429 const textWidth = ctx.measureText(wm.text).width;
430 const stepX = textWidth + 80;
431 const stepY = fontSize * 3;
432 // Expand drawing area to cover rotated canvas
433 const diagonal = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height);
434
435 ctx.translate(canvas.width / 2, canvas.height / 2);
436 ctx.rotate(-Math.PI / 6);
437
438 for (let y = -diagonal; y < diagonal; y += stepY) {
439 for (let x = -diagonal; x < diagonal; x += stepX) {
440 ctx.fillText(wm.text, x, y);
441 }
442 }
443 } else {
444 // Center: single diagonal watermark
445 ctx.translate(canvas.width / 2, canvas.height / 2);
446 ctx.rotate(-Math.atan2(canvas.height, canvas.width));
447
448 // Scale font to fit page width if text is long
449 const textWidth = ctx.measureText(wm.text).width;
450 const maxWidth = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height) * 0.8;
451 if (textWidth > maxWidth) {
452 const scaledSize = Math.floor(fontSize * (maxWidth / textWidth));
453 ctx.font = `bold ${scaledSize}px sans-serif`;
454 }
455
456 ctx.fillText(wm.text, 0, 0);
457 }
458
459 ctx.restore();
460 };
461
462 // Download PDF with watermark baked into pages as JPEG images
463 async function epWatermarkDownload(app, wm) {
464 const pdfDoc = app.pdfDocument;
465 if (!pdfDoc) throw new Error('No PDF loaded');
466
467 const numPages = pdfDoc.numPages;
468 const scale = 2; // Render at 2x for quality
469 const jpgQuality = 0.92;
470 const pageImages = [];
471
472 for (let i = 1; i <= numPages; i++) {
473 const page = await pdfDoc.getPage(i);
474 const vp = page.getViewport({ scale: scale });
475 const canvas = document.createElement('canvas');
476 canvas.width = vp.width;
477 canvas.height = vp.height;
478 const ctx = canvas.getContext('2d');
479 await page.render({ canvasContext: ctx, viewport: vp }).promise;
480 drawWatermarkOnCanvas(canvas, wm);
481
482 const dataUrl = canvas.toDataURL('image/jpeg', jpgQuality);
483 const raw = atob(dataUrl.split(',')[1]);
484 const arr = new Uint8Array(raw.length);
485 for (let j = 0; j < raw.length; j++) arr[j] = raw.charCodeAt(j);
486 pageImages.push({ jpeg: arr, w: vp.width / scale, h: vp.height / scale });
487 }
488
489 // Build minimal PDF with JPEG images
490 const enc = new TextEncoder();
491 const parts = [];
492 const offsets = [];
493
494 function writeStr(s) { parts.push(enc.encode(s)); }
495 function writeBytes(b) { parts.push(b); }
496 function currentSize() { return parts.reduce((a, p) => a + p.length, 0); }
497
498 const nl = '\n';
499 writeStr('%PDF-1.4' + nl);
500
501 // Obj 1: Catalog
502 offsets[1] = currentSize();
503 writeStr('1 0 obj' + nl + '<< /Type /Catalog /Pages 2 0 R >>' + nl + 'endobj' + nl);
504
505 // Build page objects - calculate object numbers first
506 let nextObj = 3;
507 const pageObjNumbers = [];
508 const allPageData = [];
509
510 for (let i = 0; i < pageImages.length; i++) {
511 const pg = pageImages[i];
512 const ptW = (pg.w * 72 / 96).toFixed(2);
513 const ptH = (pg.h * 72 / 96).toFixed(2);
514
515 const imgObj = nextObj++;
516 const contObj = nextObj++;
517 const pgObj = nextObj++;
518 pageObjNumbers.push(pgObj);
519
520 const contentStr = 'q ' + ptW + ' 0 0 ' + ptH + ' 0 0 cm /Im0 Do Q';
521 const contentBytes = enc.encode(contentStr);
522
523 allPageData.push({ imgObj, contObj, pgObj, ptW, ptH, jpeg: pg.jpeg, contentBytes, pgIndex: i });
524 }
525
526 // Obj 2: Pages
527 offsets[2] = currentSize();
528 writeStr('2 0 obj' + nl + '<< /Type /Pages /Kids [' + pageObjNumbers.map(n => n + ' 0 R').join(' ') + '] /Count ' + pageImages.length + ' >>' + nl + 'endobj' + nl);
529
530 // Write each page's objects
531 for (const pd of allPageData) {
532 const pg = pageImages[pd.pgIndex];
533
534 // Image XObject
535 offsets[pd.imgObj] = currentSize();
536 writeStr(pd.imgObj + ' 0 obj' + nl +
537 '<< /Type /XObject /Subtype /Image /Width ' + (pg.w * 2) +
538 ' /Height ' + (pg.h * 2) +
539 ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ' + pd.jpeg.length + ' >>' + nl +
540 'stream' + nl);
541 writeBytes(pd.jpeg);
542 writeStr(nl + 'endstream' + nl + 'endobj' + nl);
543
544 // Content stream
545 offsets[pd.contObj] = currentSize();
546 writeStr(pd.contObj + ' 0 obj' + nl +
547 '<< /Length ' + pd.contentBytes.length + ' >>' + nl +
548 'stream' + nl);
549 writeBytes(pd.contentBytes);
550 writeStr(nl + 'endstream' + nl + 'endobj' + nl);
551
552 // Page object
553 offsets[pd.pgObj] = currentSize();
554 writeStr(pd.pgObj + ' 0 obj' + nl +
555 '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ' + pd.ptW + ' ' + pd.ptH + ']' +
556 ' /Contents ' + pd.contObj + ' 0 R' +
557 ' /Resources << /XObject << /Im0 ' + pd.imgObj + ' 0 R >> >> >>' + nl + 'endobj' + nl);
558 }
559
560 // Cross-reference table
561 const xrefOffset = currentSize();
562 writeStr('xref' + nl + '0 ' + nextObj + nl);
563 writeStr('0000000000 65535 f ' + nl);
564 for (let i = 1; i < nextObj; i++) {
565 writeStr(String(offsets[i]).padStart(10, '0') + ' 00000 n ' + nl);
566 }
567
568 writeStr('trailer' + nl + '<< /Size ' + nextObj + ' /Root 1 0 R >>' + nl);
569 writeStr('startxref' + nl + xrefOffset + nl + '%%EOF');
570
571 // Combine all parts into one Uint8Array
572 const totalLen = parts.reduce((a, p) => a + p.length, 0);
573 const result = new Uint8Array(totalLen);
574 let pos = 0;
575 for (const p of parts) {
576 result.set(p, pos);
577 pos += p.length;
578 }
579
580 // Trigger download
581 const blob = new Blob([result], { type: 'application/pdf' });
582 const url = URL.createObjectURL(blob);
583 const link = document.createElement('a');
584 link.href = url;
585 const fileName = document.title || 'document';
586 link.download = fileName.replace(/\.pdf$/i, '') + '.pdf';
587 document.body.appendChild(link);
588 link.click();
589 document.body.removeChild(link);
590 setTimeout(() => URL.revokeObjectURL(url), 5000);
591 }
592
593 // Hook into PDF.js pagerendered event
594 if (data.watermark_text) {
595 const wmData = {
596 text: data.watermark_text,
597 fontSize: data.watermark_font_size || '48',
598 color: data.watermark_color || '#000000',
599 opacity: data.watermark_opacity || '15',
600 style: data.watermark_style || 'center',
601 };
602
603 // Wait for PDFViewerApplication to be ready
604 const waitForViewer = setInterval(() => {
605 if (typeof PDFViewerApplication !== 'undefined' && PDFViewerApplication.eventBus) {
606 clearInterval(waitForViewer);
607 PDFViewerApplication.eventBus.on('pagerendered', (evt) => {
608 const canvas = evt.source.canvas;
609 if (canvas) {
610 drawWatermarkOnCanvas(canvas, wmData);
611 }
612 });
613
614 // Override download to include watermark
615 const origSave = PDFViewerApplication.download ? PDFViewerApplication.download.bind(PDFViewerApplication) : null;
616 PDFViewerApplication.download = async function() {
617 try {
618 await epWatermarkDownload(PDFViewerApplication, wmData);
619 } catch(e) {
620 console.error('EmbedPress: Watermarked download failed, falling back to original', e);
621 if (origSave) origSave();
622 }
623 };
624 if (PDFViewerApplication.downloadOrSave) {
625 const origDOS = PDFViewerApplication.downloadOrSave.bind(PDFViewerApplication);
626 PDFViewerApplication.downloadOrSave = async function() {
627 try {
628 await epWatermarkDownload(PDFViewerApplication, wmData);
629 } catch(e) {
630 console.error('EmbedPress: Watermarked download failed, falling back to original', e);
631 origDOS();
632 }
633 };
634 }
635
636 // Override print to include watermark on each printed page
637 PDFViewerApplication.eventBus.on('beforeprint', () => {
638 const service = PDFViewerApplication.printService;
639 if (service && !service._epWmPatched) {
640 service._epWmPatched = true;
641 const origUseRenderedPage = service.useRenderedPage.bind(service);
642 service.useRenderedPage = function() {
643 drawWatermarkOnCanvas(service.scratchCanvas, wmData);
644 return origUseRenderedPage();
645 };
646 }
647 });
648 }
649 }, 100);
650 }
651
652