PageSourceSearch

https://eb-energiekonzepte.de/js/lib/indice-pdfview.js

js eb-energiekonzepte.de collected 2026-10-06 21:49:11 UTC 10,064 bytes, 229 lines download raw bytes

1/**
2 * IndicePdfView — leichter Canvas-PDF-Viewer auf Basis von PDF.js.
3 *
4 * Für Browser ohne eingebauten PDF-Viewer (insb. iOS Safari / Android Chrome),
5 * die PDFs im iframe nur als erste Seite oder gar nicht darstellen.
6 *
7 * Verwendung:
8 *   if (IndicePdfView.needsCanvasViewer()) {
9 *       IndicePdfView.render(containerElement, '/api/.../download?...&preview=1');
10 *   }
11 *
12 * PDF.js (lokal: /js/lib/pdfjs/) wird erst beim ersten render() lazy nachgeladen.
13 */
14(function () {
15    'use strict';
16
17    if (window.IndicePdfView) return;
18
19    var PDFJS_SRC    = '/js/lib/pdfjs/pdf.min.js';
20    var PDFJS_WORKER = '/js/lib/pdfjs/pdf.worker.min.js';
21    var PAGE_GAP = 12;       // grauer Abstand um die Seiten (px)
22    var pdfjsPromise = null;
23    var stylesInjected = false;
24
25    /* ---------------------------- Styles ---------------------------- */
26
27    function injectStyles() {
28        if (stylesInjected) return;
29        stylesInjected = true;
30        var css = ''
31            + '.ipdfv-container{position:relative;background:#8d949c;-webkit-overflow-scrolling:touch;}'
32            + '.ipdfv-pagebar{position:sticky;top:8px;z-index:5;display:table;margin:0 auto;'
33            + 'background:rgba(0,0,0,0.65);color:#fff;font-size:12px;line-height:1;'
34            + 'padding:6px 12px;border-radius:999px;pointer-events:none;font-family:sans-serif;}'
35            + '.ipdfv-page{margin:' + PAGE_GAP + 'px auto;background:#fff;'
36            + 'box-shadow:0 2px 8px rgba(0,0,0,0.35);position:relative;}'
37            + '.ipdfv-page canvas{display:block;}'
38            + '.ipdfv-page:not([data-rendered])::after{content:"";position:absolute;top:50%;left:50%;'
39            + 'width:24px;height:24px;margin:-12px 0 0 -12px;border:3px solid #e2e2e2;'
40            + 'border-top-color:#8d949c;border-radius:50%;animation:ipdfv-spin .8s linear infinite;}'
41            + '.ipdfv-status{display:flex;flex-direction:column;align-items:center;justify-content:center;'
42            + 'gap:12px;padding:48px 16px;color:#fff;font-family:sans-serif;font-size:14px;text-align:center;}'
43            + '.ipdfv-spinner{width:32px;height:32px;border:3px solid rgba(255,255,255,0.35);'
44            + 'border-top-color:#fff;border-radius:50%;animation:ipdfv-spin .8s linear infinite;}'
45            + '@keyframes ipdfv-spin{to{transform:rotate(360deg);}}';
46        var style = document.createElement('style');
47        style.setAttribute('data-ipdfv', '1');
48        style.textContent = css;
49        document.head.appendChild(style);
50    }
51
52    /* ---------------------------- PDF.js lazy ---------------------------- */
53
54    function loadPdfJs() {
55        if (window.pdfjsLib) {
56            window.pdfjsLib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER;
57            return Promise.resolve(window.pdfjsLib);
58        }
59        if (pdfjsPromise) return pdfjsPromise;
60        pdfjsPromise = new Promise(function (resolve, reject) {
61            var s = document.createElement('script');
62            s.src = PDFJS_SRC;
63            s.onload = function () {
64                if (window.pdfjsLib) {
65                    window.pdfjsLib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER;
66                    resolve(window.pdfjsLib);
67                } else {
68                    pdfjsPromise = null;
69                    reject(new Error('pdfjsLib nicht verfuegbar'));
70                }
71            };
72            s.onerror = function () {
73                pdfjsPromise = null;
74                reject(new Error('PDF.js konnte nicht geladen werden'));
75            };
76            document.head.appendChild(s);
77        });
78        return pdfjsPromise;
79    }
80
81    /* ---------------------------- Rendering ---------------------------- */
82
83    function availWidth(container) {
84        return Math.max(100, container.clientWidth - PAGE_GAP * 2);
85    }
86
87    function buildPages(container, pdf) {
88        var numPages = pdf.numPages;
89        container.innerHTML = '';
90
91        // Sticky-Seitenanzeige "Seite X / Y"
92        var pagebar = document.createElement('div');
93        pagebar.className = 'ipdfv-pagebar';
94        pagebar.textContent = 'Seite 1 / ' + numPages;
95        container.appendChild(pagebar);
96
97        // Placeholder fuer alle Seiten sofort anlegen (DIN-A4-Verhaeltnis als Startwert)
98        var w = availWidth(container);
99        var holders = [];
100        for (var i = 1; i <= numPages; i++) {
101            var holder = document.createElement('div');
102            holder.className = 'ipdfv-page';
103            holder.setAttribute('data-page', String(i));
104            holder.style.width = w + 'px';
105            holder.style.height = Math.round(w * 1.414) + 'px';
106            container.appendChild(holder);
107            holders.push(holder);
108        }
109
110        // Exakte Hoehen aus den Seiten-Metadaten nachziehen (rendert nichts)
111        (function fixHeights(idx) {
112            if (idx > numPages) return;
113            pdf.getPage(idx).then(function (page) {
114                var holder = holders[idx - 1];
115                if (holder && !holder.hasAttribute('data-rendered')) {
116                    var vp = page.getViewport({ scale: 1 });
117                    holder.style.height = Math.round(availWidth(container) * (vp.height / vp.width)) + 'px';
118                }
119                fixHeights(idx + 1);
120            }).catch(function () { fixHeights(idx + 1); });
121        })(1);
122
123        // Lazy Rendering pro Seite via IntersectionObserver
124        var started = {};
125        function renderPage(holder) {
126            var num = parseInt(holder.getAttribute('data-page'), 10);
127            if (started[num]) return;
128            started[num] = true;
129            pdf.getPage(num).then(function (page) {
130                var base = page.getViewport({ scale: 1 });
131                var scale = availWidth(container) / base.width;
132                var vp = page.getViewport({ scale: scale });
133                var dpr = window.devicePixelRatio || 1;
134
135                var canvas = document.createElement('canvas');
136                canvas.width  = Math.floor(vp.width * dpr);
137                canvas.height = Math.floor(vp.height * dpr);
138                canvas.style.width  = Math.floor(vp.width) + 'px';
139                canvas.style.height = Math.floor(vp.height) + 'px';
140
141                var task = page.render({
142                    canvasContext: canvas.getContext('2d'),
143                    viewport: vp,
144                    transform: dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : null
145                });
146                return task.promise.then(function () {
147                    holder.style.width  = Math.floor(vp.width) + 'px';
148                    holder.style.height = Math.floor(vp.height) + 'px';
149                    holder.innerHTML = '';
150                    holder.appendChild(canvas);
151                    holder.setAttribute('data-rendered', '1');
152                });
153            }).catch(function (e) {
154                if (window.console) console.error('[IndicePdfView] Seite ' + num, e);
155            });
156        }
157
158        var observer = new IntersectionObserver(function (entries) {
159            entries.forEach(function (entry) {
160                if (entry.isIntersecting) {
161                    observer.unobserve(entry.target);
162                    renderPage(entry.target);
163                }
164            });
165        }, { root: container, rootMargin: '600px' });
166        holders.forEach(function (h) { observer.observe(h); });
167
168        // Seitenanzeige beim Scrollen aktualisieren
169        function updatePagebar() {
170            var mark = container.scrollTop + container.clientHeight * 0.4;
171            var current = 1;
172            for (var j = 0; j < holders.length; j++) {
173                if (holders[j].offsetTop <= mark) current = j + 1;
174                else break;
175            }
176            pagebar.textContent = 'Seite ' + current + ' / ' + numPages;
177        }
178        container.addEventListener('scroll', updatePagebar, { passive: true });
179    }
180
181    /* ---------------------------- Public API ---------------------------- */
182
183    window.IndicePdfView = {
184
185        /**
186         * Soll PDF.js statt iframe genutzt werden?
187         */
188        needsCanvasViewer: function () {
189            if (navigator.pdfViewerEnabled === false) return true;   // Browser sagt explizit: kein PDF-Plugin
190            if (navigator.pdfViewerEnabled === true) return false;
191            // Fallback-Heuristik fuer aeltere Browser: Touch-Geraet
192            return ('ontouchstart' in window) && /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
193        },
194
195        /**
196         * Rendert das PDF seitenweise als Canvas in den Container
197         * (Seiten untereinander, Container scrollt vertikal).
198         */
199        render: function (container, url) {
200            injectStyles();
201            container.classList.add('ipdfv-container');
202            container.style.overflowY = 'auto';
203            container.style.display = 'block'; // ggf. Flex-Layout des Aufrufers neutralisieren
204            container.innerHTML = '<div class="ipdfv-status"><div class="ipdfv-spinner"></div>'
205                + '<span>PDF wird geladen...</span></div>';
206
207            function start() {
208                loadPdfJs().then(function (pdfjsLib) {
209                    return pdfjsLib.getDocument({ url: url, withCredentials: true }).promise;
210                }).then(function (pdf) {
211                    buildPages(container, pdf);
212                }).catch(function (e) {
213                    if (window.console) console.error('[IndicePdfView]', e);
214                    container.innerHTML = '<div class="ipdfv-status">'
215                        + '<span>Das PDF konnte nicht geladen werden.</span></div>';
216                });
217            }
218
219            // Falls der Container noch nicht gelayoutet ist (clientWidth 0),
220            // kurz auf das Layout warten (max. ~10 Frames).
221            var tries = 0;
222            (function waitForLayout() {
223                if (container.clientWidth > 0 || tries >= 10) { start(); return; }
224                tries++;
225                requestAnimationFrame(waitForLayout);
226            })();
227        }
228    };
229})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.