feat: refine flipbook start modes and UI behavior

This commit is contained in:
Juergen committed 2026-04-13 21:34:29 +02:00
commit 9f9b1c9935
23 files changed
+1007

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
node_modules/
.DS_Store
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Mummert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+43
View File
@@ -0,0 +1,43 @@
# Flipbook Bundle (Contao 5.7)
Contao Inhaltselement zum Darstellen von PDFs als blaetterbares Flipbook.
## Features
- Reines JavaScript (ES Modules)
- `pdfjs-dist` fuer PDF-Rendering in `<canvas>`
- `flipbook-js` fuer den Blaettereffekt
- Lazy Rendering: 2-4 Seiten sofort, Rest on-demand
- Optional Vor-/Zurueck-Navigation im Modul
- Tastatursteuerung (Pfeiltasten) und Touch-Swipe
- Responsive Breite mit beibehaltenem Seitenverhaeltnis
## Installation (VCS oder Paket)
```bash
composer require mummert/flipbook-bundle
```
## Inhaltselement im Backend
Elementtyp: `Blätterbares PDF`
Felder:
- `PDF-Datei` (singleSRC / UUID)
- `Initial geladene Seiten` (2, 3 oder 4)
- `Startmodus` (zentriert oder als Doppelseite)
- `Navigation anzeigen` (optional)
## Hinweise
- Das Inhaltselement rendert PDF-Seiten als `<canvas>` innerhalb von `.c-flipbook__page`.
- Die benoetigten Open-Source-Dateien sind lokal im Bundle enthalten.
- Siehe `THIRD_PARTY_LICENSES.md` fuer Abhaengigkeiten.
## Release
```bash
git tag v1.0.0
git push origin v1.0.0
```
+17
View File
@@ -0,0 +1,17 @@
# Third-Party Licenses
This bundle contains the following third-party open source libraries in `public/assets/vendor/`:
## pdfjs-dist
- Project: https://github.com/mozilla/pdf.js
- Package: `pdfjs-dist`
- Version: `4.9.155`
- License: Apache-2.0
## flipbook-js
- Project: https://github.com/taufiqelrahman/flipbook-js
- Package: `flipbook-js`
- Version: `1.1.1`
- License: MIT
+32
View File
@@ -0,0 +1,32 @@
{
"name": "mummert/flipbook-bundle",
"description": "PDF flipbook content element for Contao 5.7 using pdfjs-dist and flipbook-js.",
"type": "contao-bundle",
"license": "MIT",
"keywords": [
"contao",
"contao-bundle",
"pdf",
"flipbook"
],
"require": {
"php": "^8.3",
"contao/core-bundle": "^5.7",
"contao/manager-plugin": "^2.0"
},
"autoload": {
"psr-4": {
"Mummert\\FlipbookBundle\\": "src/"
}
},
"extra": {
"contao-manager-plugin": "Mummert\\FlipbookBundle\\Contao\\Manager\\Plugin"
},
"prefer-stable": true,
"config": {
"allow-plugins": {
"contao-components/installer": true,
"contao/manager-plugin": true
}
}
}
+11
View File
@@ -0,0 +1,11 @@
services:
_defaults:
autowire: true
autoconfigure: true
Mummert\FlipbookBundle\:
resource: ../src/
exclude:
- ../src/DependencyInjection/
- ../src/Contao/Manager/
- ../src/FlipbookBundle.php
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_DCA']['tl_content']['palettes']['blatterbares_pdf'] = '{type_legend},type,headline;{flipbook_legend},flipbookPdfSrc,flipbookInitialPages,flipbookStartMode,flipbookShowNavigation;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},cssID;{invisible_legend:hide},invisible,start,stop';
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookPdfSrc'] = [
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookPdfSrc'],
'exclude' => true,
'inputType' => 'fileTree',
'eval' => ['fieldType' => 'radio', 'files' => true, 'filesOnly' => true, 'extensions' => 'pdf', 'mandatory' => true, 'tl_class' => 'w50'],
'sql' => ['type' => 'binary', 'length' => 16, 'notnull' => false],
];
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookInitialPages'] = [
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPages'],
'exclude' => true,
'inputType' => 'select',
'options' => ['2', '3', '4'],
'reference' => &$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPagesOptions'],
'eval' => ['mandatory' => true, 'includeBlankOption' => false, 'tl_class' => 'w50'],
'sql' => ['type' => 'string', 'length' => 1, 'default' => '4'],
];
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookStartMode'] = [
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookStartMode'],
'exclude' => true,
'inputType' => 'select',
'options' => ['center', 'spread'],
'reference' => &$GLOBALS['TL_LANG']['tl_content']['flipbookStartModeOptions'],
'eval' => ['mandatory' => true, 'includeBlankOption' => false, 'tl_class' => 'w50'],
'sql' => ['type' => 'string', 'length' => 16, 'default' => 'center'],
];
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookShowNavigation'] = [
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'],
'exclude' => true,
'inputType' => 'checkbox',
'eval' => ['tl_class' => 'w50 m12'],
'sql' => ['type' => 'string', 'length' => 1, 'fixed' => true, 'default' => '1'],
];
+5
View File
@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['CTE']['blatterbares_pdf'] = ['Blätterbares PDF', 'Zeigt ein PDF als blätterbares Flipbook mit Lazy-Rendering an.'];
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_content']['flipbook_legend'] = 'Blätterbares PDF';
$GLOBALS['TL_LANG']['tl_content']['flipbookPdfSrc'] = ['PDF-Datei', 'Bitte waehlen Sie die PDF-Datei aus, die als Flipbook angezeigt werden soll.'];
$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPages'] = ['Initial geladene Seiten', 'Anzahl der Seiten, die beim Laden direkt gerendert werden (Rest lazy).'];
$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPagesOptions'] = [
'2' => '2 Seiten',
'3' => '3 Seiten',
'4' => '4 Seiten',
];
$GLOBALS['TL_LANG']['tl_content']['flipbookStartMode'] = ['Startmodus', 'Legt fest, ob das Flipbook zentriert oder mit Doppelseite startet.'];
$GLOBALS['TL_LANG']['tl_content']['flipbookStartModeOptions'] = [
'center' => 'Zentriert starten',
'spread' => 'als Doppelseite starten',
];
$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'] = ['Navigation anzeigen', 'Zeigt Vor-/Zurueck-Buttons unter dem Flipbook an.'];
+5
View File
@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['CTE']['blatterbares_pdf'] = ['Flippable PDF', 'Displays a PDF as a page-turning flipbook with lazy rendering.'];
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_content']['flipbook_legend'] = 'Flippable PDF';
$GLOBALS['TL_LANG']['tl_content']['flipbookPdfSrc'] = ['PDF file', 'Please select the PDF file to display as a flipbook.'];
$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPages'] = ['Initially rendered pages', 'Number of pages rendered immediately on load (remaining pages are lazy-rendered).'];
$GLOBALS['TL_LANG']['tl_content']['flipbookInitialPagesOptions'] = [
'2' => '2 pages',
'3' => '3 pages',
'4' => '4 pages',
];
$GLOBALS['TL_LANG']['tl_content']['flipbookStartMode'] = ['Start mode', 'Defines whether the flipbook starts centered or as a double-page spread.'];
$GLOBALS['TL_LANG']['tl_content']['flipbookStartModeOptions'] = [
'center' => 'Start centered',
'spread' => 'Start with double-page spread',
];
$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'] = ['Show navigation', 'Displays previous/next buttons below the flipbook.'];
View File
Whitespace-only changes.
@@ -0,0 +1,32 @@
{% set hasPdf = pdfUrl|default('') is not empty %}
<div
class="ce-blatterbares-pdf mod-pdf-flipbook"
data-pdf-flipbook-element="1"
data-pdf-url="{{ pdfUrl|default('')|e('html_attr') }}"
data-initial-pages="{{ initialRenderPages|default(4)|e('html_attr') }}"
data-start-mode="{{ startMode|default('center')|e('html_attr') }}"
data-show-navigation="{{ showNavigation ? '1' : '0' }}"
tabindex="0"
>
<div class="mod-pdf-flipbook__status" data-flipbook-loader="1" aria-live="polite">PDF wird geladen ...</div>
<div class="mod-pdf-flipbook__stage" data-flipbook-stage="1">
<div id="flipbook-{{ data.id|default(random()) }}" class="c-flipbook" data-flipbook-book="1"></div>
</div>
{% if showNavigation %}
<div class="mod-pdf-flipbook__controls" aria-label="Flipbook Navigation">
<button type="button" data-flipbook-prev="1">Zurück</button>
<button type="button" data-flipbook-next="1">Weiter</button>
</div>
{% endif %}
{% if not hasPdf %}
<p class="mod-pdf-flipbook__error">Keine PDF-Datei konfiguriert.</p>
{% endif %}
</div>
<link rel="stylesheet" href="/bundles/flipbook/assets/vendor/flipbook.min.css?v=20260414d">
<link rel="stylesheet" href="/bundles/flipbook/assets/flipbook-module.css?v=20260414d">
<script type="module" src="/bundles/flipbook/assets/flipbook-module.js?v=20260414d"></script>
+84
View File
@@ -0,0 +1,84 @@
.mod-pdf-flipbook {
--flipbook-bg: #e0e0e0;
--flipbook-text: #302d29;
--flipbook-accent: #6f4f28;
position: relative;
padding: 3rem;
background: var(--flipbook-bg);
}
.mod-pdf-flipbook:focus-visible {
outline: 2px solid var(--flipbook-accent);
outline-offset: 3px;
}
.mod-pdf-flipbook__status {
margin-bottom: 0.8rem;
font-size: 0.92rem;
color: var(--flipbook-text);
}
.mod-pdf-flipbook__status.is-hidden {
display: none;
}
.mod-pdf-flipbook__status.is-error {
color: #9f1f1f;
}
.mod-pdf-flipbook__stage {
position: relative;
width: 100%;
margin: 0 auto;
background: #e0e0e0;
}
.mod-pdf-flipbook .c-flipbook {
max-width: 100%;
}
.mod-pdf-flipbook .c-flipbook__page {
background: #fefdf9;
overflow: hidden;
border: 1px solid #e2dbcc;
}
.mod-pdf-flipbook .c-flipbook__page canvas {
display: block;
width: 100%;
height: 100%;
}
.mod-pdf-flipbook__page-loader {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
color: #6f675a;
background: repeating-linear-gradient(
-45deg,
rgba(184, 176, 160, 0.18),
rgba(184, 176, 160, 0.18) 14px,
rgba(239, 235, 227, 0.28) 14px,
rgba(239, 235, 227, 0.28) 28px
);
}
.mod-pdf-flipbook__controls {
display: flex;
gap: 0.6rem;
margin-top: 0.9rem;
}
.mod-pdf-flipbook__error {
margin-top: 0.8rem;
color: #9f1f1f;
}
@media (max-width: 767px) {
.mod-pdf-flipbook__controls {
justify-content: space-between;
}
}
+490
View File
@@ -0,0 +1,490 @@
const MODULE_SELECTOR = '[data-pdf-flipbook-element="1"]';
const PDF_MODULE_URL = '/bundles/flipbook/assets/vendor/pdf.min.mjs';
const PDF_WORKER_URL = '/bundles/flipbook/assets/vendor/pdf.worker.min.mjs';
const FLIPBOOK_MODULE_URL = '/bundles/flipbook/assets/vendor/flipbook.esm.min.js';
const INIT_MARKER = 'pdfFlipbookInitialized';
const BOOTSTRAP_MARKER = '__mummertPdfFlipbookBootstrapBound';
let dependenciesPromise;
let moduleCounter = 0;
const loadDependencies = async () => {
if (dependenciesPromise) {
return dependenciesPromise;
}
dependenciesPromise = Promise.all([
import(PDF_MODULE_URL),
import(FLIPBOOK_MODULE_URL),
]).then(([pdfjsLib, flipbookModule]) => {
pdfjsLib.GlobalWorkerOptions.workerSrc = PDF_WORKER_URL;
return {
pdfjsLib,
FlipBook: flipbookModule.default,
};
});
return dependenciesPromise;
};
class PdfFlipbookModule {
constructor(root, dependencies) {
const parsedInitialPages = Number.parseInt(root.dataset.initialPages || '4', 10);
this.root = root;
this.dependencies = dependencies;
this.pdfUrl = (root.dataset.pdfUrl || '').trim();
this.initialPages = Number.isFinite(parsedInitialPages)
? Math.min(4, Math.max(2, parsedInitialPages))
: 4;
this.showNavigation = root.dataset.showNavigation === '1';
this.startMode = (root.dataset.startMode === 'spread' || root.dataset.startMode === 'cover')
? 'spread'
: 'center';
this.loader = root.querySelector('[data-flipbook-loader="1"]');
this.stage = root.querySelector('[data-flipbook-stage="1"]');
this.bookElement = root.querySelector('[data-flipbook-book="1"]');
this.nextButton = this.showNavigation ? root.querySelector('[data-flipbook-next="1"]') : null;
this.prevButton = this.showNavigation ? root.querySelector('[data-flipbook-prev="1"]') : null;
this.pdf = null;
this.flipbook = null;
this.pageElements = new Map();
this.renderedPages = new Set();
this.pendingPages = new Set();
this.renderQueue = [];
this.renderInProgress = false;
this.resizeTimer = null;
this.touchStart = null;
this.aspectRatio = 1.4142;
this.pageWidth = 0;
this.pageHeight = 0;
this.pageGap = 2;
this.totalPages = 0;
this.instanceId = `pdf-flipbook-${++moduleCounter}`;
}
async init() {
if (!this.bookElement || !this.stage) {
return;
}
if (!this.pdfUrl) {
this.setStatus('Keine PDF-Datei gefunden.', true);
return;
}
this.bookElement.id = this.instanceId;
try {
this.setStatus('PDF wird geladen ...');
await this.loadPdf();
await this.resolveInitialAspectRatio();
this.buildPageSkeleton();
this.updateLayout();
await this.renderInitialPages();
this.initializeFlipbook();
this.bindKeyboard();
this.bindTouchSwipe();
this.bindResize();
this.queuePages(this.getLazyCandidates());
this.setStatus('');
} catch {
this.setStatus('PDF konnte nicht geladen werden.', true);
}
}
async loadPdf() {
const loadingTask = this.dependencies.pdfjsLib.getDocument({
url: this.pdfUrl,
useWorkerFetch: true,
isEvalSupported: false,
});
this.pdf = await loadingTask.promise;
this.totalPages = Number(this.pdf.numPages || 0);
if (this.totalPages <= 0) {
throw new Error('The selected PDF has no pages.');
}
}
async resolveInitialAspectRatio() {
const firstPage = await this.pdf.getPage(1);
const viewport = firstPage.getViewport({ scale: 1 });
if (viewport.width > 0 && viewport.height > 0) {
this.aspectRatio = viewport.height / viewport.width;
}
}
buildPageSkeleton() {
const visualPageCount = this.totalPages % 2 === 0 ? this.totalPages : this.totalPages + 1;
this.bookElement.innerHTML = '';
this.pageElements.clear();
for (let pageNumber = 1; pageNumber <= visualPageCount; pageNumber += 1) {
const page = document.createElement('div');
const loader = document.createElement('div');
page.className = 'c-flipbook__page';
page.dataset.pageNumber = String(pageNumber);
loader.className = 'mod-pdf-flipbook__page-loader';
loader.textContent = pageNumber <= this.totalPages ? `Seite ${pageNumber} wird gerendert ...` : 'Leere Seite';
page.appendChild(loader);
if (pageNumber > this.totalPages) {
page.dataset.empty = '1';
}
this.bookElement.appendChild(page);
if (pageNumber <= this.totalPages) {
this.pageElements.set(pageNumber, page);
}
}
}
updateLayout() {
const stageWidth = Math.max(this.stage.clientWidth || 0, 282);
this.pageWidth = Math.max(Math.floor((stageWidth - this.pageGap) / 2), 140);
this.pageHeight = Math.max(Math.floor(this.pageWidth * this.aspectRatio), 180);
this.stage.style.height = `${this.pageHeight}px`;
this.bookElement.style.height = `${this.pageHeight}px`;
this.pageElements.forEach((pageElement) => {
pageElement.style.width = `${this.pageWidth}px`;
pageElement.style.height = `${this.pageHeight}px`;
});
}
async renderInitialPages() {
const limit = Math.min(this.initialPages, this.totalPages);
for (let pageNumber = 1; pageNumber <= limit; pageNumber += 1) {
await this.renderPage(pageNumber);
}
}
initializeFlipbook() {
const startsWithDoubleSpread = this.startMode === 'spread';
this.flipbook = new this.dependencies.FlipBook(this.bookElement, {
nextButton: this.nextButton,
previousButton: this.prevButton,
canClose: !startsWithDoubleSpread,
arrowKeys: false,
initialActivePage: 0,
initialCall: false,
width: '100%',
height: `${this.pageHeight}px`,
onPageTurn: () => {
this.queuePages(this.getLazyCandidates());
},
});
}
bindKeyboard() {
this.root.addEventListener('pointerdown', () => {
this.root.focus();
});
this.root.addEventListener('keydown', (event) => {
if (!this.flipbook) {
return;
}
if (event.key === 'ArrowRight') {
this.flipbook.turnPage('forward');
event.preventDefault();
}
if (event.key === 'ArrowLeft') {
this.flipbook.turnPage('back');
event.preventDefault();
}
});
}
bindTouchSwipe() {
this.bookElement.addEventListener('touchstart', (event) => {
const touch = event.changedTouches && event.changedTouches[0];
if (!touch) {
return;
}
this.touchStart = {
x: touch.clientX,
y: touch.clientY,
};
}, { passive: true });
this.bookElement.addEventListener('touchend', (event) => {
if (!this.flipbook || !this.touchStart) {
this.touchStart = null;
return;
}
const touch = event.changedTouches && event.changedTouches[0];
if (!touch) {
this.touchStart = null;
return;
}
const deltaX = touch.clientX - this.touchStart.x;
const deltaY = touch.clientY - this.touchStart.y;
const horizontalThreshold = 40;
const verticalLimit = 60;
if (Math.abs(deltaX) >= horizontalThreshold && Math.abs(deltaY) < verticalLimit) {
if (deltaX < 0) {
this.flipbook.turnPage('forward');
} else {
this.flipbook.turnPage('back');
}
}
this.touchStart = null;
}, { passive: true });
}
bindResize() {
const onResize = () => {
window.clearTimeout(this.resizeTimer);
this.resizeTimer = window.setTimeout(() => {
if (!this.pdf) {
return;
}
const previousHeight = this.pageHeight;
this.updateLayout();
if (Math.abs(previousHeight - this.pageHeight) < 2) {
return;
}
this.bookElement.style.height = `${this.pageHeight}px`;
const rerender = Array.from(this.renderedPages);
this.renderedPages.clear();
rerender.forEach((pageNumber) => {
const page = this.pageElements.get(pageNumber);
if (page) {
const oldCanvas = page.querySelector('canvas');
if (oldCanvas) {
oldCanvas.remove();
}
}
});
this.queuePages(rerender, true);
}, 120);
};
if ('ResizeObserver' in window) {
const observer = new ResizeObserver(onResize);
observer.observe(this.stage);
} else {
window.addEventListener('resize', onResize, { passive: true });
}
}
getLazyCandidates() {
const activePages = Array.from(this.bookElement.querySelectorAll('.c-flipbook__page.is-active'));
const candidates = new Set();
if (activePages.length === 0) {
const fallbackFrom = Math.min(this.initialPages + 1, this.totalPages);
const fallbackTo = Math.min(fallbackFrom + 2, this.totalPages);
for (let pageNumber = fallbackFrom; pageNumber <= fallbackTo; pageNumber += 1) {
candidates.add(pageNumber);
}
return Array.from(candidates);
}
activePages.forEach((activePage) => {
const currentPage = Number(activePage.dataset.pageNumber || 0);
for (let offset = -1; offset <= 3; offset += 1) {
const pageNumber = currentPage + offset;
if (pageNumber >= 1 && pageNumber <= this.totalPages) {
candidates.add(pageNumber);
}
}
});
return Array.from(candidates);
}
queuePages(pageNumbers, prioritize = false) {
for (const pageNumber of pageNumbers) {
if (!Number.isInteger(pageNumber) || pageNumber < 1 || pageNumber > this.totalPages) {
continue;
}
if (this.renderedPages.has(pageNumber) || this.pendingPages.has(pageNumber)) {
continue;
}
this.pendingPages.add(pageNumber);
if (prioritize) {
this.renderQueue.unshift(pageNumber);
} else {
this.renderQueue.push(pageNumber);
}
}
this.processRenderQueue();
}
async processRenderQueue() {
if (this.renderInProgress) {
return;
}
this.renderInProgress = true;
while (this.renderQueue.length > 0) {
const pageNumber = this.renderQueue.shift();
if (!pageNumber) {
continue;
}
this.pendingPages.delete(pageNumber);
try {
await this.renderPage(pageNumber);
} catch {
continue;
}
}
this.renderInProgress = false;
}
async renderPage(pageNumber) {
if (this.renderedPages.has(pageNumber)) {
return;
}
const pageElement = this.pageElements.get(pageNumber);
if (!pageElement) {
return;
}
const page = await this.pdf.getPage(pageNumber);
const viewportAtScale1 = page.getViewport({ scale: 1 });
const scale = this.pageWidth / viewportAtScale1.width;
const viewport = page.getViewport({ scale });
const devicePixelRatio = window.devicePixelRatio || 1;
const outputScale = Math.max(1, devicePixelRatio);
const existingCanvas = pageElement.querySelector('canvas');
if (existingCanvas) {
existingCanvas.remove();
}
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d', { alpha: false });
if (!context) {
throw new Error('Could not create canvas 2D context.');
}
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = `${Math.floor(viewport.width)}px`;
canvas.style.height = `${Math.floor(viewport.height)}px`;
const renderTask = page.render({
canvasContext: context,
viewport,
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
intent: 'display',
});
await renderTask.promise;
const loader = pageElement.querySelector('.mod-pdf-flipbook__page-loader');
if (loader) {
loader.remove();
}
pageElement.appendChild(canvas);
this.renderedPages.add(pageNumber);
}
setStatus(message, isError = false) {
if (!this.loader) {
return;
}
if (!message) {
this.loader.classList.add('is-hidden');
this.loader.classList.remove('is-error');
this.loader.textContent = '';
return;
}
this.loader.classList.remove('is-hidden');
this.loader.classList.toggle('is-error', isError);
this.loader.textContent = message;
}
}
const run = async () => {
const modules = Array.from(document.querySelectorAll(MODULE_SELECTOR))
.filter((moduleElement) => moduleElement.dataset[INIT_MARKER] !== '1');
if (modules.length === 0) {
return;
}
const dependencies = await loadDependencies();
await Promise.all(modules.map(async (moduleElement) => {
moduleElement.dataset[INIT_MARKER] = '1';
const module = new PdfFlipbookModule(moduleElement, dependencies);
await module.init();
}));
};
if (window[BOOTSTRAP_MARKER]) {
run().catch(() => {
});
} else {
window[BOOTSTRAP_MARKER] = true;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
run().catch(() => {
});
}, { once: true });
} else {
run().catch(() => {
});
}
}
+1
View File
@@ -0,0 +1 @@
function getDefaultExportFromCjs(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var hasRequiredFlipBook,FlipBook$1={exports:{}},FlipBook_1=FlipBook$1.exports;function requireFlipBook(){return hasRequiredFlipBook||(hasRequiredFlipBook=1,function(e){class s{constructor(e,t={}){if(!(this instanceof s))return new s(e,t);this.options={nextButton:null,previousButton:null,canClose:!1,arrowKeys:!0,initialActivePage:0,onPageTurn:()=>{},initialCall:!1,width:"100%",height:"283px",...t},this.classNames={page:"c-flipbook__page",hiddenCover:"hidden-cover",atFrontCover:"at-front-cover",atBackCover:"at-rear-cover",firstPage:"first-page",lastPage:"last-page",isReady:"is-ready",isActive:"is-active",isCalling:"is-calling",isAnimating:"is-animating",wasActive:"was-active"},this.Modernizr=window.Modernizr||{csstransforms3d:!0},"boolean"!=typeof this.Modernizr.preserve3d&&(this.Modernizr.preserve3d=!0),this.el="string"==typeof e?document.getElementById(e):e,this.el.style.width=this.options.width,this.el.style.height=this.options.height,this.el.setAttribute("data-useragent",navigator.userAgent),this.pages=this.el.querySelectorAll(`.${this.classNames.page}, .${this.classNames.hiddenCover}`),this.options.canClose&&(0===this.options.initialActivePage&&this.el.classList.add(this.classNames.atFrontCover),this.pages.item(0).classList.add(this.classNames.firstPage),this.pages.item(this.pages.length-1).classList.add(this.classNames.lastPage)),this.init()}getActivePages(){return this.pages=this.el.querySelectorAll(`.${this.classNames.page}, .${this.classNames.hiddenCover}`),Array.from(this.pages).reduce((e,s,t)=>(s.classList.contains(this.classNames.isActive)&&e.push(t),e),[])}init(){const{el:e,options:t,classNames:i}=this;e.classList.add(i.isReady);const a=t.canClose?"even":"odd",o=t.canClose?"odd":"even";let r,n=Array.from(e.querySelectorAll(`.${i.page}:nth-child(${a})`)),l=Array.from(e.querySelectorAll(`.${i.page}:nth-child(${o})`)),c=t.initialActivePage;if(1&c&&(c-=1),t.canClose)0===t.initialActivePage&&this.pages.item(0).classList.add(i.isActive);else{const s=document.createElement("div");s.classList.add(i.hiddenCover),e.prepend(s.cloneNode()),e.append(s.cloneNode())}(0!==t.initialActivePage&&t.canClose||!t.canClose)&&Array.from(this.pages).forEach((e,s)=>{s!==c&&s!==c+1||e.classList.add(i.isActive)}),t.initialCall&&l[0]&&(r=s.setupPageCalls(l,i,r)),t.previousButton&&(n=[...n,t.previousButton]),t.nextButton&&(l=[...l,t.nextButton]),n.forEach(e=>e.addEventListener("click",()=>this.turnPage("back"))),l.forEach(e=>e.addEventListener("click",()=>{this.turnPage("forward"),t.initialCall&&clearInterval(r)})),t.arrowKeys&&s.handleArrowKeys(t,r,this.turnPage.bind(this),this.Modernizr)}isLastPage(){const e=this.getActivePages()[0];return this.pages.length-1===e}isFirstPage(){return 0===this.getActivePages()[0]}turnPage(e){const{el:t,options:i,classNames:a,Modernizr:o}=this,r=t.querySelectorAll(`.${a.page}.${a.isActive}`);let n=t.querySelectorAll(`.${a.page}.${a.isAnimating}`);const l=t.querySelectorAll(`.${a.page}, .${a.hiddenCover}`);if(!o.preserve3d&&n.length>2)return;const c=this.getActivePages()[0],d=c+1,h=c===(i.canClose?0:1)&&"back"===e,g=d===(i.canClose?this.pages.length-1:this.pages.length-2)&&"forward"===e;if(h||g)return;let p,v;if("number"==typeof e){const s=1&e,t=(i.canClose?s:!s)?e:e+1,a=t-1;if(a===c)return;a>d?(e="forward",p=a,v=p+1):(e="back",p=t,v=p-1)}else p="forward"===e?d+1:c-1,v="forward"===e?d+2:c-2;"forward"===e&&2===p&&(p=1,v=2);const u="back"===e?r.item(0):r.item(1);let f=l.item(p),m=[f];-1!==v&&(m=[...m,l.item(v)]),n=[f,u],r.forEach(e=>{e.classList.remove(a.isActive),e.classList.add(a.wasActive)}),m.forEach(e=>{e&&e.classList.add(a.isActive)}),o.csstransforms3d&&n.forEach(e=>{e&&e.classList.add(a.isAnimating)}),s.handleAnimationEnd(n,r,this),i.canClose&&s.handleAtCovers(m,a,e,t),i.onPageTurn(t,{pagesActive:r,children:l})}static setupPageCalls(e,s,t){const i=()=>{e[0].classList.add(s.isCalling),setTimeout(()=>{e[0].classList.remove(s.isCalling)},900)};return setTimeout(i,500),setInterval(i,3e3)}static handleArrowKeys(e,s,t,i){document.addEventListener("keydown",({keyCode:a})=>{const o=i.csstransforms3d?39:37;a===(i.csstransforms3d?37:39)&&t("back"),a===o&&(t("forward"),e.initialCall&&clearInterval(s))})}static handleAnimationEnd(e,s,t){e.forEach(i=>{if(!i)return;["webkitTransitionEnd","oTransitionEnd","msTransitionEnd","transitionend"].forEach(a=>{i.addEventListener(a,()=>{e.forEach(e=>{e&&e.classList.remove(t.classNames.isAnimating)}),s.forEach(e=>{e.classList.remove(t.classNames.wasActive)})})})})}static handleAtCovers(e,s,t,i){const a=e[e.length-1],o=e[0]&&e[0].classList.contains(s.lastPage),r=a&&a.classList.contains(s.firstPage);"back"===t&&r?(i.classList.remove(s.atBackCover),i.classList.add(s.atFrontCover)):"forward"===t&&o?(i.classList.remove(s.atFrontCover),i.classList.add(s.atBackCover)):(i.classList.remove(s.atBackCover),i.classList.remove(s.atFrontCover))}}"undefined"Line truncated
+1
View File
@@ -0,0 +1 @@
.c-flipbook{perspective:2200px;transform-style:preserve-3d;opacity:1;height:200px;position:absolute;left:0;transition:left .7s;top:0}.c-flipbook.at-front-cover{left:-25%}.c-flipbook.at-rear-cover{left:25%}.c-flipbook:not(.is-ready) *{transition:none!important}.c-flipbook:after{content:'';display:table;clear:both}.c-flipbook[data-useragent*='MSIE 10.0'] .c-flipbook__page{opacity:0}.c-flipbook[data-useragent*='MSIE 10.0'] .c-flipbook__page.is-active{transition:opacity .9s ease,transform .9s ease;opacity:1}.c-flipbook[data-useragent*='MSIE 10.0'] .c-flipbook__page.was-active{transition-delay:2s;transition:opacity .9s ease,transform .9s ease;opacity:0}.is-calling{transform:rotateY(-20deg)!important}.c-flipbook__page{cursor:pointer;overflow:hidden;position:absolute;width:50%;background:#efeef4;backface-visibility:hidden;transform:rotateY(0);user-select:none;transition:transform .9s ease}.c-flipbook__page.is-active{z-index:2}.c-flipbook__page.was-active{z-index:1}.c-flipbook__page.is-animating:nth-child(odd){z-index:4}.c-flipbook__page.is-animating:nth-child(odd)~.c-flipbook__page.is-animating{z-index:3}.c-flipbook__page.is-animating+.c-flipbook__page:not(.is-animating):nth-child(odd){z-index:1}.c-flipbook__page:nth-child(2n){transform-origin:100%;left:0;border-radius:6px 0 0 6px}.c-flipbook__page:nth-child(2n).is-active{transform:rotateY(10deg)}.c-flipbook__page:nth-child(2n).is-active:hover{transform:rotateY(15deg)}.c-flipbook__page:nth-child(2n):not(.last-page){border-right:none}.c-flipbook__page:nth-child(2n).is-active:hover{transform:rotateY(5deg)}.c-flipbook__page:nth-child(odd){transform-origin:0;right:0;transform:rotateY(-180deg);border-radius:0 6px 6px 0}.c-flipbook__page:nth-child(odd).is-active{transform:rotateY(-10deg)}.c-flipbook__page:nth-child(odd).is-active:hover{transform:rotateY(-15deg)}.c-flipbook__page:nth-child(odd):not(.first-page){border-left:none}.c-flipbook__page:nth-child(odd).is-active~.c-flipbook__page:nth-child(2n){transform:rotateY(180deg)}.c-flipbook__page:nth-child(odd).is-active~.c-flipbook__page:nth-child(odd){transform:rotateY(0)}.c-flipbook__page:nth-child(odd).is-active:hover{transform:rotateY(-5deg)}.c-flipbook__page.is-active:not(:hover){transform:rotateY(0)}.c-flipbook__page:before{content:'';position:absolute;z-index:3;right:0;width:100%;height:100%;background-size:100% 100%}.no-csstransforms3d .c-flipbook__page{display:none}.no-csstransforms3d .c-flipbook__page.is-active{display:block;position:relative;float:left}.c-flipbook-image{position:relative;z-index:2;height:auto;width:100%;display:block;pointer-events:none}.c-flipbook__page .ss-loading{font-size:2rem;position:absolute;z-index:1;top:0;bottom:0;width:100%;display:flex}.c-flipbook__page .ss-loading:before{display:flex;align-items:center;justify-content:center;width:100%}@supports (transition:transform 0.9s ease) and (not (-ms-ime-align:auto)){.c-flipbook__page{transition:transform .9s ease}}
+28
View File
@@ -0,0 +1,28 @@
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/**
* pdfjsVersion = 5.6.205
* pdfjsBuild = ada343803
*/const t=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),e=[.001,0,0,.001,0,0],i=1.35,n=1,s=2,r=4,a=16,o=32,l=64,h=128,d=256,c={DISABLE:0,ENABLE:1,ENABLE_FORMS:2,ENABLE_STORAGE:3},u="pdfjs_internal_editor_",p={DISABLE:-1,NONE:0,FREETEXT:3,HIGHLIGHT:9,STAMP:13,INK:15,POPUP:16,SIGNATURE:101,COMMENT:102},g={RESIZE:1,CREATE:2,FREETEXT_SIZE:11,FREETEXT_COLOR:12,FREETEXT_OPACITY:13,INK_COLOR:21,INK_THICKNESS:22,INK_OPACITY:23,HIGHLIGHT_COLOR:31,HIGHLIGHT_THICKNESS:32,HIGHLIGHT_FREE:33,HIGHLIGHT_SHOW_ALL:34,DRAW_STEP:41},m={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},f=1,b=2,y=0,w=1,A=2,v=3,x=3,E=4,C={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},T={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},S=1,_=2,D=3,P=4,M=5,k={ERRORS:0,WARNINGS:1,INFOS:5},I={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotation:80,endAnnotation:81,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91,setStrokeTransparent:92,setFillTransparent:93,rawFillPath:94},F=0,B=1,R=2,L=3,O=4,N={NEED_PASSWORD:1,INCORRECT_PASSWORD:2};let U=k.WARNINGS;function setVerbosityLevel(t){Number.isInteger(t)&&(U=t)}function getVerbosityLevel(){return U}function info(t){U>=k.INFOS&&console.info(`Info: ${t}`)}function warn(t){U>=k.WARNINGS&&console.warn(`Warning: ${t}`)}function unreachable(t){throw new Error(t)}function assert(t,e){t||unreachable(e)}function createValidAbsoluteUrl(t,e=null,i=null){if(!t)return null;if(i&&"string"==typeof t){if(i.addDefaultProtocol&&t.startsWith("www.")){const e=t.match(/\./g);e?.length>=2&&(t=`http://${t}`)}if(i.tryConvertEncoding)try{t=function stringToUTF8String(t){return decodeURIComponent(escape(t))}(t)}catch{}}const n=e?URL.parse(t,e):URL.parse(t);return function _isValidProtocol(t){switch(t?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(n)?n:null}function updateUrlHash(t,e,i=!1){const n=URL.parse(t);if(n){n.hash=e;return n.href}return i&&createValidAbsoluteUrl(t,"http://example.com")?t.split("#",1)[0]+""+(e?`#${e}`:""):""}function stripPath(t){return t.substring(t.lastIndexOf("/")+1)}function shadow(t,e,i,n=!1){Object.defineProperty(t,e,{value:i,enumerable:!n,configurable:!0,writable:!1});return i}const H=function BaseExceptionClosure(){function BaseException(t,e){this.message=t;this.name=e}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends H{constructor(t,e){super(t,"PasswordException");this.code=e}}class UnknownErrorException extends H{constructor(t,e){super(t,"UnknownErrorException");this.details=e}}class InvalidPDFException extends H{constructor(t){super(t,"InvalidPDFException")}}class ResponseException extends H{constructor(t,e,i){super(t,"ResponseException");this.status=e;this.missing=i}}class FormatError extends H{constructor(t){super(t,"FormatError")}}class AbortException extends H{constructor(t){super(t,"AbortException")}}function stringToBytes(t){"string"!=typeof t&&unreachable("Invalid argument for stringToBytes");const e=t.length,i=new Uint8Array(e);for(let n=0;n<e;++n)i[n]=255&t.charCodeAt(n);return i}class FeatureTest{static get isLittleEndian(){return shadow(this,"isLittleEndian",function isLittleEndian(){const t=new Line truncated
/*webpackIgnore: true*/
/*@vite-ignore*/
this.workerSrc)).WorkerMessageHandler})())}}class WorkerTransport{downloadInfoCapability=Promise.withResolvers();#mr=null;#fr=new Map;#br=null;#yr=new Map;#wr=new Map;#Ar=new Map;#vr=null;constructor(t,e,i,n,s,r){this.messageHandler=t;this.loadingTask=e;this.#br=i;this.commonObjs=new PDFObjects;this.fontLoader=new FontLoader({ownerDocument:n.ownerDocument,styleElement:n.styleElement});this.enableHWA=n.enableHWA;this.loadingParams=n.loadingParams;this._params=n;this.canvasFactory=s.canvasFactory;this.filterFactory=s.filterFactory;this.binaryDataFactory=s.binaryDataFactory;this.pagesMapper=r;this.destroyed=!1;this.destroyCapability=null;this.setupMessageHandler()}updatePage(t){const{_pageIndex:e}=t;this.#yr.set(e,t);this.#wr.set(e,Promise.resolve(t))}#xr(t,e=null){return this.#fr.getOrInsertComputed(t,()=>this.messageHandler.sendWithPromise(t,e))}#Qn({loaded:t,total:e}){this.loadingTask.onProgress?.({loaded:t,total:e,percent:e?MathClamp(Math.round(t/e*100),0,100):NaN})}get annotationStorage(){return shadow(this,"annotationStorage",new AnnotationStorage)}getRenderingIntent(t,e=c.ENABLE,i=null,u=!1,p=!1){let g=s,m=Q;switch(t){case"any":g=n;break;case"display":break;case"print":g=r;break;default:warn(`getRenderingIntent - invalid intent: ${t}`)}const f=g&r&&i instanceof PrintAnnotationStorage?i:this.annotationStorage;switch(e){case c.DISABLE:g+=l;break;case c.ENABLE:break;case c.ENABLE_FORMS:g+=a;break;case c.ENABLE_STORAGE:g+=o;m=f.serializable;break;default:warn(`getRenderingIntent - invalid annotationMode: ${e}`)}u&&(g+=h);p&&(g+=d);const{ids:b,hash:y}=f.modifiedIds;return{renderingIntent:g,cacheKey:[g,m.hash,y].join("_"),annotationStorageSerializable:m,modifiedIds:b}}destroy(){if(this.destroyCapability)return this.destroyCapability.promise;this.destroyed=!0;this.destroyCapability=Promise.withResolvers();this.#vr?.reject(new Error("Worker was destroyed during onPassword callback"));const t=[];for(const e of this.#yr.values())t.push(e._destroy());this.#yr.clear();this.#wr.clear();this.#Ar.clear();this.hasOwnProperty("annotationStorage")&&this.annotationStorage.resetModified();const e=this.messageHandler.sendWithPromise("Terminate",null);t.push(e);Promise.all(t).then(()=>{this.commonObjs.clear();this.fontLoader.clear();this.#fr.clear();this.filterFactory.destroy();TextLayer.cleanup();this.#br?.cancelAllRequests(new AbortException("Worker was terminated."));this.messageHandler?.destroy();this.messageHandler=null;this.destroyCapability.resolve()},this.destroyCapability.reject);return this.destroyCapability.promise}setupMessageHandler(){const{messageHandler:t,loadingTask:e}=this;t.on("GetReader",(t,e)=>{assert(this.#br,"GetReader - no `BasePDFStream` instance available.");this.#mr=this.#br.getFullReader();this.#mr.onProgress=t=>this.#Qn(t);e.onPull=()=>{this.#mr.read().then(function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}}).catch(t=>{e.error(t)})};e.onCancel=t=>{this.#mr.cancel(t);e.ready.catch(t=>{if(!this.destroyed)throw t})}});t.on("ReaderHeadersReady",async t=>{await this.#mr.headersReady;const{isStreamingSupported:e,isRangeSupported:i,contentLength:n}=this.#mr;e&&i&&(this.#mr.onProgress=null);return{isStreamingSupported:e,isRangeSupported:i,contentLength:n}});t.on("GetRangeReader",(t,e)=>{assert(this.#br,"GetRangeReader - no `BasePDFStream` instance available.");const i=this.#br.getRangeReader(t.begin,t.end);if(i){e.onPull=()=>{i.read().then(function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetRangeReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}}).catch(t=>{e.error(t)})};e.onCancel=t=>{i.cancel(t);e.ready.catch(t=>{if(!this.destroyed)throw t})}}else e.close()});t.on("GetDoc",({pdfInfo:t})=>{this.pagesMapper.pagesNumber=t.numPages;this._numPages=t.numPages;this._htmlForXfa=t.htmlForXfa;delete t.htmlForXfa;e._capability.resolve(new PDFDocumentProxy(t,this))});t.on("DocException",t=>{e._capability.reject(wrapReason(t))});t.on("PasswordRequest",t=>{this.#vr=Promise.withResolvers();try{if(!e.onPassword)throw wrapReason(t);const updatePassword=t=>{t instanceof Error?this.#vr.reject(t):this.#vr.resolve({password:t})};e.onPassword(updatePassword,t.code)}catch(t){this.#vr.reject(t)}return this.#vr.promise});t.on("DataLoaded",t=>{this.#Qn({loaded:t.length,total:t.length});this.downloadInfoCapability.resolve(t)});t.on("StartRenderPage",t=>{if(this.destroyed)return;this.#yr.get(t.pageIndex)._startRenderPage(t.transparency,t.cacheKey)});t.on("commonobj",([e,i,n])=>{if(this.destroyed)return null;if(this.commonObjs.has(e))return null;switch(i){case"Font":if("error"in n){const t=n.error;warn(`Error during font loading: ${t}`);this.commonObjs.resolve(e,t);break}const s=new FontInfo(n),r=this._params.pdfBug&&globalThis.FontInspector?.enabled?(t,e)=>globalThis.FontInspector.fontAdded(t,e):null,a=new FontFaceObject(s,r,n.charProcOperatorList,n.extra);this.fontLoader.bindLine truncated
+28
View File
@@ -0,0 +1,28 @@
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/**
* pdfjsVersion = 5.6.205
* pdfjsBuild = ada343803
*/const e=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),t=[.001,0,0,.001,0,0],n=1.35,a=.35,s=.25925925925925924,r=1,i=2,o=4,l=8,f=16,c=64,h=128,u=256,m="pdfjs_internal_editor_",p=3,d=9,g=13,b=15,w=101,j={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},k=1,y=2,q=3,v=0,S=4,x=1,C=2,F=3,T={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},O="Group",R="R",H=1,D=2,M=4,N=16,E=32,z=128,L=512,U=1,_=2,X=4096,W=8192,K=32768,V=65536,G=131072,$=1048576,Y=2097152,J=8388608,Q=16777216,Z=1,ee=2,te=3,ne=4,ae=5,se={E:"Mouse Enter",X:"Mouse Exit",D:"Mouse Down",U:"Mouse Up",Fo:"Focus",Bl:"Blur",PO:"PageOpen",PC:"PageClose",PV:"PageVisible",PI:"PageInvisible",K:"Keystroke",F:"Format",V:"Validate",C:"Calculate"},re={WC:"WillClose",WS:"WillSave",DS:"DidSave",WP:"WillPrint",DP:"DidPrint"},ie={O:"PageOpen",C:"PageClose"},oe=1,le=5,fe=1,ce=2,he=3,ue=4,me=5,pe=6,de=7,ge=8,be=9,we=10,je=11,ke=12,ye=13,qe=14,ve=15,Se=16,Ae=17,xe=18,Ce=19,Ie=20,Fe=21,Te=22,Oe=23,Re=24,He=25,Be=26,De=27,Me=28,Pe=29,Ne=30,Ee=31,ze=32,Le=33,Ue=34,_e=35,Xe=36,We=37,Ke=38,Ve=39,Ge=40,$e=41,Ye=42,Je=43,Qe=44,Ze=45,et=46,tt=47,nt=48,at=49,st=50,rt=51,it=52,ot=53,lt=54,ft=55,ct=56,ht=57,ut=58,mt=59,pt=60,dt=61,gt=62,bt=63,wt=64,jt=65,kt=66,yt=67,qt=68,vt=69,St=70,At=71,xt=72,Ct=73,It=74,Ft=75,Tt=76,Ot=77,Rt=80,Ht=81,Bt=83,Dt=84,Mt=85,Pt=86,Nt=87,Et=88,zt=89,Lt=90,Ut=91,_t=92,Xt=93,Wt=94,Kt=0,Vt=1,Gt=2,$t=3,Yt=4,Jt=1,Qt=2;let Zt=oe;function getVerbosityLevel(){return Zt}function info(e){Zt>=le&&console.info(`Info: ${e}`)}function warn(e){Zt>=oe&&console.warn(`Warning: ${e}`)}function unreachable(e){throw new Error(e)}function assert(e,t){e||unreachable(t)}function createValidAbsoluteUrl(e,t=null,n=null){if(!e)return null;if(n&&"string"==typeof e){if(n.addDefaultProtocol&&e.startsWith("www.")){const t=e.match(/\./g);t?.length>=2&&(e=`http://${e}`)}if(n.tryConvertEncoding)try{e=stringToUTF8String(e)}catch{}}const a=t?URL.parse(e,t):URL.parse(e);return function _isValidProtocol(e){switch(e?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(a)?a:null}function shadow(e,t,n,a=!1){Object.defineProperty(e,t,{value:n,enumerable:!a,configurable:!0,writable:!1});return n}const en=function BaseExceptionClosure(){function BaseException(e,t){this.message=e;this.name=t}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends en{constructor(e,t){super(e,"PasswordException");this.code=t}}class UnknownErrorException extends en{constructor(e,t){super(e,"UnknownErrorException");this.details=t}}class InvalidPDFException extends en{constructor(e){super(e,"InvalidPDFException")}}class ResponseException extends en{constructor(e,t,n){super(e,"ResponseException");this.status=t;this.missing=n}}class FormatError extends en{constructor(e){super(e,"FormatError")}}class AbortException extends en{constructor(e){super(e,"AbortException")}}function bytesToString(e){"object"==typeof e&&void 0!==e?.length||unreachable("Invalid argument for bytesToString");const t=e.length,n=8192;if(t<n)return String.fromCharCode.apply(null,e);const a=[];for(let s=0;s<t;s+=n){const r=Math.min(s+n,t),i=e.subarray(s,r);a.push(String.fromCharCode.apply(null,i))}return a.join("")}function stringToBytes(e){"string"!=typeof e&&unreachable("Invalid argument for stringToBytes");const t=e.length,n=new Uint8Array(t);for(let a=0;a<t;++a)n[a]=255&e.charCodeAt(a);return n}function string32(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e)}function objectSize(e){return Object.keys(e).length}class FeatureTest{static get isLittleEndian(){return shadow(this,"isLittleEndian",function isLittleEndian(){const e=new Uint8Array(4);e[0]=1;return 1===new Uint32Array(e.buffer,0,1)[0]}())}static get isEvalSupported(){return shadow(this,"isEvalSupported",function isEvalSupported(){try{new Function("");return!0}catch{return!1}}())}static get isOffscreenCanvasSupported(){return shadow(this,"isOffscreenCanvasSupported","undefined"!=typeof OffscreenCanvas)}static get isImageDecoderSupported(){return shadow(this,"isImageDecoderSupported","undefined"!=typeof ImageDecoder)}static get isFloat16ArraySupported(){return shadow(this,"isFloat16ArraySupported","undefined"!=typeof Float16Array)}static get isSanitizerSupported(){return shadow(this,"isSanitizerSupported","undefined"!=typeof Sanitizer)}static get platform(){const{platform:e,userAgent:t}=navigator;return shadow(this,"platform",{isAndroid:t.includes("Android"),isLinux:e.includes("Linux"),isMac:e.includes("Mac"),isWindows:e.includes("Win"),isLine truncated
/*webpackIgnore: true*/
/*@vite-ignore*/
t)).default()}catch(e){warn(`JpxImage#getJsModule: ${e}`)}e(n)}static async#N(e,t,n){const a="openjpeg.wasm";try{this.#B||(this.#P?this.#B=await fetchBinaryData(`${this.#I}${a}`):this.#B=await this.#D.sendWithPromise("FetchBinaryData",{kind:"wasmUrl",filename:a}));return n((await WebAssembly.instantiate(this.#B,t)).instance)}catch(t){warn(`JpxImage#instantiateWasm: ${t}`);this.#U(e);return null}finally{this.#D=null}}static async decode(e,{numComponents:t=4,isIndexedColormap:n=!1,smaskInData:a=!1,reducePower:s=0}={}){if(!this.#M){const{promise:e,resolve:t}=Promise.withResolvers(),n=[e];this.#C?n.push(Jn({warn,instantiateWasm:this.#N.bind(this,t)})):this.#U(t);this.#M=Promise.race(n)}const r=await this.#M;if(!r)throw new JpxError("OpenJPEG failed to initialize");let i;try{const o=e.length;i=r._malloc(o);r.writeArrayToMemory(e,i);if(r._jp2_decode(i,o,t>0?t:0,!!n,!!a,s)){const{errorMessages:e}=r;if(e){delete r.errorMessages;throw new JpxError(e)}throw new JpxError("Unknown error")}const{imageData:l}=r;r.imageData=null;return l}finally{i&&r._free(i)}}static cleanup(){this.#M=null}static parseImageProperties(e){let t=e.getByte();for(;t>=0;){const n=t;t=e.getByte();if(65361===(n<<8|t)){e.skip(4);const t=e.getInt32()>>>0,n=e.getInt32()>>>0,a=e.getInt32()>>>0,s=e.getInt32()>>>0;e.skip(16);return{width:t-a,height:n-s,bitsPerComponent:8,componentsCount:e.getUint16()}}}throw new JpxError("No size marker found in JPX stream")}}function addState(e,t,n,a,s){let r=e;for(let e=0,n=t.length-1;e<n;e++){const n=t[e];r=r[n]||=[]}r[t.at(-1)]={checkFn:n,iterateFn:a,processFn:s}}const Qn=[];addState(Qn,[we,ke,Pt,je],null,function iterateInlineImageGroup(e,t){const n=e.fnArray,a=(t-(e.iCurr-3))%4;switch(a){case 0:return n[t]===we;case 1:return n[t]===ke;case 2:return n[t]===Pt;case 3:return n[t]===je}throw new Error(`iterateInlineImageGroup - invalid pos: ${a}`)},function foundInlineImageGroup(e,t){const n=e.fnArray,a=e.argsArray,s=e.iCurr,r=s-3,i=s-2,o=s-1,l=Math.min(Math.floor((t-r)/4),200);if(l<10)return t-(t-r)%4;let f=0;const c=[];let h=0,u=1,m=1;for(let e=0;e<l;e++){const t=a[i+(e<<2)],n=a[o+(e<<2)][0];if(u+n.width>1e3){f=Math.max(f,u);m+=h+2;u=0;h=0}c.push({transform:t,x:u,y:m,w:n.width,h:n.height});u+=n.width+2;h=Math.max(h,n.height)}const p=Math.max(f,u)+1,d=m+h+1,g=new Uint8Array(p*d*4),b=p<<2;for(let e=0;e<l;e++){const t=a[o+(e<<2)][0].data,n=c[e].w<<2;let s=0,r=c[e].x+c[e].y*p<<2;g.set(t.subarray(0,n),r-b);for(let a=0,i=c[e].h;a<i;a++){g.set(t.subarray(s,s+n),r);s+=n;r+=b}g.set(t.subarray(s-n,s),r);for(;r>=0;){t[r-4]=t[r];t[r-3]=t[r+1];t[r-2]=t[r+2];t[r-1]=t[r+3];t[r+n]=t[r+n-4];t[r+n+1]=t[r+n-3];t[r+n+2]=t[r+n-2];t[r+n+3]=t[r+n-1];r-=b}}const w={width:p,height:d};if(e.isOffscreenCanvasSupported){const e=new OffscreenCanvas(p,d);e.getContext("2d").putImageData(new ImageData(new Uint8ClampedArray(g.buffer),p,d),0,0);w.bitmap=e.transferToImageBitmap();w.data=null}else{w.kind=F;w.data=g}n.splice(r,4*l,Nt);a.splice(r,4*l,[w,c]);return r+1});addState(Qn,[we,ke,Bt,je],null,function iterateImageMaskGroup(e,t){const n=e.fnArray,a=(t-(e.iCurr-3))%4;switch(a){case 0:return n[t]===we;case 1:return n[t]===ke;case 2:return n[t]===Bt;case 3:return n[t]===je}throw new Error(`iterateImageMaskGroup - invalid pos: ${a}`)},function foundImageMaskGroup(e,t){const n=e.fnArray,a=e.argsArray,s=e.iCurr,r=s-3,i=s-2,o=s-1;let l=Math.floor((t-r)/4);if(l<10)return t-(t-r)%4;let f,c,h=!1;const u=a[o][0],m=a[i][0],p=a[i][1],d=a[i][2],g=a[i][3];if(p===d){h=!0;f=i+4;let e=o+4;for(let t=1;t<l;t++,f+=4,e+=4){c=a[f];if(a[e][0]!==u||c[0]!==m||c[1]!==p||c[2]!==d||c[3]!==g){t<10?h=!1:l=t;break}}}if(h){l=Math.min(l,1e3);const e=new Float32Array(2*l);f=i;for(let t=0;t<l;t++,f+=4){c=a[f];e[t<<1]=c[4];e[1+(t<<1)]=c[5]}n.splice(r,4*l,zt);a.splice(r,4*l,[u,m,p,d,g,e])}else{l=Math.min(l,100);const e=[];for(let t=0;t<l;t++){c=a[i+(t<<2)];const n=a[o+(t<<2)][0];e.push({data:n.data,width:n.width,height:n.height,interpolate:n.interpolate,count:n.count,transform:c})}n.splice(r,4*l,Dt);a.splice(r,4*l,[e])}return r+1});addState(Qn,[we,ke,Mt,je],function(e){const t=e.argsArray,n=e.iCurr-2;return 0===t[n][1]&&0===t[n][2]},function iterateImageGroup(e,t){const n=e.fnArray,a=e.argsArray,s=(t-(e.iCurr-3))%4;switch(s){case 0:return n[t]===we;case 1:if(n[t]!==ke)return!1;const s=e.iCurr-2,r=a[s][0],i=a[s][3];return a[t][0]===r&&0===a[t][1]&&0===a[t][2]&&a[t][3]===i;case 2:if(n[t]!==Mt)return!1;const o=a[e.iCurr-1][0];return a[t][0]===o;case 3:return n[t]===je}throw new Error(`iterateImageGroup - invalid pos: ${s}`)},function(e,t){const n=e.fnArray,a=e.argsArray,s=e.iCurr,r=s-3,i=s-2,o=a[s-1][0],l=a[i][0],f=a[i][3],c=Math.min(Math.floor((t-r)/4),1e3);if(c<3)return t-(t-r)%4;const h=new Float32Array(2*c);let u=i;for(let e=0;e<c;e++,u+=4){const t=a[u];h[e<<1]=t[4];h[1+(e<<1)]=t[5]}const m=[o,l,f,h];n.splice(r,4*c,Et);a.splice(r,4*c,m);return r+1});addState(Qn,[Ee,We,Ye,Qe,ze],null,function iterateShowTextGroup(e,t){const n=e.fnArray,a=e.argsArray,s=(t-(e.iCurr-4))%5;switch(s){casLine truncated
+22
View File
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Mummert\FlipbookBundle\Contao\Manager;
use Contao\CoreBundle\ContaoCoreBundle;
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
use Mummert\FlipbookBundle\FlipbookBundle;
class Plugin implements BundlePluginInterface
{
public function getBundles(ParserInterface $parser): iterable
{
return [
BundleConfig::create(FlipbookBundle::class)
->setLoadAfter([ContaoCoreBundle::class]),
];
}
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Mummert\FlipbookBundle\Controller\ContentElement;
use Contao\ContentModel;
use Contao\CoreBundle\Controller\ContentElement\AbstractContentElementController;
use Contao\CoreBundle\DependencyInjection\Attribute\AsContentElement;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Contao\FilesModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
#[AsContentElement(type: 'blatterbares_pdf', category: 'media', template: 'content_element/blatterbares_pdf')]
class BlatterbaresPdfController extends AbstractContentElementController
{
protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
{
$template->set('pdfUrl', $this->resolvePdfUrl($model));
$template->set('showNavigation', '1' === (string) ($model->flipbookShowNavigation ?? '1'));
$template->set('initialRenderPages', $this->normalizeInitialRenderPages((string) ($model->flipbookInitialPages ?? '4')));
$template->set('startMode', $this->normalizeStartMode((string) ($model->flipbookStartMode ?? 'center')));
return $template->getResponse();
}
private function resolvePdfUrl(ContentModel $model): string
{
$uuid = (string) ($model->flipbookPdfSrc ?? '');
if ('' === $uuid) {
return '';
}
$fileModel = FilesModel::findByUuid($uuid);
if (null === $fileModel) {
return '';
}
$path = trim((string) $fileModel->path, '/');
if ('' === $path || !str_ends_with(strtolower($path), '.pdf')) {
return '';
}
return '/'.implode('/', array_map('rawurlencode', explode('/', $path)));
}
private function normalizeInitialRenderPages(string $value): int
{
$count = (int) $value;
if ($count < 2) {
return 2;
}
if ($count > 4) {
return 4;
}
return $count;
}
private function normalizeStartMode(string $value): string
{
if ('spread' === $value || 'cover' === $value) {
return 'spread';
}
return 'center';
}
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Mummert\FlipbookBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class FlipbookExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('services.yaml');
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Mummert\FlipbookBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class FlipbookBundle extends Bundle
{
public function getPath(): string
{
return dirname(__DIR__);
}
}