PageSourceSearch

https://pdfle.com/js/pptx-viewer.js?v=5aH57ij7tpZbd5Xs-2jI6nDBqg0qMEDtrXQDoguzDIY

js pdfle.com collected 2026-09-25 14:52:51 UTC 13,993 bytes, 328 lines download raw bytes

1(() => {
2    'use strict';
3
4    let ooxmlEnginePromise = null;
5    let legacyEnginePromise = null;
6    let activeState = null;
7    const maxLegacySlides = 500;
8    const maxLegacyTopLevelElements = 50000;
9    const maxLegacySvgCharacters = 40 * 1024 * 1024;
10
11    function loadOoxmlEngine() {
12        ooxmlEnginePromise ??= import('../lib/pptx-renderer/aiden0z-pptx-renderer.browser.es.js');
13        return ooxmlEnginePromise;
14    }
15
16    function loadLegacyEngine() {
17        legacyEnginePromise ??= import('../lib/web-ppt/core.js');
18        return legacyEnginePromise;
19    }
20
21    function sanitizeLinks(container) {
22        container.querySelectorAll('a').forEach(link => {
23            const href = link.getAttribute('href') || link.getAttribute('xlink:href') || '';
24            if (href && !href.startsWith('#')) {
25                try {
26                    const protocol = new URL(href, window.location.href).protocol;
27                    if (!['http:', 'https:', 'mailto:', 'tel:'].includes(protocol)) {
28                        link.removeAttribute('href');
29                        link.removeAttribute('xlink:href');
30                    }
31                } catch {
32                    link.removeAttribute('href');
33                    link.removeAttribute('xlink:href');
34                }
35            }
36            link.target = '_blank';
37            link.rel = 'noopener noreferrer nofollow';
38        });
39    }
40
41    function containsExternalCssUrl(value) {
42        return /@import/i.test(value) || /url\s*\(\s*['"]?(?!#|data:image\/|blob:)/i.test(value);
43    }
44
45    function sanitizeLegacySvg(svgMarkup, idPrefix) {
46        const parsed = new DOMParser().parseFromString(svgMarkup, 'image/svg+xml');
47        if (parsed.querySelector('parsererror') || parsed.documentElement.localName !== 'svg') {
48            throw new Error('The legacy presentation renderer produced invalid SVG.');
49        }
50
51        const root = parsed.documentElement;
52        root.querySelectorAll('script, iframe, object, embed, foreignObject').forEach(element => element.remove());
53        root.querySelectorAll('style').forEach(style => {
54            if (containsExternalCssUrl(style.textContent || '')) style.remove();
55        });
56
57        const nodes = [root, ...root.querySelectorAll('*')];
58        nodes.forEach(node => {
59            for (const attribute of [...node.attributes]) {
60                const name = attribute.name.toLowerCase();
61                if (name.startsWith('on') || (name === 'style' && containsExternalCssUrl(attribute.value))) {
62                    node.removeAttribute(attribute.name);
63                    continue;
64                }
65                if (!['src', 'href', 'xlink:href'].includes(name) || node.localName === 'a') continue;
66                const value = attribute.value.trim();
67                if (!value.startsWith('#') && !value.startsWith('blob:') && !value.startsWith('data:image/')) {
68                    node.removeAttribute(attribute.name);
69                }
70            }
71        });
72
73        const ids = new Map();
74        root.querySelectorAll('[id]').forEach(node => {
75            const original = node.id;
76            const replacement = `${idPrefix}${original.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
77            ids.set(original, replacement);
78            node.id = replacement;
79        });
80        nodes.forEach(node => {
81            for (const attribute of [...node.attributes]) {
82                let value = attribute.value;
83                ids.forEach((replacement, original) => {
84                    value = value.replaceAll(`url(#${original})`, `url(#${replacement})`);
85                    if (value === `#${original}`) value = `#${replacement}`;
86                });
87                if (value !== attribute.value) node.setAttribute(attribute.name, value);
88            }
89        });
90
91        root.setAttribute('width', '100%');
92        root.removeAttribute('height');
93        root.setAttribute('preserveAspectRatio', 'xMidYMid meet');
94        root.classList.add('pptx-legacy-slide');
95        return document.importNode(root, true);
96    }
97
98    function createLegacyViewer(engine, presentation, mount) {
99        let destroyed = false;
100        let currentSlideIndex = 0;
101        let renderSequence = 0;
102
103        function renderInto(index, container, kind) {
104            if (destroyed) return;
105            const markup = engine.renderSlideToSvg(presentation, presentation.slides[index], {
106                includeNotes: false,
107                media: 'badge',
108                showComments: false,
109                textMode: 'svg'
110            });
111            if (markup.length > maxLegacySvgCharacters) {
112                throw new Error('This slide is too complex for a safe browser preview.');
113            }
114            const prefix = `pdfle-${kind}-${index}-${renderSequence += 1}-`;
115            container.replaceChildren(sanitizeLegacySvg(markup, prefix));
116            sanitizeLinks(container);
117        }
118
119        return {
120            get currentSlideIndex() { return currentSlideIndex; },
121            slideCount: presentation.slides.length,
122            async renderSlide(index) {
123                renderInto(index, mount, 'slide');
124                currentSlideIndex = index;
125            },
126            renderThumbnailToContainer(index, container) {
127                let disposed = false;
128                const ready = Promise.resolve().then(() => {
129                    if (!disposed) renderInto(index, container, 'thumbnail');
130                });
131                return {
132                    ready,
133                    dispose() {
134                        disposed = true;
135                        container.replaceChildren();
136                    }
137                };
138            },
139            destroy() {
140                if (destroyed) return;
141                destroyed = true;
142                presentation.dispose?.();
143            }
144        };
145    }
146
147    async function openLegacyViewer(file, mount, state, isCurrent) {
148        const engine = await loadLegacyEngine();
149        const presentation = await engine.parse(file, { lazy: false });
150        if (activeState !== state || !isCurrent()) {
151            presentation.dispose?.();
152            return null;
153        }
154        if (!presentation.slides.length) {
155            presentation.dispose?.();
156            throw new Error('This presentation does not contain any slides.');
157        }
158        const elementCount = presentation.slides.reduce((total, slide) => total + slide.elements.length, 0);
159        if (presentation.slides.length > maxLegacySlides || elementCount > maxLegacyTopLevelElements) {
160            presentation.dispose?.();
161            throw new Error('This presentation is too complex for a safe browser preview.');
162        }
163        const viewer = createLegacyViewer(engine, presentation, mount);
164        await viewer.renderSlide(0);
165        return viewer;
166    }
167
168    function disposeState(state) {
169        if (!state) return;
170        state.abortController.abort();
171        state.thumbnailObserver?.disconnect();
172        state.thumbnailHandles.forEach(handle => handle?.dispose());
173        state.thumbnailHandles.clear();
174        state.viewer?.destroy();
175        state.elements.stage.onkeydown = null;
176        state.elements.previous.onclick = null;
177        state.elements.next.onclick = null;
178        state.elements.mount.replaceChildren();
179        state.elements.thumbnails.replaceChildren();
180        state.elements.counter.textContent = '';
181        state.elements.host.classList.add('d-none');
182    }
183
184    function reset() {
185        disposeState(activeState);
186        activeState = null;
187    }
188
189    function updateNavigation(state, index, scrollThumbnail = false) {
190        if (activeState !== state) return;
191        state.currentIndex = index;
192        state.elements.previous.disabled = index <= 0;
193        state.elements.next.disabled = index >= state.viewer.slideCount - 1;
194        state.elements.counter.textContent = `Slide ${index + 1} of ${state.viewer.slideCount}`;
195
196        state.elements.thumbnails.querySelectorAll('.pptx-thumbnail-button').forEach(button => {
197            const selected = Number(button.dataset.slideIndex) === index;
198            button.classList.toggle('is-active', selected);
199            button.setAttribute('aria-current', selected ? 'true' : 'false');
200            if (selected && scrollThumbnail) button.scrollIntoView({ block: 'nearest' });
201        });
202    }
203
204    async function showSlide(state, index, scrollThumbnail = true) {
205        if (activeState !== state || !state.viewer || index < 0 || index >= state.viewer.slideCount) return;
206        await state.viewer.renderSlide(index);
207        if (activeState !== state) return;
208        sanitizeLinks(state.elements.mount);
209        updateNavigation(state, index, scrollThumbnail);
210    }
211
212    function renderThumbnail(state, index, container) {
213        if (activeState !== state || state.thumbnailHandles.has(index)) return;
214        try {
215            const handle = state.viewer.renderThumbnailToContainer(index, container, { width: 150 });
216            state.thumbnailHandles.set(index, handle);
217            void handle?.ready.then(() => {
218                if (activeState === state) sanitizeLinks(container);
219            }).catch(() => {
220                container.classList.add('pptx-thumbnail-preview--error');
221            });
222        } catch {
223            container.classList.add('pptx-thumbnail-preview--error');
224        }
225    }
226
227    function buildThumbnails(state) {
228        if ('IntersectionObserver' in window) {
229            state.thumbnailObserver = new IntersectionObserver(entries => {
230                entries.forEach(entry => {
231                    if (!entry.isIntersecting) return;
232                    const preview = entry.target;
233                    state.thumbnailObserver.unobserve(preview);
234                    renderThumbnail(state, Number(preview.dataset.slideIndex), preview);
235                });
236            }, { root: state.elements.thumbnails, rootMargin: '180px 0px' });
237        }
238
239        const fragment = document.createDocumentFragment();
240        for (let index = 0; index < state.viewer.slideCount; index += 1) {
241            const button = document.createElement('button');
242            button.className = 'pptx-thumbnail-button';
243            button.type = 'button';
244            button.dataset.slideIndex = String(index);
245            button.setAttribute('aria-label', `Show slide ${index + 1}`);
246            button.setAttribute('aria-current', index === 0 ? 'true' : 'false');
247
248            const preview = document.createElement('span');
249            preview.className = 'pptx-thumbnail-preview';
250            preview.dataset.slideIndex = String(index);
251            const label = document.createElement('span');
252            label.className = 'pptx-thumbnail-label';
253            label.textContent = String(index + 1);
254            button.append(preview, label);
255            button.addEventListener('click', () => void showSlide(state, index, false));
256            fragment.append(button);
257
258            if (state.thumbnailObserver) state.thumbnailObserver.observe(preview);
259            else renderThumbnail(state, index, preview);
260        }
261        state.elements.thumbnails.append(fragment);
262    }
263
264    async function render(file, extension, elements, isCurrent) {
265        reset();
266        const state = {
267            abortController: new AbortController(),
268            currentIndex: 0,
269            elements,
270            nodeErrorCount: 0,
271            thumbnailHandles: new Map(),
272            thumbnailObserver: null,
273            viewer: null
274        };
275        activeState = state;
276        elements.host.classList.remove('d-none');
277
278        try {
279            if (extension === 'ppt' || extension === 'pps') {
280                state.viewer = await openLegacyViewer(file, elements.mount, state, isCurrent);
281                if (!state.viewer) return null;
282            } else {
283                const { PptxViewer, RECOMMENDED_ZIP_LIMITS } = await loadOoxmlEngine();
284                if (activeState !== state || !isCurrent()) return null;
285
286                state.viewer = await PptxViewer.open(file, elements.mount, {
287                    renderMode: 'slide',
288                    fitMode: 'contain',
289                    lazyMedia: true,
290                    lazySlides: true,
291                    pdfjs: false,
292                    signal: state.abortController.signal,
293                    zipLimits: RECOMMENDED_ZIP_LIMITS,
294                    onNodeError: () => { state.nodeErrorCount += 1; },
295                    onSlideRendered: (_index, element) => sanitizeLinks(element),
296                    onSlideChange: index => {
297                        if (state.viewer) updateNavigation(state, index);
298                    }
299                });
300            }
301            if (activeState !== state || !isCurrent()) {
302                disposeState(state);
303                return null;
304            }
305            if (!state.viewer.slideCount) throw new Error('This presentation does not contain any slides.');
306
307            buildThumbnails(state);
308            updateNavigation(state, state.viewer.currentSlideIndex || 0);
309            elements.previous.onclick = () => void showSlide(state, state.currentIndex - 1);
310            elements.next.onclick = () => void showSlide(state, state.currentIndex + 1);
311            elements.stage.onkeydown = event => {
312                if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
313                event.preventDefault();
314                const offset = event.key === 'ArrowLeft' ? -1 : 1;
315                void showSlide(state, state.currentIndex + offset);
316            };
317            sanitizeLinks(elements.mount);
318            return { slideCount: state.viewer.slideCount, warningCount: state.nodeErrorCount };
319        } catch (error) {
320            if (activeState === state) reset();
321            throw error;
322        }
323    }
324
325    const api = Object.freeze({ render, reset });
326    window.PdflePresentationViewer = api;
327    window.PdflePptxViewer = api;
328})();

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.