feat: support PDF spread splitting and nav button states
This commit is contained in:
@@ -41,6 +41,7 @@ class PdfFlipbookModule {
|
||||
this.initialPages = Number.isFinite(parsedInitialPages)
|
||||
? Math.min(4, Math.max(2, parsedInitialPages))
|
||||
: 4;
|
||||
this.splitSpreads = root.dataset.splitSpreads === '1';
|
||||
this.showNavigation = root.dataset.showNavigation === '1';
|
||||
this.playTurnSoundEnabled = root.dataset.playTurnSound !== '0';
|
||||
this.startMode = (root.dataset.startMode === 'spread' || root.dataset.startMode === 'cover')
|
||||
@@ -67,7 +68,9 @@ class PdfFlipbookModule {
|
||||
this.pageWidth = 0;
|
||||
this.pageHeight = 0;
|
||||
this.pageGap = 2;
|
||||
this.sourcePageCount = 0;
|
||||
this.totalPages = 0;
|
||||
this.pageDescriptors = [];
|
||||
|
||||
this.instanceId = `pdf-flipbook-${++moduleCounter}`;
|
||||
}
|
||||
@@ -136,6 +139,8 @@ class PdfFlipbookModule {
|
||||
if (this.controlsElement) {
|
||||
this.controlsElement.style.opacity = '1';
|
||||
}
|
||||
|
||||
this.updateNavigationState();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,19 +152,84 @@ class PdfFlipbookModule {
|
||||
});
|
||||
|
||||
this.pdf = await loadingTask.promise;
|
||||
this.totalPages = Number(this.pdf.numPages || 0);
|
||||
this.sourcePageCount = Number(this.pdf.numPages || 0);
|
||||
|
||||
if (this.totalPages <= 0) {
|
||||
if (this.sourcePageCount <= 0) {
|
||||
throw new Error('The selected PDF has no pages.');
|
||||
}
|
||||
|
||||
await this.buildPageDescriptors();
|
||||
this.totalPages = this.pageDescriptors.length;
|
||||
}
|
||||
|
||||
async buildPageDescriptors() {
|
||||
const descriptors = [];
|
||||
|
||||
for (let sourcePageNumber = 1; sourcePageNumber <= this.sourcePageCount; sourcePageNumber += 1) {
|
||||
const page = await this.pdf.getPage(sourcePageNumber);
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
|
||||
if (this.shouldSplitSpread(sourcePageNumber, viewport)) {
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'left',
|
||||
});
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'right',
|
||||
});
|
||||
} else {
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'full',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.pageDescriptors = descriptors;
|
||||
}
|
||||
|
||||
shouldSplitSpread(sourcePageNumber, viewport) {
|
||||
if (!this.splitSpreads) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourcePageNumber <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const width = Number(viewport.width || 0);
|
||||
const height = Number(viewport.height || 0);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return width >= (height * 1.2);
|
||||
}
|
||||
|
||||
getDescriptor(pageNumber) {
|
||||
if (!Number.isInteger(pageNumber) || pageNumber < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.pageDescriptors[pageNumber - 1] || null;
|
||||
}
|
||||
|
||||
async resolveInitialAspectRatio() {
|
||||
const firstPage = await this.pdf.getPage(1);
|
||||
const viewport = firstPage.getViewport({ scale: 1 });
|
||||
const firstDescriptor = this.getDescriptor(1);
|
||||
|
||||
if (viewport.width > 0 && viewport.height > 0) {
|
||||
this.aspectRatio = viewport.height / viewport.width;
|
||||
if (!firstDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstPage = await this.pdf.getPage(firstDescriptor.sourcePageNumber);
|
||||
const viewport = firstPage.getViewport({ scale: 1 });
|
||||
const divisor = firstDescriptor.segment === 'full' ? 1 : 2;
|
||||
const width = viewport.width / divisor;
|
||||
|
||||
if (width > 0 && viewport.height > 0) {
|
||||
this.aspectRatio = viewport.height / width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,8 +301,42 @@ class PdfFlipbookModule {
|
||||
onPageTurn: () => {
|
||||
this.playTurnSound();
|
||||
this.queuePages(this.getLazyCandidates());
|
||||
this.updateNavigationState();
|
||||
},
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.updateNavigationState();
|
||||
});
|
||||
}
|
||||
|
||||
updateNavigationState() {
|
||||
if (!this.showNavigation || !this.prevButton || !this.nextButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activePages = Array.from(this.bookElement.querySelectorAll('.c-flipbook__page.is-active'))
|
||||
.map((element) => Number(element.dataset.pageNumber || 0))
|
||||
.filter((pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0);
|
||||
|
||||
let disablePrev = false;
|
||||
let disableNext = false;
|
||||
|
||||
if (activePages.length === 0) {
|
||||
disablePrev = true;
|
||||
disableNext = this.totalPages <= 1;
|
||||
} else {
|
||||
const minActivePage = Math.min(...activePages);
|
||||
const maxActivePage = Math.max(...activePages);
|
||||
|
||||
disablePrev = minActivePage <= 1;
|
||||
disableNext = maxActivePage >= this.totalPages;
|
||||
}
|
||||
|
||||
this.prevButton.disabled = disablePrev;
|
||||
this.nextButton.disabled = disableNext;
|
||||
this.prevButton.setAttribute('aria-disabled', disablePrev ? 'true' : 'false');
|
||||
this.nextButton.setAttribute('aria-disabled', disableNext ? 'true' : 'false');
|
||||
}
|
||||
|
||||
createTurnSound() {
|
||||
@@ -449,14 +553,18 @@ class PdfFlipbookModule {
|
||||
}
|
||||
|
||||
const pageElement = this.pageElements.get(pageNumber);
|
||||
const descriptor = this.getDescriptor(pageNumber);
|
||||
|
||||
if (!pageElement) {
|
||||
if (!pageElement || !descriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = await this.pdf.getPage(pageNumber);
|
||||
const page = await this.pdf.getPage(descriptor.sourcePageNumber);
|
||||
const viewportAtScale1 = page.getViewport({ scale: 1 });
|
||||
const scale = this.pageWidth / viewportAtScale1.width;
|
||||
const renderWidth = descriptor.segment === 'full'
|
||||
? viewportAtScale1.width
|
||||
: viewportAtScale1.width / 2;
|
||||
const scale = this.pageWidth / renderWidth;
|
||||
const viewport = page.getViewport({ scale });
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
const outputScale = Math.max(1, devicePixelRatio);
|
||||
@@ -473,19 +581,59 @@ class PdfFlipbookModule {
|
||||
throw new Error('Could not create canvas 2D context.');
|
||||
}
|
||||
|
||||
canvas.width = Math.floor(viewport.width * outputScale);
|
||||
const canvasCssWidth = descriptor.segment === 'full'
|
||||
? Math.floor(viewport.width)
|
||||
: Math.floor(viewport.width / 2);
|
||||
|
||||
canvas.width = Math.floor(canvasCssWidth * outputScale);
|
||||
canvas.height = Math.floor(viewport.height * outputScale);
|
||||
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
||||
canvas.style.width = `${canvasCssWidth}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',
|
||||
});
|
||||
if (descriptor.segment === 'full') {
|
||||
const renderTask = page.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
|
||||
intent: 'display',
|
||||
});
|
||||
|
||||
await renderTask.promise;
|
||||
await renderTask.promise;
|
||||
} else {
|
||||
const spreadCanvas = document.createElement('canvas');
|
||||
const spreadContext = spreadCanvas.getContext('2d', { alpha: false });
|
||||
|
||||
if (!spreadContext) {
|
||||
throw new Error('Could not create split canvas context.');
|
||||
}
|
||||
|
||||
spreadCanvas.width = Math.floor(viewport.width * outputScale);
|
||||
spreadCanvas.height = Math.floor(viewport.height * outputScale);
|
||||
|
||||
const spreadRenderTask = page.render({
|
||||
canvasContext: spreadContext,
|
||||
viewport,
|
||||
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
|
||||
intent: 'display',
|
||||
});
|
||||
|
||||
await spreadRenderTask.promise;
|
||||
|
||||
const halfWidth = Math.floor(spreadCanvas.width / 2);
|
||||
const sourceX = descriptor.segment === 'left' ? 0 : spreadCanvas.width - halfWidth;
|
||||
|
||||
context.drawImage(
|
||||
spreadCanvas,
|
||||
sourceX,
|
||||
0,
|
||||
halfWidth,
|
||||
spreadCanvas.height,
|
||||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
);
|
||||
}
|
||||
|
||||
const loader = pageElement.querySelector('.mod-pdf-flipbook__page-loader');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user