Stabilize WebGL book pagination restore

This commit is contained in:
2026-06-09 16:42:12 +02:00
parent fe51410a3b
commit 171cafeb65
7 changed files with 315 additions and 85 deletions
+51 -35
View File
@@ -117,14 +117,25 @@ class BookTextureRendererModule extends BaseModule {
this.addEventListener(document, 'webgl-book:page-count-changed', this.handlePageCountChanged);
this.addEventListener(document, 'book-pagination:spread-updated', (event) => {
const spread = event.detail?.spread || this.pagination?.getCurrentSpread?.();
const spreadIndex = Math.max(0, Number(event.detail?.spreadIndex ?? spread?.index ?? 0));
const latestBlockId = event.detail?.latestBlockId;
const latestRenderedBlockId = Math.max(0, Number(event.detail?.latestRenderedBlockId || 0));
this.currentSpread = spread || { left: [], right: [] };
if (latestBlockId && Number(latestBlockId) > latestRenderedBlockId) {
this.markPendingReveal(latestBlockId);
const id = String(latestBlockId);
if (event.detail?.allowFutureUnrendered === true && !this.activeAnimations.has(id)) {
this.drawSpread(this.stripUnrenderedLines(this.currentSpread, latestRenderedBlockId), ['left', 'right']);
return;
}
if (this.activeAnimations.has(id)) {
this.revealPublishBlockIds = new Set([id]);
const visibleSpread = Math.max(0, Number(window.BookLabDebug?.getBookState?.().spreadIndex || 0));
const flipActive = document.documentElement.dataset.webglPageFlipActive === 'true';
if (!flipActive && event.detail?.allowFutureUnrendered !== true && spreadIndex > visibleSpread) {
this.drawSpread(this.currentSpread, ['left', 'right'], { preloadOnly: true });
return;
}
this.drawSpread(this.currentSpread, ['left', 'right']);
}
return;
@@ -143,10 +154,24 @@ class BookTextureRendererModule extends BaseModule {
});
this.addEventListener(document, 'story:manual-scroll', this.fastForwardAnimations);
this.addEventListener(document, 'story:history-restoring', this.stopAnimations);
this.currentSpread = this.pagination?.getCurrentSpread?.() || { index: 0, left: [], right: [], pageMeta: { left: null, right: null } };
this.drawSpread(this.currentSpread);
this.reportProgress(100, 'Book texture renderer ready');
return true;
}
stripUnrenderedLines(spread = {}, latestRenderedBlockId = 0) {
const latestRendered = Math.max(0, Number(latestRenderedBlockId || 0));
return {
...spread,
left: (Array.isArray(spread.left) ? spread.left : [])
.filter(line => Math.max(0, Number(line?.blockId || 0)) <= latestRendered),
right: (Array.isArray(spread.right) ? spread.right : [])
.filter(line => Math.max(0, Number(line?.blockId || 0)) <= latestRendered),
pageMeta: spread.pageMeta || { left: null, right: null }
};
}
markPipelineTiming(name, detail = {}) {
const entry = {
name,
@@ -602,7 +627,8 @@ class BookTextureRendererModule extends BaseModule {
if (!animation || animation.completed) return;
regions.push(...this.assignRevealTiming(blockRegions, animation));
});
const sideRegions = regions.filter(region => region.side === side);
const currentSpreadIndex = Math.max(0, Number(this.currentSpread?.index ?? this.pagination?.currentSpreadIndex ?? 0));
const sideRegions = regions.filter(region => region.side === side && Math.max(0, Number(region.spreadIndex || 0)) === currentSpreadIndex);
if (!sideRegions.length) return null;
const bounds = sideRegions.reduce((box, region) => ({
x: Math.min(box.x, region.pixelRect.x),
@@ -636,26 +662,30 @@ class BookTextureRendererModule extends BaseModule {
collectRevealRegionCandidates() {
const candidates = [];
['left', 'right'].forEach((side) => {
const spreadLines = Array.isArray(this.currentSpread?.[side]) ? this.currentSpread[side] : [];
spreadLines.forEach((lineRecord) => {
const region = this.createRevealRegionForLine(side, lineRecord);
if (region) candidates.push(region);
const sourceSpreads = Array.isArray(this.pagination?.spreads) && this.pagination.spreads.length
? this.pagination.spreads
: [this.currentSpread || { index: 0, left: [], right: [] }];
sourceSpreads.forEach((spread) => {
['left', 'right'].forEach((side) => {
const spreadLines = Array.isArray(spread?.[side]) ? spread[side] : [];
spreadLines.forEach((lineRecord) => {
const region = this.createRevealRegionForLine(side, lineRecord, spread?.index);
if (region) candidates.push(region);
});
});
});
return candidates;
}
assignRevealTiming(blockRegions = [], animation = {}) {
const wordTimings = Array.isArray(animation.wordTimings) ? animation.wordTimings : [];
const totalDuration = Math.max(
Number(animation.totalDuration || 0),
...wordTimings.map(timing => Number(timing.delay || 0) + Number(timing.duration || 0))
...((Array.isArray(animation.wordTimings) ? animation.wordTimings : []).map(timing => Number(timing.delay || 0) + Number(timing.duration || 0)))
);
const sortedRegions = [...blockRegions].sort((a, b) => {
const aStart = Math.max(0, Number(a.wordStart || 0));
const bStart = Math.max(0, Number(b.wordStart || 0));
if (aStart !== bStart) return aStart - bStart;
const aSpread = Math.max(0, Number(a.spreadIndex || 0));
const bSpread = Math.max(0, Number(b.spreadIndex || 0));
if (aSpread !== bSpread) return aSpread - bSpread;
const aLine = Math.max(0, Number(a.lineIndex || 0));
const bLine = Math.max(0, Number(b.lineIndex || 0));
return aLine - bLine;
@@ -667,25 +697,14 @@ class BookTextureRendererModule extends BaseModule {
const totalArea = textRegions.reduce((sum, region) => sum + Math.max(1, region.area), 0);
textRegions.forEach((region) => {
const wordStart = Math.max(0, Number(region.wordStart || 0));
const wordEnd = Math.max(wordStart + 1, Number(region.wordEnd || wordStart + 1));
const firstTiming = wordTimings[wordStart] || null;
const lastTiming = wordTimings[Math.min(wordTimings.length - 1, wordEnd - 1)] || firstTiming;
let delay = firstTiming ? Math.max(0, Number(firstTiming.delay || 0)) : fallbackDelay;
let duration = lastTiming
? Math.max(1, (Number(lastTiming.delay || 0) + Number(lastTiming.duration || 0)) - delay)
: 0;
if (!Number.isFinite(duration) || duration <= 0) {
duration = totalArea > 0
? Math.max(1, totalDuration * (Math.max(1, region.area) / totalArea))
: Math.max(1, totalDuration / Math.max(1, textRegions.length));
delay = fallbackDelay;
}
const duration = totalArea > 0
? Math.max(1, totalDuration * (Math.max(1, region.area) / totalArea))
: Math.max(1, totalDuration / Math.max(1, textRegions.length));
timedRegions.push({
...region,
timing: { delay, duration }
timing: { delay: fallbackDelay, duration }
});
fallbackDelay = Math.max(fallbackDelay, delay + duration);
fallbackDelay += duration;
});
fixedRegions.forEach((region) => {
@@ -707,7 +726,7 @@ class BookTextureRendererModule extends BaseModule {
});
}
createRevealRegionForLine(side, lineRecord = {}) {
createRevealRegionForLine(side, lineRecord = {}, spreadIndex = null) {
const blockId = String(lineRecord?.blockId ?? '');
if (!blockId || !this.revealPublishBlockIds.has(blockId)) return null;
const animation = this.activeAnimations.get(blockId);
@@ -719,16 +738,14 @@ class BookTextureRendererModule extends BaseModule {
const y = content.y + Number(rect.y || 0);
const width = Math.max(1, Number(rect.width || content.width));
const height = Math.max(1, Number(rect.height || this.metrics.typographyLineHeightPx));
return this.normalizeRevealRegion(side, blockId, lineRecord, x, y, width, height, this.getImageRevealDurationMs(lineRecord));
return this.normalizeRevealRegion(side, blockId, lineRecord, x, y, width, height, this.getImageRevealDurationMs(lineRecord), spreadIndex);
}
const rect = this.getLineInkRect(side, lineRecord);
if (!rect) return null;
const wordStart = Math.max(0, Number(lineRecord.blockWordStart || 0));
const wordCount = Math.max(1, this.getLineWordCount(lineRecord.line || {}));
return this.normalizeRevealRegion(side, blockId, lineRecord, rect.x, rect.y, rect.width, rect.height, 0, wordStart, wordStart + wordCount);
return this.normalizeRevealRegion(side, blockId, lineRecord, rect.x, rect.y, rect.width, rect.height, 0, spreadIndex);
}
normalizeRevealRegion(side, blockId, lineRecord, x, y, width, height, fixedDurationMs = 0, wordStart = 0, wordEnd = 0) {
normalizeRevealRegion(side, blockId, lineRecord, x, y, width, height, fixedDurationMs = 0, spreadIndex = null) {
const padding = Math.max(2, Number(lineRecord.fontPx || 18) * 0.12);
const left = Math.max(0, x - padding);
const top = Math.max(0, y - padding);
@@ -738,11 +755,10 @@ class BookTextureRendererModule extends BaseModule {
const rectHeight = Math.max(1, bottom - top);
return {
side,
spreadIndex: Math.max(0, Number((spreadIndex ?? Math.floor(Number(lineRecord.pageIndex || 0) / 2)) || 0)),
blockId,
lineIndex: Number(lineRecord.lineIndex ?? lineRecord.pageLine ?? 0),
fixedDurationMs,
wordStart,
wordEnd,
area: rectWidth * rectHeight,
pixelRect: { x: left, y: top, right, bottom },
rect: {