Slideshow folders can include PDFs

PDFs in a slideshow folder now play alongside images and Word documents:
a one-page PDF is shown whole, a longer one fits the width and scrolls
through its pages during docInterval (first 30 pages - rendering a huge
PDF for a few seconds on screen isn't worth the time and memory).

- kiosk-app/pdf-render.mjs renders pages to canvases with pdf.js
  (pdfjs-dist ^6.3.289, new kiosk-app dependency): no viewer toolbar, and
  the slideshow's existing auto-scroll works on it. pdf.js >= 4.2.67 is
  required (CVE-2024-4367: older versions could run script embedded in a
  crafted PDF); isEvalSupported:false too. It's a module because pdf.js
  only ships as one - confirmed to load (with its worker) from file:// in
  Electron 44.
- Hands pdf.js a copy of the bytes: it transfers the buffer it's given,
  which would break the cached copy the next time round.
- slideshow.js lists .pdf as a document type; the title-request handler
  serves any listed non-image item (still only from that folder).
- provision_install_app now copies kiosk-app/*.mjs as well - without it
  pdf-render.mjs would never reach an installed kiosk.

Tested with the real main.js on Electron 44.5.1 under Xvfb + Openbox,
app files copied with provision_install_app's own glob: a photo, a
one-page PDF, a three-page PDF and a .docx played in order, twice round;
the one-page PDF shown whole, the three-page one scrolled through all
pages. Offline bundle rebuilt with pdfjs-dist (437 MB total, +22 MB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
This commit is contained in:
Claude
2026-10-03 15:43:05 +00:00
parent 4b85ae59fc
commit fb4ee6fa62
6 changed files with 107 additions and 24 deletions
+17 -7
View File
@@ -24,6 +24,7 @@
<script src="node_modules/jszip/dist/jszip.min.js"></script>
<script src="node_modules/docx-preview/dist/docx-preview.min.js"></script>
<script src="docx-render.js"></script>
<script type="module" src="pdf-render.mjs"></script>
</head>
<body>
<div class="slide" id="a"></div>
@@ -91,7 +92,7 @@
async function prepare(layer, item) {
layer.innerHTML = '';
layer.scrollTop = 0;
layer.classList.toggle('doc', item.type === 'docx');
layer.classList.toggle('doc', item.type !== 'image');
if (item.type === 'image') {
const img = new Image();
img.style.objectFit = state.options.fit;
@@ -103,6 +104,14 @@
layer.appendChild(img);
return;
}
if (item.type === 'pdf') {
if (!window.KioskPdf) throw new Error('PDF renderer missing');
const bytes = await getDocBytes(item);
const box = document.createElement('div');
layer.appendChild(box);
await KioskPdf.render(bytes, box, window.innerWidth, window.innerHeight);
return;
}
if (!KioskDocx.available()) throw new Error('Word document viewer missing');
const bytes = await getDocBytes(item);
const zoomEl = document.createElement('div');
@@ -161,10 +170,11 @@
// Free the old slide (a rendered document can be large) once faded out.
setTimeout(() => { if (!old.classList.contains('show')) old.innerHTML = ''; },
state.options.transition * 1000 + 100);
const seconds = item.type === 'docx' ? state.options.docInterval : state.options.interval;
if (item.type === 'docx') autoScroll(back, seconds);
const isDoc = item.type !== 'image';
const seconds = isDoc ? state.options.docInterval : state.options.interval;
if (isDoc) autoScroll(back, seconds);
// A lone document still cycles, so its scroll starts over from the top.
if (order.length > 1 || item.type === 'docx') timer = setTimeout(next, seconds * 1000);
if (order.length > 1 || isDoc) timer = setTimeout(next, seconds * 1000);
}
window.kioskSlideshow = {
@@ -182,7 +192,7 @@
if (!s.items.length) {
order = [];
generation++;
showMessage(s.exists ? `No images or documents in ${s.dir}` : `Folder not found: ${s.dir}`);
showMessage(s.exists ? `No images, documents or PDFs in ${s.dir}` : `Folder not found: ${s.dir}`);
return;
}
if (sameItems && !first) return;
@@ -195,8 +205,8 @@
pos = keep;
clearTimeout(timer);
const it = order[pos];
const seconds = it.type === 'docx' ? s.options.docInterval : s.options.interval;
if (order.length > 1 || it.type === 'docx') timer = setTimeout(next, seconds * 1000);
const seconds = it.type !== 'image' ? s.options.docInterval : s.options.interval;
if (order.length > 1 || it.type !== 'image') timer = setTimeout(next, seconds * 1000);
} else {
pos = -1;
next();