@papyrus-sdk/engine-epub 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +66 -5
- package/dist/index.d.ts +66 -5
- package/dist/index.js +1259 -80
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1256 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +50 -50
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -27,24 +27,55 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
29
|
// index.ts
|
|
30
|
-
var
|
|
31
|
-
__export(
|
|
30
|
+
var engine_epub_exports = {};
|
|
31
|
+
__export(engine_epub_exports, {
|
|
32
32
|
EPUBEngine: () => EPUBEngine
|
|
33
33
|
});
|
|
34
|
-
module.exports = __toCommonJS(
|
|
34
|
+
module.exports = __toCommonJS(engine_epub_exports);
|
|
35
35
|
var import_epubjs = __toESM(require("epubjs"));
|
|
36
36
|
var import_core = require("@papyrus-sdk/core");
|
|
37
|
-
var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
37
|
+
var EPUBEngine = class _EPUBEngine extends import_core.BaseDocumentEngine {
|
|
38
38
|
constructor() {
|
|
39
39
|
super(...arguments);
|
|
40
40
|
this.book = null;
|
|
41
41
|
this.spineItems = [];
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
42
|
+
this.coverUrl = null;
|
|
43
|
+
this.readerRendition = null;
|
|
44
|
+
this.readerTarget = null;
|
|
44
45
|
this.pageSizes = /* @__PURE__ */ new Map();
|
|
45
46
|
this.currentPage = 1;
|
|
46
47
|
this.zoom = 1;
|
|
47
48
|
this.rotation = 0;
|
|
49
|
+
this.heightSyncVersion = 0;
|
|
50
|
+
this.renderVersion = 0;
|
|
51
|
+
this.renderLock = Promise.resolve();
|
|
52
|
+
this.pendingHrefDestination = null;
|
|
53
|
+
this.destinationSequence = [];
|
|
54
|
+
this.destinationCursor = -1;
|
|
55
|
+
this.lastDestinationNavTime = 0;
|
|
56
|
+
this.lastDestinationPageIndex = null;
|
|
57
|
+
this.lastDestinationHref = null;
|
|
58
|
+
}
|
|
59
|
+
static {
|
|
60
|
+
this.A4_RATIO = 1.4142;
|
|
61
|
+
}
|
|
62
|
+
static {
|
|
63
|
+
this.USE_INTERNAL_IFRAME_SCROLL = true;
|
|
64
|
+
}
|
|
65
|
+
static {
|
|
66
|
+
this.MOBILE_VIEWPORT_MAX_WIDTH_PX = 768;
|
|
67
|
+
}
|
|
68
|
+
static {
|
|
69
|
+
this.MOBILE_SHORT_VIEWPORT_MAX_HEIGHT_PX = 500;
|
|
70
|
+
}
|
|
71
|
+
static {
|
|
72
|
+
this.INTERNAL_VIEWPORT_PADDING_PX = 10;
|
|
73
|
+
}
|
|
74
|
+
static {
|
|
75
|
+
this.MAX_SECTION_HEIGHT = 1e6;
|
|
76
|
+
}
|
|
77
|
+
static {
|
|
78
|
+
this.HEIGHT_PADDING = 24;
|
|
48
79
|
}
|
|
49
80
|
getRenderTargetType() {
|
|
50
81
|
return "element";
|
|
@@ -53,34 +84,243 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
53
84
|
try {
|
|
54
85
|
const { source, type } = this.normalizeLoadInput(input);
|
|
55
86
|
if (type && type !== "epub") {
|
|
56
|
-
throw new Error(
|
|
87
|
+
throw new Error(
|
|
88
|
+
`[EPUBEngine] Tipo de documento n\xE3o suportado: ${type}`
|
|
89
|
+
);
|
|
57
90
|
}
|
|
91
|
+
this.renderVersion += 1;
|
|
92
|
+
this.renderLock = Promise.resolve();
|
|
93
|
+
this.disposeReader();
|
|
94
|
+
this.pageSizes.clear();
|
|
95
|
+
this.spineItems = [];
|
|
96
|
+
this.coverUrl = null;
|
|
97
|
+
this.destinationSequence = [];
|
|
98
|
+
this.destinationCursor = -1;
|
|
99
|
+
this.lastDestinationNavTime = 0;
|
|
100
|
+
this.lastDestinationPageIndex = null;
|
|
101
|
+
this.lastDestinationHref = null;
|
|
102
|
+
if (this.book?.destroy) this.book.destroy();
|
|
58
103
|
const data = await this.resolveSource(source);
|
|
59
104
|
this.book = (0, import_epubjs.default)(data);
|
|
105
|
+
if (this.book?.opened) {
|
|
106
|
+
await this.book.opened;
|
|
107
|
+
}
|
|
60
108
|
await this.book.ready;
|
|
61
|
-
this.
|
|
109
|
+
const packaging = this.book?.package ?? this.book?.packaging ?? null;
|
|
110
|
+
if (!packaging) {
|
|
111
|
+
throw new Error("[EPUBEngine] packaging indisponivel.");
|
|
112
|
+
}
|
|
113
|
+
if (!this.book.package) {
|
|
114
|
+
this.book.package = packaging;
|
|
115
|
+
}
|
|
116
|
+
if (!this.book.packaging) {
|
|
117
|
+
this.book.packaging = packaging;
|
|
118
|
+
}
|
|
119
|
+
if (this.book?.loaded?.navigation) {
|
|
120
|
+
await this.book.loaded.navigation;
|
|
121
|
+
}
|
|
122
|
+
this.spineItems = this.getLinearSpineItems(this.book.spine?.items ?? []);
|
|
123
|
+
if (typeof this.book.coverUrl === "function") {
|
|
124
|
+
try {
|
|
125
|
+
const resolvedCover = await this.book.coverUrl();
|
|
126
|
+
if (typeof resolvedCover === "string" && resolvedCover.length > 0) {
|
|
127
|
+
this.coverUrl = resolvedCover;
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
this.coverUrl = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
62
133
|
this.currentPage = 1;
|
|
63
|
-
this.renditions.forEach((rendition) => rendition?.destroy?.());
|
|
64
|
-
this.renditions.clear();
|
|
65
|
-
this.renditionTargets.clear();
|
|
66
|
-
this.pageSizes.clear();
|
|
67
134
|
} catch (error) {
|
|
68
135
|
console.error("[EPUBEngine] Erro ao carregar:", error);
|
|
69
136
|
throw error;
|
|
70
137
|
}
|
|
71
138
|
}
|
|
72
139
|
getPageCount() {
|
|
73
|
-
return this.spineItems.length;
|
|
140
|
+
return this.spineItems.length + (this.hasCoverPage() ? 1 : 0);
|
|
74
141
|
}
|
|
75
142
|
getCurrentPage() {
|
|
76
143
|
return this.currentPage;
|
|
77
144
|
}
|
|
78
145
|
goToPage(page) {
|
|
79
|
-
if (page
|
|
146
|
+
if (page < 1 || page > this.getPageCount()) return;
|
|
147
|
+
this.currentPage = page;
|
|
148
|
+
this.pendingHrefDestination = null;
|
|
149
|
+
if (this.destinationSequence.length) {
|
|
150
|
+
const destinationIndex = this.findNearestDestinationIndexByPage(page - 1);
|
|
151
|
+
if (destinationIndex >= 0) this.destinationCursor = destinationIndex;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async goToDestination(dest) {
|
|
155
|
+
const href = this.extractHrefDestination(dest);
|
|
156
|
+
if (!href) {
|
|
157
|
+
const pageIndex = await this.getPageIndex(dest);
|
|
158
|
+
if (pageIndex != null) this.goToPage(pageIndex + 1);
|
|
159
|
+
return pageIndex;
|
|
160
|
+
}
|
|
161
|
+
const baseHref = this.normalizeHref(href);
|
|
162
|
+
const currentBaseHref = this.lastDestinationHref ? this.normalizeHref(this.lastDestinationHref) : null;
|
|
163
|
+
const hasAnchor = href.includes("#");
|
|
164
|
+
if (!hasAnchor && currentBaseHref && baseHref === currentBaseHref && this.readerRendition) {
|
|
165
|
+
this.debugLog("goToDestination:skip-same-base", {
|
|
166
|
+
href,
|
|
167
|
+
currentBaseHref
|
|
168
|
+
});
|
|
169
|
+
const pageIndex = await this.resolvePageIndexFromHref(href);
|
|
170
|
+
return pageIndex;
|
|
171
|
+
}
|
|
172
|
+
let releaseCurrent = null;
|
|
173
|
+
const previousRender = this.renderLock;
|
|
174
|
+
this.renderLock = new Promise((resolve) => {
|
|
175
|
+
releaseCurrent = resolve;
|
|
176
|
+
});
|
|
177
|
+
await previousRender;
|
|
178
|
+
try {
|
|
179
|
+
this.pendingHrefDestination = href;
|
|
180
|
+
let pageIndex = await this.resolvePageIndexFromHref(href);
|
|
181
|
+
if (pageIndex != null) this.currentPage = pageIndex + 1;
|
|
182
|
+
this.debugLog("goToDestination:start", {
|
|
183
|
+
href,
|
|
184
|
+
pageIndex,
|
|
185
|
+
currentPage: this.currentPage
|
|
186
|
+
});
|
|
187
|
+
const rendition = this.readerRendition;
|
|
188
|
+
let displayFailed = false;
|
|
189
|
+
if (rendition) {
|
|
190
|
+
try {
|
|
191
|
+
const didDisplay = await this.displayDestinationWithRetry(
|
|
192
|
+
rendition,
|
|
193
|
+
href
|
|
194
|
+
);
|
|
195
|
+
if (!didDisplay)
|
|
196
|
+
throw new Error("Destination display retry exhausted");
|
|
197
|
+
if (pageIndex == null) {
|
|
198
|
+
const locationPageIndex = this.getPageIndexFromRenditionLocation(rendition);
|
|
199
|
+
if (locationPageIndex != null) {
|
|
200
|
+
pageIndex = locationPageIndex;
|
|
201
|
+
this.currentPage = locationPageIndex + 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
this.debugLog("goToDestination:displayed", {
|
|
205
|
+
href,
|
|
206
|
+
pageIndex,
|
|
207
|
+
currentPage: this.currentPage
|
|
208
|
+
});
|
|
209
|
+
this.updateDestinationCursor(href, pageIndex);
|
|
210
|
+
this.pendingHrefDestination = null;
|
|
211
|
+
this.lastDestinationNavTime = Date.now();
|
|
212
|
+
this.lastDestinationPageIndex = pageIndex;
|
|
213
|
+
this.lastDestinationHref = href;
|
|
214
|
+
return pageIndex;
|
|
215
|
+
} catch {
|
|
216
|
+
displayFailed = true;
|
|
217
|
+
this.debugLog("goToDestination:display-failed", { href, pageIndex });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (displayFailed && pageIndex != null && this.readerTarget && typeof this.readerTarget.isConnected === "boolean" && this.readerTarget.isConnected) {
|
|
221
|
+
void this.renderPage(pageIndex, this.readerTarget, 1).catch(() => {
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
this.debugLog("goToDestination:no-rendition", {
|
|
225
|
+
href,
|
|
226
|
+
pageIndex,
|
|
227
|
+
currentPage: this.currentPage
|
|
228
|
+
});
|
|
229
|
+
this.updateDestinationCursor(href, pageIndex);
|
|
230
|
+
return pageIndex;
|
|
231
|
+
} finally {
|
|
232
|
+
releaseCurrent?.();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async goToAdjacentDestination(delta) {
|
|
236
|
+
if (!Number.isFinite(delta) || delta === 0) return this.currentPage - 1;
|
|
237
|
+
await this.ensureDestinationSequence();
|
|
238
|
+
if (!this.destinationSequence.length) {
|
|
239
|
+
const basePageIndex = Math.max(0, this.currentPage - 1);
|
|
240
|
+
const nextPageIndex = Math.max(
|
|
241
|
+
0,
|
|
242
|
+
Math.min(this.getPageCount() - 1, basePageIndex + (delta > 0 ? 1 : -1))
|
|
243
|
+
);
|
|
244
|
+
if (nextPageIndex === basePageIndex) return basePageIndex;
|
|
245
|
+
this.goToPage(nextPageIndex + 1);
|
|
246
|
+
return nextPageIndex;
|
|
247
|
+
}
|
|
248
|
+
if (this.destinationCursor < 0) {
|
|
249
|
+
const inferred = this.findNearestDestinationIndexByPage(
|
|
250
|
+
this.currentPage - 1
|
|
251
|
+
);
|
|
252
|
+
this.destinationCursor = inferred >= 0 ? inferred : 0;
|
|
253
|
+
}
|
|
254
|
+
const currentEntry = this.destinationSequence[this.destinationCursor];
|
|
255
|
+
const currentBaseHref = currentEntry ? this.normalizeHref(currentEntry.href) : null;
|
|
256
|
+
this.debugLog("goToAdjacentDestination:start", {
|
|
257
|
+
delta,
|
|
258
|
+
cursor: this.destinationCursor,
|
|
259
|
+
total: this.destinationSequence.length,
|
|
260
|
+
currentPage: this.currentPage,
|
|
261
|
+
currentBaseHref
|
|
262
|
+
});
|
|
263
|
+
const step = delta > 0 ? 1 : -1;
|
|
264
|
+
let nextIndex = this.destinationCursor;
|
|
265
|
+
const limit = delta > 0 ? this.destinationSequence.length - 1 : 0;
|
|
266
|
+
while (true) {
|
|
267
|
+
const candidate = nextIndex + step;
|
|
268
|
+
if (candidate < 0 || candidate > this.destinationSequence.length - 1)
|
|
269
|
+
break;
|
|
270
|
+
nextIndex = candidate;
|
|
271
|
+
const candidateBaseHref = this.normalizeHref(
|
|
272
|
+
this.destinationSequence[nextIndex].href
|
|
273
|
+
);
|
|
274
|
+
if (!currentBaseHref || candidateBaseHref !== currentBaseHref) break;
|
|
275
|
+
}
|
|
276
|
+
if (nextIndex === this.destinationCursor) {
|
|
277
|
+
this.debugLog("goToAdjacentDestination:at-boundary", {
|
|
278
|
+
delta,
|
|
279
|
+
cursor: this.destinationCursor
|
|
280
|
+
});
|
|
281
|
+
const current = this.destinationSequence[this.destinationCursor];
|
|
282
|
+
return current?.pageIndex ?? this.currentPage - 1;
|
|
283
|
+
}
|
|
284
|
+
const target = this.destinationSequence[nextIndex];
|
|
285
|
+
if (!target) return null;
|
|
286
|
+
this.destinationCursor = nextIndex;
|
|
287
|
+
const resolved = await this.goToDestination({
|
|
288
|
+
kind: "href",
|
|
289
|
+
value: target.href
|
|
290
|
+
});
|
|
291
|
+
if (resolved != null) {
|
|
292
|
+
this.debugLog("goToAdjacentDestination:resolved", {
|
|
293
|
+
delta,
|
|
294
|
+
nextIndex,
|
|
295
|
+
href: target.href,
|
|
296
|
+
resolved
|
|
297
|
+
});
|
|
298
|
+
return resolved;
|
|
299
|
+
}
|
|
300
|
+
this.destinationCursor = nextIndex - step;
|
|
301
|
+
this.debugLog("goToAdjacentDestination:failed", {
|
|
302
|
+
delta,
|
|
303
|
+
nextIndex,
|
|
304
|
+
href: target.href
|
|
305
|
+
});
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
getDestinationNavigationState() {
|
|
309
|
+
const total = this.destinationSequence.length;
|
|
310
|
+
if (!total) {
|
|
311
|
+
return {
|
|
312
|
+
hasPrev: this.currentPage > 1,
|
|
313
|
+
hasNext: this.currentPage < this.getPageCount()
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const cursor = this.destinationCursor >= 0 ? this.destinationCursor : this.findNearestDestinationIndexByPage(this.currentPage - 1);
|
|
317
|
+
return {
|
|
318
|
+
hasPrev: cursor > 0,
|
|
319
|
+
hasNext: cursor >= 0 && cursor < total - 1
|
|
320
|
+
};
|
|
80
321
|
}
|
|
81
322
|
setZoom(zoom) {
|
|
82
323
|
this.zoom = Math.max(0.5, Math.min(3, zoom));
|
|
83
|
-
this.renditions.forEach((rendition) => this.applyRenditionTheme(rendition));
|
|
84
324
|
}
|
|
85
325
|
getZoom() {
|
|
86
326
|
return this.zoom;
|
|
@@ -101,74 +341,184 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
101
341
|
return { width: 0, height: 0 };
|
|
102
342
|
}
|
|
103
343
|
async selectText(pageIndex, rect) {
|
|
104
|
-
void pageIndex;
|
|
105
|
-
void rect;
|
|
106
344
|
return null;
|
|
107
345
|
}
|
|
108
346
|
async renderPage(pageIndex, target, scale) {
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
element.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
347
|
+
let releaseCurrent = null;
|
|
348
|
+
const previousRender = this.renderLock;
|
|
349
|
+
this.renderLock = new Promise((resolve) => {
|
|
350
|
+
releaseCurrent = resolve;
|
|
351
|
+
});
|
|
352
|
+
await previousRender;
|
|
353
|
+
try {
|
|
354
|
+
const pageCount = this.getPageCount();
|
|
355
|
+
if (pageCount <= 0) return;
|
|
356
|
+
const safePageIndex = Math.max(0, Math.min(pageCount - 1, pageIndex));
|
|
357
|
+
const renderVersion = ++this.renderVersion;
|
|
358
|
+
const element = target;
|
|
359
|
+
if (!this.book || !element) return;
|
|
360
|
+
const width = element.clientWidth > 0 ? element.clientWidth : 640;
|
|
361
|
+
const viewportHeightHint = _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL ? this.getViewportHeightHint(element) : null;
|
|
362
|
+
const rawHeight = viewportHeightHint ?? (element.clientHeight > 0 ? element.clientHeight : 900);
|
|
363
|
+
const normalizedHeight = _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL ? Math.max(360, Math.min(4e3, rawHeight)) : Math.max(480, Math.min(1400, rawHeight));
|
|
364
|
+
const minA4Height = _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL ? 0 : this.getA4MinHeight(width);
|
|
365
|
+
const seedHeight = _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL ? normalizedHeight : Math.max(minA4Height, normalizedHeight);
|
|
366
|
+
if (this.isCoverPage(safePageIndex)) {
|
|
367
|
+
this.renderCoverPage(element, width, seedHeight, safePageIndex);
|
|
368
|
+
if (renderVersion === this.renderVersion) {
|
|
369
|
+
this.currentPage = safePageIndex + 1;
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const spineItem = this.getSpineItemForPage(safePageIndex);
|
|
374
|
+
if (!spineItem) return;
|
|
375
|
+
const displayTargets = [...this.getDisplayTargetsForSpineItem(spineItem)];
|
|
376
|
+
if (!displayTargets.length) return;
|
|
377
|
+
const defaultSectionIndex = typeof spineItem.index === "number" ? spineItem.index : null;
|
|
378
|
+
const pendingHrefDestination = this.pendingHrefDestination;
|
|
379
|
+
let consumedPendingHref = false;
|
|
380
|
+
if (pendingHrefDestination) {
|
|
381
|
+
const pendingPageIndex = await this.resolvePageIndexFromHref(
|
|
382
|
+
pendingHrefDestination
|
|
383
|
+
);
|
|
384
|
+
const pendingMatchesCurrentPage = pendingPageIndex === safePageIndex || this.isHrefMatchingSpineItem(pendingHrefDestination, spineItem);
|
|
385
|
+
if (pendingMatchesCurrentPage) {
|
|
386
|
+
consumedPendingHref = true;
|
|
387
|
+
displayTargets.unshift(
|
|
388
|
+
pendingHrefDestination,
|
|
389
|
+
this.decodeHref(pendingHrefDestination)
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const uniqueTargets = this.uniqueDisplayTargets(displayTargets);
|
|
394
|
+
if (!uniqueTargets.length) return;
|
|
395
|
+
element.style.width = `${width}px`;
|
|
396
|
+
element.style.height = `${seedHeight}px`;
|
|
397
|
+
if (width >= 320 && seedHeight >= 480)
|
|
398
|
+
this.pageSizes.set(safePageIndex, { width, height: seedHeight });
|
|
399
|
+
let rendition = this.readerRendition;
|
|
400
|
+
if (!rendition || this.readerTarget !== element) {
|
|
401
|
+
this.disposeReader();
|
|
402
|
+
element.innerHTML = "";
|
|
403
|
+
rendition = this.createRendition(element, width, seedHeight);
|
|
404
|
+
this.readerRendition = rendition;
|
|
405
|
+
this.readerTarget = element;
|
|
406
|
+
} else if (typeof rendition.resize === "function" && rendition.manager?.resize) {
|
|
407
|
+
try {
|
|
408
|
+
rendition.resize(width, seedHeight);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.disposeReader();
|
|
411
|
+
element.innerHTML = "";
|
|
412
|
+
rendition = this.createRendition(element, width, seedHeight);
|
|
413
|
+
this.readerRendition = rendition;
|
|
414
|
+
this.readerTarget = element;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (rendition) {
|
|
418
|
+
const syncVersion = ++this.heightSyncVersion;
|
|
419
|
+
this.applyRenditionTheme(rendition);
|
|
420
|
+
if (renderVersion !== this.renderVersion) return;
|
|
421
|
+
const recentDestNav = Date.now() - this.lastDestinationNavTime < 200 && this.lastDestinationPageIndex === safePageIndex;
|
|
422
|
+
if (recentDestNav) {
|
|
423
|
+
this.debugLog("renderPage:skip-recent-dest-nav", {
|
|
424
|
+
safePageIndex,
|
|
425
|
+
msSince: Date.now() - this.lastDestinationNavTime
|
|
426
|
+
});
|
|
427
|
+
await this.syncSectionHeight(
|
|
428
|
+
rendition,
|
|
429
|
+
element,
|
|
430
|
+
safePageIndex,
|
|
431
|
+
width,
|
|
432
|
+
seedHeight,
|
|
433
|
+
defaultSectionIndex,
|
|
434
|
+
true
|
|
435
|
+
);
|
|
436
|
+
if (renderVersion !== this.renderVersion) return;
|
|
437
|
+
this.scheduleHeightSync(
|
|
438
|
+
syncVersion,
|
|
439
|
+
rendition,
|
|
440
|
+
element,
|
|
441
|
+
safePageIndex,
|
|
442
|
+
width,
|
|
443
|
+
seedHeight,
|
|
444
|
+
defaultSectionIndex,
|
|
445
|
+
true
|
|
446
|
+
);
|
|
447
|
+
if (renderVersion === this.renderVersion) {
|
|
448
|
+
this.currentPage = safePageIndex + 1;
|
|
138
449
|
}
|
|
139
|
-
}
|
|
450
|
+
} else {
|
|
451
|
+
const sectionIndex = await this.displayWithFallback(
|
|
452
|
+
rendition,
|
|
453
|
+
uniqueTargets,
|
|
454
|
+
() => renderVersion !== this.renderVersion
|
|
455
|
+
);
|
|
456
|
+
if (sectionIndex === void 0) return;
|
|
457
|
+
if (renderVersion !== this.renderVersion) return;
|
|
458
|
+
if (consumedPendingHref && pendingHrefDestination && pendingHrefDestination.includes("#")) {
|
|
459
|
+
const anchored = await this.displayDestinationWithRetry(
|
|
460
|
+
rendition,
|
|
461
|
+
pendingHrefDestination
|
|
462
|
+
);
|
|
463
|
+
this.debugLog("renderPage:anchor-second-pass", {
|
|
464
|
+
href: pendingHrefDestination,
|
|
465
|
+
anchored
|
|
466
|
+
});
|
|
467
|
+
if (renderVersion !== this.renderVersion) return;
|
|
468
|
+
}
|
|
469
|
+
await this.syncSectionHeight(
|
|
470
|
+
rendition,
|
|
471
|
+
element,
|
|
472
|
+
safePageIndex,
|
|
473
|
+
width,
|
|
474
|
+
seedHeight,
|
|
475
|
+
sectionIndex ?? defaultSectionIndex
|
|
476
|
+
);
|
|
477
|
+
if (renderVersion !== this.renderVersion) return;
|
|
478
|
+
this.scheduleHeightSync(
|
|
479
|
+
syncVersion,
|
|
480
|
+
rendition,
|
|
481
|
+
element,
|
|
482
|
+
safePageIndex,
|
|
483
|
+
width,
|
|
484
|
+
seedHeight,
|
|
485
|
+
sectionIndex ?? defaultSectionIndex
|
|
486
|
+
);
|
|
487
|
+
if (renderVersion === this.renderVersion) {
|
|
488
|
+
if (consumedPendingHref) this.pendingHrefDestination = null;
|
|
489
|
+
this.currentPage = safePageIndex + 1;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
140
492
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
if (rendition) {
|
|
145
|
-
this.applyRenditionTheme(rendition);
|
|
146
|
-
const targetRef = spineItem.href || spineItem.idref || spineItem.cfiBase || pageIndex;
|
|
147
|
-
await rendition.display(targetRef);
|
|
493
|
+
} finally {
|
|
494
|
+
releaseCurrent?.();
|
|
148
495
|
}
|
|
149
496
|
}
|
|
150
497
|
async renderTextLayer(pageIndex, container, scale) {
|
|
151
|
-
void pageIndex;
|
|
152
|
-
void scale;
|
|
153
498
|
const element = container;
|
|
154
499
|
if (element) element.innerHTML = "";
|
|
155
500
|
}
|
|
156
501
|
async getTextContent(pageIndex) {
|
|
157
502
|
if (!this.book) return [];
|
|
158
|
-
|
|
503
|
+
if (this.isCoverPage(pageIndex)) return [];
|
|
504
|
+
const spineIndex = this.toSpineIndex(pageIndex);
|
|
505
|
+
if (spineIndex < 0) return [];
|
|
506
|
+
const spineItem = this.spineItems[spineIndex];
|
|
159
507
|
if (!spineItem) return [];
|
|
160
508
|
try {
|
|
161
509
|
const section = this.book.spine.get(spineItem.idref || spineItem.href);
|
|
162
510
|
const text = typeof section?.text === "function" ? await section.text() : "";
|
|
163
511
|
if (!text) return [];
|
|
164
|
-
return [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
512
|
+
return [
|
|
513
|
+
{
|
|
514
|
+
str: text,
|
|
515
|
+
dir: "ltr",
|
|
516
|
+
width: 0,
|
|
517
|
+
height: 0,
|
|
518
|
+
transform: [1, 0, 0, 1, 0, 0],
|
|
519
|
+
fontName: "default"
|
|
520
|
+
}
|
|
521
|
+
];
|
|
172
522
|
} catch {
|
|
173
523
|
return [];
|
|
174
524
|
}
|
|
@@ -178,36 +528,53 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
178
528
|
const nav = await this.book.loaded?.navigation;
|
|
179
529
|
const toc = nav?.toc ?? [];
|
|
180
530
|
if (!toc.length) return [];
|
|
181
|
-
|
|
531
|
+
await this.ensureDestinationSequence();
|
|
532
|
+
const mapItem = async (item) => {
|
|
182
533
|
const title = item.label || item.title || "";
|
|
183
|
-
const
|
|
184
|
-
const
|
|
534
|
+
const href = item.href || "";
|
|
535
|
+
const resolvedIndex = await this.resolvePageIndexFromHref(href);
|
|
536
|
+
const pageIndex = resolvedIndex ?? -1;
|
|
537
|
+
const children = Array.isArray(item.subitems) ? await Promise.all(item.subitems.map(mapItem)) : [];
|
|
185
538
|
const outlineItem = { title, pageIndex };
|
|
539
|
+
if (href) {
|
|
540
|
+
outlineItem.dest = { kind: "href", value: href };
|
|
541
|
+
}
|
|
186
542
|
if (children.length > 0) outlineItem.children = children;
|
|
187
543
|
return outlineItem;
|
|
188
544
|
};
|
|
189
|
-
return toc.map(mapItem);
|
|
545
|
+
return await Promise.all(toc.map(mapItem));
|
|
190
546
|
}
|
|
191
547
|
async getPageIndex(dest) {
|
|
192
548
|
if (!dest) return null;
|
|
193
|
-
if (typeof dest === "string")
|
|
194
|
-
|
|
195
|
-
if (dest.kind === "
|
|
196
|
-
|
|
549
|
+
if (typeof dest === "string")
|
|
550
|
+
return await this.resolvePageIndexFromHref(dest);
|
|
551
|
+
if (dest.kind === "href")
|
|
552
|
+
return await this.resolvePageIndexFromHref(dest.value);
|
|
553
|
+
if (dest.kind === "pageIndex")
|
|
554
|
+
return Math.max(0, Math.min(this.getPageCount() - 1, dest.value));
|
|
555
|
+
if (dest.kind === "pageNumber")
|
|
556
|
+
return Math.max(0, Math.min(this.getPageCount() - 1, dest.value - 1));
|
|
197
557
|
return null;
|
|
198
558
|
}
|
|
199
559
|
destroy() {
|
|
200
|
-
this.
|
|
201
|
-
this.
|
|
202
|
-
this.
|
|
560
|
+
this.renderVersion += 1;
|
|
561
|
+
this.renderLock = Promise.resolve();
|
|
562
|
+
this.pendingHrefDestination = null;
|
|
563
|
+
this.destinationSequence = [];
|
|
564
|
+
this.destinationCursor = -1;
|
|
565
|
+
this.lastDestinationNavTime = 0;
|
|
566
|
+
this.lastDestinationPageIndex = null;
|
|
567
|
+
this.lastDestinationHref = null;
|
|
568
|
+
this.disposeReader();
|
|
203
569
|
this.pageSizes.clear();
|
|
204
570
|
if (this.book?.destroy) this.book.destroy();
|
|
205
571
|
this.book = null;
|
|
206
572
|
this.spineItems = [];
|
|
573
|
+
this.coverUrl = null;
|
|
207
574
|
}
|
|
208
575
|
applyRenditionTheme(rendition) {
|
|
209
576
|
if (!rendition) return;
|
|
210
|
-
const fontSize =
|
|
577
|
+
const fontSize = "100%";
|
|
211
578
|
if (rendition.themes?.fontSize) {
|
|
212
579
|
rendition.themes.fontSize(fontSize);
|
|
213
580
|
} else if (rendition.themes?.override) {
|
|
@@ -216,13 +583,457 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
216
583
|
}
|
|
217
584
|
getSpineIndexByHref(href) {
|
|
218
585
|
const normalized = this.normalizeHref(href);
|
|
586
|
+
const decoded = this.decodeHref(normalized);
|
|
219
587
|
if (!normalized) return -1;
|
|
220
|
-
const
|
|
221
|
-
|
|
588
|
+
const candidates = new Set([normalized, decoded].filter(Boolean));
|
|
589
|
+
const exactIndex = this.spineItems.findIndex((item) => {
|
|
590
|
+
const itemHref = this.normalizeHref(item.href);
|
|
591
|
+
const itemDecoded = this.decodeHref(itemHref);
|
|
592
|
+
return candidates.has(itemHref) || candidates.has(itemDecoded);
|
|
593
|
+
});
|
|
594
|
+
if (exactIndex >= 0) return exactIndex;
|
|
595
|
+
return this.spineItems.findIndex((item) => {
|
|
596
|
+
const itemHref = this.normalizeHref(item.href);
|
|
597
|
+
if (!itemHref) return false;
|
|
598
|
+
const itemDecoded = this.decodeHref(itemHref);
|
|
599
|
+
for (const candidate of candidates) {
|
|
600
|
+
if (itemHref.endsWith(candidate) || candidate.endsWith(itemHref) || itemDecoded.endsWith(candidate) || candidate.endsWith(itemDecoded)) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return false;
|
|
605
|
+
});
|
|
222
606
|
}
|
|
223
607
|
normalizeHref(href) {
|
|
608
|
+
if (!href) return "";
|
|
224
609
|
return href.split("#")[0];
|
|
225
610
|
}
|
|
611
|
+
decodeHref(href) {
|
|
612
|
+
if (!href) return "";
|
|
613
|
+
try {
|
|
614
|
+
return decodeURIComponent(href);
|
|
615
|
+
} catch {
|
|
616
|
+
return href;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
getSectionFromHref(href) {
|
|
620
|
+
const normalized = this.normalizeHref(href);
|
|
621
|
+
if (!normalized || !this.book?.spine?.get) return null;
|
|
622
|
+
const decoded = this.decodeHref(normalized);
|
|
623
|
+
const candidates = [normalized, decoded];
|
|
624
|
+
for (const candidate of candidates) {
|
|
625
|
+
if (!candidate) continue;
|
|
626
|
+
try {
|
|
627
|
+
const section = this.book.spine.get(candidate);
|
|
628
|
+
if (section) return section;
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
getSpineItemForPage(pageIndex) {
|
|
635
|
+
const spineIndex = this.toSpineIndex(pageIndex);
|
|
636
|
+
if (spineIndex < 0 || spineIndex >= this.spineItems.length) return null;
|
|
637
|
+
return this.spineItems[spineIndex] ?? null;
|
|
638
|
+
}
|
|
639
|
+
getSpineIndexForSection(section) {
|
|
640
|
+
if (!section) return -1;
|
|
641
|
+
const sectionIdRef = typeof section?.idref === "string" ? section.idref.trim() : "";
|
|
642
|
+
if (sectionIdRef) {
|
|
643
|
+
const idrefIndex = this.spineItems.findIndex(
|
|
644
|
+
(item) => typeof item?.idref === "string" && item.idref.trim() === sectionIdRef
|
|
645
|
+
);
|
|
646
|
+
if (idrefIndex >= 0) return idrefIndex;
|
|
647
|
+
}
|
|
648
|
+
const sectionHref = typeof section?.href === "string" ? this.normalizeHref(section.href) : "";
|
|
649
|
+
if (sectionHref) {
|
|
650
|
+
const hrefIndex = this.getSpineIndexByHref(sectionHref);
|
|
651
|
+
if (hrefIndex >= 0) return hrefIndex;
|
|
652
|
+
}
|
|
653
|
+
if (typeof section?.index === "number" && section.index >= 0) {
|
|
654
|
+
const directItem = this.spineItems[section.index];
|
|
655
|
+
if (directItem) {
|
|
656
|
+
const directHref = this.normalizeHref(directItem?.href ?? "");
|
|
657
|
+
const directIdRef = typeof directItem?.idref === "string" ? directItem.idref.trim() : "";
|
|
658
|
+
const hasSameHref = Boolean(sectionHref && directHref === sectionHref);
|
|
659
|
+
const hasSameIdRef = Boolean(
|
|
660
|
+
sectionIdRef && directIdRef && directIdRef === sectionIdRef
|
|
661
|
+
);
|
|
662
|
+
if (hasSameHref || hasSameIdRef) return section.index;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return -1;
|
|
666
|
+
}
|
|
667
|
+
getPageIndexFromSection(section) {
|
|
668
|
+
const spineIndex = this.getSpineIndexForSection(section);
|
|
669
|
+
if (spineIndex < 0) return null;
|
|
670
|
+
return this.toPageIndexFromSpine(spineIndex);
|
|
671
|
+
}
|
|
672
|
+
isHrefMatchingSpineItem(href, spineItem) {
|
|
673
|
+
const normalizedCandidate = this.normalizeHref(href);
|
|
674
|
+
if (!normalizedCandidate) return false;
|
|
675
|
+
const decodedCandidate = this.decodeHref(normalizedCandidate);
|
|
676
|
+
const itemHref = this.normalizeHref(spineItem?.href ?? "");
|
|
677
|
+
if (!itemHref) return false;
|
|
678
|
+
const itemDecoded = this.decodeHref(itemHref);
|
|
679
|
+
for (const candidate of [normalizedCandidate, decodedCandidate]) {
|
|
680
|
+
if (!candidate) continue;
|
|
681
|
+
if (itemHref === candidate || itemDecoded === candidate || itemHref.endsWith(candidate) || candidate.endsWith(itemHref) || itemDecoded.endsWith(candidate) || candidate.endsWith(itemDecoded)) {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
uniqueDisplayTargets(targets) {
|
|
688
|
+
const deduped = [];
|
|
689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
690
|
+
for (const target of targets) {
|
|
691
|
+
if (typeof target !== "string" && typeof target !== "number") continue;
|
|
692
|
+
const normalized = typeof target === "string" ? target.trim() : String(target).trim();
|
|
693
|
+
if (!normalized) continue;
|
|
694
|
+
const key = `${typeof target}:${normalized}`;
|
|
695
|
+
if (seen.has(key)) continue;
|
|
696
|
+
seen.add(key);
|
|
697
|
+
deduped.push(target);
|
|
698
|
+
}
|
|
699
|
+
return deduped;
|
|
700
|
+
}
|
|
701
|
+
getDisplayTargetsForSpineItem(spineItem) {
|
|
702
|
+
const targets = [];
|
|
703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
704
|
+
const pushTarget = (value) => {
|
|
705
|
+
if (typeof value !== "string" && typeof value !== "number") return;
|
|
706
|
+
const normalized = typeof value === "string" ? value.trim() : String(value).trim();
|
|
707
|
+
if (!normalized) return;
|
|
708
|
+
const key = `${typeof value}:${normalized}`;
|
|
709
|
+
if (seen.has(key)) return;
|
|
710
|
+
seen.add(key);
|
|
711
|
+
targets.push(value);
|
|
712
|
+
};
|
|
713
|
+
pushTarget(spineItem?.href);
|
|
714
|
+
pushTarget(this.decodeHref(spineItem?.href ?? ""));
|
|
715
|
+
pushTarget(spineItem?.idref);
|
|
716
|
+
pushTarget(spineItem?.cfiBase);
|
|
717
|
+
if (typeof spineItem?.index === "number") pushTarget(spineItem.index);
|
|
718
|
+
const sectionFromHref = this.getSectionFromHref(spineItem?.href ?? "");
|
|
719
|
+
if (sectionFromHref) {
|
|
720
|
+
pushTarget(sectionFromHref.href);
|
|
721
|
+
pushTarget(sectionFromHref.idref);
|
|
722
|
+
pushTarget(sectionFromHref.cfiBase);
|
|
723
|
+
if (typeof sectionFromHref.index === "number")
|
|
724
|
+
pushTarget(sectionFromHref.index);
|
|
725
|
+
}
|
|
726
|
+
return targets;
|
|
727
|
+
}
|
|
728
|
+
normalizeDestinationKey(href) {
|
|
729
|
+
if (!href) return "";
|
|
730
|
+
const trimmed = href.trim();
|
|
731
|
+
if (!trimmed) return "";
|
|
732
|
+
return this.decodeHref(trimmed).toLowerCase();
|
|
733
|
+
}
|
|
734
|
+
findDestinationIndexByHref(href) {
|
|
735
|
+
const candidate = this.normalizeDestinationKey(href);
|
|
736
|
+
if (!candidate || !this.destinationSequence.length) return -1;
|
|
737
|
+
const exact = this.destinationSequence.findIndex(
|
|
738
|
+
(entry) => this.normalizeDestinationKey(entry.href) === candidate
|
|
739
|
+
);
|
|
740
|
+
if (exact >= 0) return exact;
|
|
741
|
+
return this.destinationSequence.findIndex((entry) => {
|
|
742
|
+
const key = this.normalizeDestinationKey(entry.href);
|
|
743
|
+
return key === candidate || key.endsWith(candidate) || candidate.endsWith(key);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
findNearestDestinationIndexByPage(pageIndex) {
|
|
747
|
+
if (!this.destinationSequence.length) return -1;
|
|
748
|
+
const candidates = this.destinationSequence.map((entry, idx) => ({ idx, pageIndex: entry.pageIndex })).filter((entry) => entry.pageIndex <= pageIndex);
|
|
749
|
+
if (candidates.length) return candidates[candidates.length - 1].idx;
|
|
750
|
+
const firstNext = this.destinationSequence.findIndex(
|
|
751
|
+
(entry) => entry.pageIndex >= pageIndex
|
|
752
|
+
);
|
|
753
|
+
return firstNext >= 0 ? firstNext : this.destinationSequence.length - 1;
|
|
754
|
+
}
|
|
755
|
+
async ensureDestinationSequence() {
|
|
756
|
+
if (this.destinationSequence.length) return;
|
|
757
|
+
if (!this.book?.loaded?.navigation) return;
|
|
758
|
+
const nav = await this.book.loaded.navigation;
|
|
759
|
+
const toc = nav?.toc ?? [];
|
|
760
|
+
if (!Array.isArray(toc) || !toc.length) return;
|
|
761
|
+
const hrefs = [];
|
|
762
|
+
const walk = (items) => {
|
|
763
|
+
for (const item of items) {
|
|
764
|
+
const href = typeof item?.href === "string" ? item.href.trim() : "";
|
|
765
|
+
if (href) hrefs.push(href);
|
|
766
|
+
if (Array.isArray(item?.subitems) && item.subitems.length) {
|
|
767
|
+
walk(item.subitems);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
walk(toc);
|
|
772
|
+
if (!hrefs.length) return;
|
|
773
|
+
const deduped = [];
|
|
774
|
+
const seen = /* @__PURE__ */ new Set();
|
|
775
|
+
for (const href of hrefs) {
|
|
776
|
+
const key = this.normalizeDestinationKey(href);
|
|
777
|
+
if (!key || seen.has(key)) continue;
|
|
778
|
+
seen.add(key);
|
|
779
|
+
deduped.push(href);
|
|
780
|
+
}
|
|
781
|
+
const sequence = [];
|
|
782
|
+
for (const href of deduped) {
|
|
783
|
+
const pageIndex = await this.resolvePageIndexFromHref(href);
|
|
784
|
+
if (pageIndex == null) continue;
|
|
785
|
+
sequence.push({ href, pageIndex });
|
|
786
|
+
}
|
|
787
|
+
this.destinationSequence = sequence;
|
|
788
|
+
if (this.destinationCursor < 0 && sequence.length) {
|
|
789
|
+
this.destinationCursor = this.findNearestDestinationIndexByPage(
|
|
790
|
+
this.currentPage - 1
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
updateDestinationCursor(href, pageIndex) {
|
|
795
|
+
if (!this.destinationSequence.length) {
|
|
796
|
+
void this.ensureDestinationSequence().then(() => {
|
|
797
|
+
if (!this.destinationSequence.length) return;
|
|
798
|
+
this.updateDestinationCursor(href, pageIndex);
|
|
799
|
+
});
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const byHref = this.findDestinationIndexByHref(href);
|
|
803
|
+
if (byHref >= 0) {
|
|
804
|
+
this.destinationCursor = byHref;
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (typeof pageIndex === "number" && pageIndex >= 0) {
|
|
808
|
+
const byPage = this.findNearestDestinationIndexByPage(pageIndex);
|
|
809
|
+
if (byPage >= 0) this.destinationCursor = byPage;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
getSectionFromTarget(target) {
|
|
813
|
+
if (!this.book?.spine?.get) return null;
|
|
814
|
+
try {
|
|
815
|
+
const section = this.book.spine.get(target);
|
|
816
|
+
if (section) return section;
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
if (typeof target === "string") {
|
|
820
|
+
return this.getSectionFromHref(target);
|
|
821
|
+
}
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
getPageIndexFromRenditionLocation(rendition) {
|
|
825
|
+
const location = rendition?.currentLocation?.();
|
|
826
|
+
if (!location) return null;
|
|
827
|
+
const starts = Array.isArray(location) ? location.map((entry) => entry?.start).filter(Boolean) : [location?.start];
|
|
828
|
+
for (const start of starts) {
|
|
829
|
+
if (!start) continue;
|
|
830
|
+
const href = typeof start?.href === "string" ? this.normalizeHref(start.href) : "";
|
|
831
|
+
if (href) {
|
|
832
|
+
const hrefIndex = this.getSpineIndexByHref(href);
|
|
833
|
+
if (hrefIndex >= 0) return this.toPageIndexFromSpine(hrefIndex);
|
|
834
|
+
}
|
|
835
|
+
if (typeof start?.index === "number" && start.index >= 0) {
|
|
836
|
+
const section = this.getSectionFromTarget(start.index);
|
|
837
|
+
const sectionPageIndex = this.getPageIndexFromSection(section);
|
|
838
|
+
if (sectionPageIndex != null) return sectionPageIndex;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
isNoSectionError(error) {
|
|
844
|
+
if (!error || typeof error !== "object") return false;
|
|
845
|
+
const message = "message" in error && typeof error.message === "string" ? error.message : "";
|
|
846
|
+
return message.includes("No Section Found");
|
|
847
|
+
}
|
|
848
|
+
isTransientDisplayError(error) {
|
|
849
|
+
if (!error || typeof error !== "object") return false;
|
|
850
|
+
const message = "message" in error && typeof error.message === "string" ? error.message : "";
|
|
851
|
+
if (!message) return false;
|
|
852
|
+
const normalized = message.toLowerCase();
|
|
853
|
+
return normalized.includes("cannot read properties of undefined") || normalized.includes("cannot read properties of null") || normalized.includes("reading 'package'") || normalized.includes('reading "package"');
|
|
854
|
+
}
|
|
855
|
+
async displayWithFallback(rendition, targets, isCancelled) {
|
|
856
|
+
for (const target of targets) {
|
|
857
|
+
if (isCancelled()) return null;
|
|
858
|
+
try {
|
|
859
|
+
await rendition.display(target);
|
|
860
|
+
if (isCancelled()) return null;
|
|
861
|
+
const section = this.getSectionFromTarget(target);
|
|
862
|
+
return typeof section?.index === "number" ? section.index : null;
|
|
863
|
+
} catch (error) {
|
|
864
|
+
if (this.isNoSectionError(error) || this.isTransientDisplayError(error)) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return void 0;
|
|
871
|
+
}
|
|
872
|
+
async displayDestinationWithRetry(rendition, href) {
|
|
873
|
+
const targets = this.uniqueDisplayTargets([href, this.decodeHref(href)]);
|
|
874
|
+
if (!targets.length) return false;
|
|
875
|
+
const hasAnchor = href.includes("#");
|
|
876
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
877
|
+
try {
|
|
878
|
+
const firstPass = await this.displayWithFallback(
|
|
879
|
+
rendition,
|
|
880
|
+
targets,
|
|
881
|
+
() => false
|
|
882
|
+
);
|
|
883
|
+
if (firstPass === void 0) {
|
|
884
|
+
this.debugLog("displayDestinationWithRetry:first-pass-miss", {
|
|
885
|
+
href,
|
|
886
|
+
attempt
|
|
887
|
+
});
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
if (!hasAnchor) return true;
|
|
891
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
892
|
+
const secondPass = await this.displayWithFallback(
|
|
893
|
+
rendition,
|
|
894
|
+
targets,
|
|
895
|
+
() => false
|
|
896
|
+
);
|
|
897
|
+
if (secondPass !== void 0) return true;
|
|
898
|
+
this.debugLog("displayDestinationWithRetry:second-pass-miss", {
|
|
899
|
+
href,
|
|
900
|
+
attempt
|
|
901
|
+
});
|
|
902
|
+
} catch {
|
|
903
|
+
this.debugLog("displayDestinationWithRetry:error", { href, attempt });
|
|
904
|
+
}
|
|
905
|
+
await new Promise((resolve) => setTimeout(resolve, 40 * (attempt + 1)));
|
|
906
|
+
}
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
isDebugEnabled() {
|
|
910
|
+
try {
|
|
911
|
+
const globalFlag = Boolean(globalThis?.__PAPYRUS_EPUB_DEBUG__);
|
|
912
|
+
if (globalFlag) return true;
|
|
913
|
+
const localStorageRef = globalThis?.localStorage;
|
|
914
|
+
if (!localStorageRef) return false;
|
|
915
|
+
const persisted = localStorageRef.getItem("papyrus:epubDebug");
|
|
916
|
+
return persisted === "1" || persisted === "true";
|
|
917
|
+
} catch {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
debugLog(message, payload) {
|
|
922
|
+
if (!this.isDebugEnabled()) return;
|
|
923
|
+
if (payload === void 0) {
|
|
924
|
+
console.log("[EPUBEngine]", message);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
console.log("[EPUBEngine]", message, payload);
|
|
928
|
+
}
|
|
929
|
+
async syncSectionHeight(rendition, element, pageIndex, width, seedHeight, targetSectionIndex, skipResize = false) {
|
|
930
|
+
if (_EPUBEngine.USE_INTERNAL_IFRAME_SCROLL) {
|
|
931
|
+
const fallbackHeight = Math.max(360, Math.ceil(seedHeight));
|
|
932
|
+
element.style.height = `${fallbackHeight}px`;
|
|
933
|
+
this.pageSizes.set(pageIndex, { width, height: fallbackHeight });
|
|
934
|
+
if (!skipResize && typeof rendition?.resize === "function" && rendition.manager?.resize) {
|
|
935
|
+
try {
|
|
936
|
+
rendition.resize(width, fallbackHeight);
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const measureElementHeight = (elementToMeasure) => {
|
|
943
|
+
const measuredElement = elementToMeasure;
|
|
944
|
+
if (!measuredElement) return 0;
|
|
945
|
+
const rectHeight = typeof measuredElement.getBoundingClientRect === "function" ? measuredElement.getBoundingClientRect().height : 0;
|
|
946
|
+
return Math.max(
|
|
947
|
+
measuredElement.scrollHeight ?? 0,
|
|
948
|
+
measuredElement.offsetHeight ?? 0,
|
|
949
|
+
measuredElement.clientHeight ?? 0,
|
|
950
|
+
Number.isFinite(rectHeight) ? rectHeight : 0
|
|
951
|
+
);
|
|
952
|
+
};
|
|
953
|
+
const measureDocumentHeight = (doc) => {
|
|
954
|
+
if (!doc) return 0;
|
|
955
|
+
return Math.max(
|
|
956
|
+
measureElementHeight(doc.documentElement),
|
|
957
|
+
measureElementHeight(doc.body)
|
|
958
|
+
);
|
|
959
|
+
};
|
|
960
|
+
const measureContentHeight = (strictTarget) => {
|
|
961
|
+
let measured = 0;
|
|
962
|
+
const contents = typeof rendition?.getContents === "function" ? rendition.getContents() : [];
|
|
963
|
+
if (Array.isArray(contents)) {
|
|
964
|
+
for (const content of contents) {
|
|
965
|
+
if (strictTarget && targetSectionIndex !== null && typeof content?.sectionIndex === "number" && content.sectionIndex !== targetSectionIndex) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const doc = content?.document;
|
|
969
|
+
if (!doc) continue;
|
|
970
|
+
measured = Math.max(measured, measureDocumentHeight(doc));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (measured > 0) return measured;
|
|
974
|
+
const frameSelector = strictTarget && targetSectionIndex !== null ? `.epub-view[ref="${targetSectionIndex}"] iframe` : "iframe";
|
|
975
|
+
const frame = element.querySelector(
|
|
976
|
+
frameSelector
|
|
977
|
+
);
|
|
978
|
+
const fallbackFrame = frame ?? (strictTarget && targetSectionIndex !== null ? element.querySelector("iframe") : null);
|
|
979
|
+
const selectedFrame = fallbackFrame ?? frame;
|
|
980
|
+
const frameDoc = selectedFrame?.contentDocument;
|
|
981
|
+
if (!frameDoc) return 0;
|
|
982
|
+
return measureDocumentHeight(frameDoc);
|
|
983
|
+
};
|
|
984
|
+
let contentHeight = measureContentHeight(true);
|
|
985
|
+
if (contentHeight <= 0 && targetSectionIndex !== null) {
|
|
986
|
+
contentHeight = measureContentHeight(false);
|
|
987
|
+
}
|
|
988
|
+
if (contentHeight <= 0) {
|
|
989
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
990
|
+
contentHeight = measureContentHeight(true);
|
|
991
|
+
if (contentHeight <= 0 && targetSectionIndex !== null) {
|
|
992
|
+
contentHeight = measureContentHeight(false);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const measuredTarget = contentHeight > 0 ? Math.ceil(contentHeight + _EPUBEngine.HEIGHT_PADDING) : Math.ceil(seedHeight);
|
|
996
|
+
const minA4Height = this.getA4MinHeight(width);
|
|
997
|
+
const unclampedTarget = Math.max(minA4Height, measuredTarget);
|
|
998
|
+
const targetHeight = Math.max(
|
|
999
|
+
minA4Height,
|
|
1000
|
+
Math.min(_EPUBEngine.MAX_SECTION_HEIGHT, unclampedTarget)
|
|
1001
|
+
);
|
|
1002
|
+
if (targetHeight !== unclampedTarget) {
|
|
1003
|
+
this.debugLog("syncSectionHeight:clamped", {
|
|
1004
|
+
pageIndex,
|
|
1005
|
+
measuredTarget,
|
|
1006
|
+
max: _EPUBEngine.MAX_SECTION_HEIGHT
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
if (targetHeight <= 0) return;
|
|
1010
|
+
element.style.height = `${targetHeight}px`;
|
|
1011
|
+
this.pageSizes.set(pageIndex, { width, height: targetHeight });
|
|
1012
|
+
if (!skipResize && typeof rendition?.resize === "function" && rendition.manager?.resize) {
|
|
1013
|
+
try {
|
|
1014
|
+
rendition.resize(width, targetHeight);
|
|
1015
|
+
} catch {
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async resolvePageIndexFromHref(href) {
|
|
1020
|
+
const normalizedHref = href.trim();
|
|
1021
|
+
if (!normalizedHref) return null;
|
|
1022
|
+
const section = this.getSectionFromHref(normalizedHref);
|
|
1023
|
+
const sectionPageIndex = this.getPageIndexFromSection(section);
|
|
1024
|
+
if (sectionPageIndex != null) return sectionPageIndex;
|
|
1025
|
+
const spineIndex = this.getSpineIndexByHref(
|
|
1026
|
+
this.normalizeHref(normalizedHref)
|
|
1027
|
+
);
|
|
1028
|
+
return spineIndex >= 0 ? this.toPageIndexFromSpine(spineIndex) : null;
|
|
1029
|
+
}
|
|
1030
|
+
extractHrefDestination(dest) {
|
|
1031
|
+
if (typeof dest === "string") return dest;
|
|
1032
|
+
if (dest?.kind === "href" && typeof dest.value === "string") {
|
|
1033
|
+
return dest.value;
|
|
1034
|
+
}
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
226
1037
|
isUriSource(source) {
|
|
227
1038
|
return typeof source === "object" && source !== null && "uri" in source;
|
|
228
1039
|
}
|
|
@@ -270,7 +1081,9 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
270
1081
|
decodeBase64(value) {
|
|
271
1082
|
const clean = value.replace(/\s/g, "");
|
|
272
1083
|
if (typeof atob !== "function") {
|
|
273
|
-
throw new Error(
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
"[EPUBEngine] atob n\xE3o est\xE1 dispon\xEDvel para decodificar base64."
|
|
1086
|
+
);
|
|
274
1087
|
}
|
|
275
1088
|
const binary = atob(clean);
|
|
276
1089
|
const len = binary.length;
|
|
@@ -289,6 +1102,372 @@ var EPUBEngine = class extends import_core.BaseDocumentEngine {
|
|
|
289
1102
|
if (value.length < 16) return false;
|
|
290
1103
|
return /^[A-Za-z0-9+/=]+$/.test(value);
|
|
291
1104
|
}
|
|
1105
|
+
createRendition(element, width, height) {
|
|
1106
|
+
const rendition = this.book.renderTo(element, {
|
|
1107
|
+
width,
|
|
1108
|
+
height,
|
|
1109
|
+
flow: "scrolled-doc",
|
|
1110
|
+
spread: "none",
|
|
1111
|
+
method: "write",
|
|
1112
|
+
overflow: _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL ? "auto" : "hidden"
|
|
1113
|
+
});
|
|
1114
|
+
this.registerContentHook(rendition);
|
|
1115
|
+
return rendition;
|
|
1116
|
+
}
|
|
1117
|
+
disposeReader() {
|
|
1118
|
+
const rendition = this.readerRendition;
|
|
1119
|
+
if (!rendition) return;
|
|
1120
|
+
try {
|
|
1121
|
+
rendition?.manager?.destroy?.();
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
this.readerRendition = null;
|
|
1125
|
+
this.readerTarget = null;
|
|
1126
|
+
this.heightSyncVersion += 1;
|
|
1127
|
+
}
|
|
1128
|
+
registerContentHook(rendition) {
|
|
1129
|
+
if (!rendition?.hooks?.content?.register) return;
|
|
1130
|
+
rendition.hooks.content.register((contents) => {
|
|
1131
|
+
try {
|
|
1132
|
+
const setImportantStyle = (node, property, value) => {
|
|
1133
|
+
if (!node) return;
|
|
1134
|
+
node.style.setProperty(property, value, "important");
|
|
1135
|
+
};
|
|
1136
|
+
const useInternalScroll = _EPUBEngine.USE_INTERNAL_IFRAME_SCROLL;
|
|
1137
|
+
const managerContainer = rendition?.manager?.container;
|
|
1138
|
+
const viewsContainer = rendition?.manager?.views?.container;
|
|
1139
|
+
if (managerContainer) {
|
|
1140
|
+
managerContainer.classList.add("papyrus-epub-scroll-host");
|
|
1141
|
+
managerContainer.style.overflow = useInternalScroll ? "auto" : "hidden";
|
|
1142
|
+
managerContainer.style.overflowY = useInternalScroll ? "auto" : "hidden";
|
|
1143
|
+
managerContainer.style.overflowX = "hidden";
|
|
1144
|
+
managerContainer.style.height = "100%";
|
|
1145
|
+
managerContainer.style.maxHeight = "100%";
|
|
1146
|
+
managerContainer.style.scrollbarWidth = useInternalScroll ? "thin" : "none";
|
|
1147
|
+
managerContainer.style.setProperty(
|
|
1148
|
+
"-webkit-overflow-scrolling",
|
|
1149
|
+
"touch"
|
|
1150
|
+
);
|
|
1151
|
+
managerContainer.style.setProperty("overscroll-behavior", "contain");
|
|
1152
|
+
}
|
|
1153
|
+
if (viewsContainer) {
|
|
1154
|
+
viewsContainer.style.overflow = useInternalScroll ? "visible" : "hidden";
|
|
1155
|
+
viewsContainer.style.overflowY = useInternalScroll ? "visible" : "hidden";
|
|
1156
|
+
viewsContainer.style.overflowX = "hidden";
|
|
1157
|
+
viewsContainer.style.minHeight = "100%";
|
|
1158
|
+
viewsContainer.style.scrollbarWidth = useInternalScroll ? "auto" : "none";
|
|
1159
|
+
}
|
|
1160
|
+
const frame = contents?.window?.frameElement;
|
|
1161
|
+
const mobileProbe = managerContainer ?? frame ?? contents?.document?.body;
|
|
1162
|
+
const mobileWidthHint = Math.max(
|
|
1163
|
+
managerContainer?.clientWidth ?? 0,
|
|
1164
|
+
frame?.clientWidth ?? 0,
|
|
1165
|
+
mobileProbe?.clientWidth ?? 0
|
|
1166
|
+
);
|
|
1167
|
+
const isMobileInternalScroll = Boolean(
|
|
1168
|
+
useInternalScroll && mobileProbe && this.isMobileViewport(mobileProbe, mobileWidthHint)
|
|
1169
|
+
);
|
|
1170
|
+
const contentOverflow = useInternalScroll ? "hidden" : "visible";
|
|
1171
|
+
const doc = contents?.document;
|
|
1172
|
+
const root = doc?.documentElement;
|
|
1173
|
+
const body = doc?.body;
|
|
1174
|
+
contents?.overflow?.(contentOverflow);
|
|
1175
|
+
contents?.overflowX?.("hidden");
|
|
1176
|
+
contents?.overflowY?.(contentOverflow);
|
|
1177
|
+
contents?.css?.("overflow", contentOverflow, true);
|
|
1178
|
+
contents?.css?.("overflow-y", contentOverflow, true);
|
|
1179
|
+
contents?.css?.("overflow-x", "hidden", true);
|
|
1180
|
+
contents?.css?.("height", "auto", true);
|
|
1181
|
+
contents?.css?.("max-height", "none", true);
|
|
1182
|
+
contents?.css?.("min-height", "100%", true);
|
|
1183
|
+
if (root) {
|
|
1184
|
+
setImportantStyle(root, "overflow", contentOverflow);
|
|
1185
|
+
setImportantStyle(root, "overflow-y", contentOverflow);
|
|
1186
|
+
setImportantStyle(root, "overflow-x", "hidden");
|
|
1187
|
+
setImportantStyle(root, "height", "auto");
|
|
1188
|
+
setImportantStyle(root, "min-height", "100%");
|
|
1189
|
+
setImportantStyle(root, "max-height", "none");
|
|
1190
|
+
setImportantStyle(root, "scrollbar-width", "none");
|
|
1191
|
+
setImportantStyle(root, "overscroll-behavior", "none");
|
|
1192
|
+
}
|
|
1193
|
+
if (body) {
|
|
1194
|
+
setImportantStyle(body, "overflow", contentOverflow);
|
|
1195
|
+
setImportantStyle(body, "overflow-y", contentOverflow);
|
|
1196
|
+
setImportantStyle(body, "overflow-x", "hidden");
|
|
1197
|
+
setImportantStyle(body, "height", "auto");
|
|
1198
|
+
setImportantStyle(body, "min-height", "100%");
|
|
1199
|
+
setImportantStyle(body, "max-height", "none");
|
|
1200
|
+
setImportantStyle(body, "scrollbar-width", "none");
|
|
1201
|
+
setImportantStyle(body, "overscroll-behavior", "none");
|
|
1202
|
+
if (isMobileInternalScroll) {
|
|
1203
|
+
setImportantStyle(body, "margin", "0");
|
|
1204
|
+
setImportantStyle(body, "padding", "0");
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (useInternalScroll && managerContainer && doc && root) {
|
|
1208
|
+
const proxyMarker = "data-papyrus-scroll-proxy";
|
|
1209
|
+
if (!root.hasAttribute(proxyMarker)) {
|
|
1210
|
+
root.setAttribute(proxyMarker, "1");
|
|
1211
|
+
const scrollHost = managerContainer;
|
|
1212
|
+
const onWheel = (event) => {
|
|
1213
|
+
if (event.defaultPrevented) return;
|
|
1214
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
1215
|
+
const deltaY = Number(event.deltaY) || 0;
|
|
1216
|
+
const deltaX = Number(event.deltaX) || 0;
|
|
1217
|
+
if (!deltaY && !deltaX) return;
|
|
1218
|
+
const previousTop = scrollHost.scrollTop;
|
|
1219
|
+
const previousLeft = scrollHost.scrollLeft;
|
|
1220
|
+
scrollHost.scrollTop += deltaY;
|
|
1221
|
+
scrollHost.scrollLeft += deltaX;
|
|
1222
|
+
const moved = scrollHost.scrollTop !== previousTop || scrollHost.scrollLeft !== previousLeft;
|
|
1223
|
+
if (moved && event.cancelable) event.preventDefault();
|
|
1224
|
+
};
|
|
1225
|
+
let lastTouchY = null;
|
|
1226
|
+
let lastTouchX = null;
|
|
1227
|
+
const onTouchStart = (event) => {
|
|
1228
|
+
if (event.touches.length !== 1) return;
|
|
1229
|
+
lastTouchY = event.touches[0].clientY;
|
|
1230
|
+
lastTouchX = event.touches[0].clientX;
|
|
1231
|
+
};
|
|
1232
|
+
const onTouchMove = (event) => {
|
|
1233
|
+
if (event.touches.length !== 1) return;
|
|
1234
|
+
const touch = event.touches[0];
|
|
1235
|
+
if (lastTouchY == null || lastTouchX == null) {
|
|
1236
|
+
lastTouchY = touch.clientY;
|
|
1237
|
+
lastTouchX = touch.clientX;
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const deltaY = lastTouchY - touch.clientY;
|
|
1241
|
+
const deltaX = lastTouchX - touch.clientX;
|
|
1242
|
+
const previousTop = scrollHost.scrollTop;
|
|
1243
|
+
const previousLeft = scrollHost.scrollLeft;
|
|
1244
|
+
if (deltaY || deltaX) {
|
|
1245
|
+
scrollHost.scrollTop += deltaY;
|
|
1246
|
+
scrollHost.scrollLeft += deltaX;
|
|
1247
|
+
}
|
|
1248
|
+
const moved = scrollHost.scrollTop !== previousTop || scrollHost.scrollLeft !== previousLeft;
|
|
1249
|
+
if (moved && event.cancelable) event.preventDefault();
|
|
1250
|
+
lastTouchY = touch.clientY;
|
|
1251
|
+
lastTouchX = touch.clientX;
|
|
1252
|
+
};
|
|
1253
|
+
const onTouchEnd = () => {
|
|
1254
|
+
lastTouchY = null;
|
|
1255
|
+
lastTouchX = null;
|
|
1256
|
+
};
|
|
1257
|
+
doc.addEventListener("wheel", onWheel, { passive: false });
|
|
1258
|
+
doc.addEventListener("touchstart", onTouchStart, {
|
|
1259
|
+
passive: true
|
|
1260
|
+
});
|
|
1261
|
+
doc.addEventListener("touchmove", onTouchMove, {
|
|
1262
|
+
passive: false
|
|
1263
|
+
});
|
|
1264
|
+
doc.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
1265
|
+
doc.addEventListener("touchcancel", onTouchEnd, {
|
|
1266
|
+
passive: true
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (isMobileInternalScroll && doc?.body) {
|
|
1271
|
+
const singleImage = this.getSingleImageForMobileLayout(doc);
|
|
1272
|
+
if (singleImage) {
|
|
1273
|
+
doc.body.style.display = "flex";
|
|
1274
|
+
doc.body.style.justifyContent = "center";
|
|
1275
|
+
doc.body.style.alignItems = "flex-start";
|
|
1276
|
+
singleImage.style.width = "100%";
|
|
1277
|
+
singleImage.style.maxWidth = "100%";
|
|
1278
|
+
singleImage.style.height = "auto";
|
|
1279
|
+
singleImage.style.maxHeight = "100%";
|
|
1280
|
+
singleImage.style.display = "block";
|
|
1281
|
+
singleImage.style.objectFit = "contain";
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (frame) {
|
|
1285
|
+
if (useInternalScroll) {
|
|
1286
|
+
frame.setAttribute("scrolling", "no");
|
|
1287
|
+
setImportantStyle(frame, "overflow", "hidden");
|
|
1288
|
+
setImportantStyle(frame, "overflow-y", "hidden");
|
|
1289
|
+
setImportantStyle(frame, "overflow-x", "hidden");
|
|
1290
|
+
setImportantStyle(frame, "height", "auto");
|
|
1291
|
+
setImportantStyle(frame, "min-height", "100%");
|
|
1292
|
+
setImportantStyle(frame, "max-height", "none");
|
|
1293
|
+
setImportantStyle(frame, "width", "100%");
|
|
1294
|
+
setImportantStyle(frame, "max-width", "100%");
|
|
1295
|
+
setImportantStyle(frame, "display", "block");
|
|
1296
|
+
setImportantStyle(frame, "border", "0");
|
|
1297
|
+
} else {
|
|
1298
|
+
frame.setAttribute("scrolling", "no");
|
|
1299
|
+
setImportantStyle(frame, "overflow", "hidden");
|
|
1300
|
+
setImportantStyle(frame, "height", "auto");
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
scheduleHeightSync(syncVersion, rendition, element, pageIndex, width, fallbackHeight, targetSectionIndex, skipResize = false) {
|
|
1308
|
+
const run = () => {
|
|
1309
|
+
if (syncVersion !== this.heightSyncVersion) return;
|
|
1310
|
+
if (this.readerRendition !== rendition) return;
|
|
1311
|
+
if (this.readerTarget !== element) return;
|
|
1312
|
+
void this.syncSectionHeight(
|
|
1313
|
+
rendition,
|
|
1314
|
+
element,
|
|
1315
|
+
pageIndex,
|
|
1316
|
+
width,
|
|
1317
|
+
fallbackHeight,
|
|
1318
|
+
targetSectionIndex,
|
|
1319
|
+
skipResize
|
|
1320
|
+
);
|
|
1321
|
+
};
|
|
1322
|
+
setTimeout(run, 80);
|
|
1323
|
+
setTimeout(run, 260);
|
|
1324
|
+
setTimeout(run, 620);
|
|
1325
|
+
setTimeout(run, 1200);
|
|
1326
|
+
}
|
|
1327
|
+
getViewportHeightHint(element) {
|
|
1328
|
+
const viewerRoot = element.closest(".papyrus-viewer");
|
|
1329
|
+
const hostParent = element.parentElement;
|
|
1330
|
+
const measured = Math.max(
|
|
1331
|
+
viewerRoot?.clientHeight ?? 0,
|
|
1332
|
+
hostParent?.clientHeight ?? 0,
|
|
1333
|
+
element.clientHeight ?? 0
|
|
1334
|
+
);
|
|
1335
|
+
if (measured <= 0) return null;
|
|
1336
|
+
const viewportWidth = Math.max(
|
|
1337
|
+
viewerRoot?.clientWidth ?? 0,
|
|
1338
|
+
globalThis?.visualViewport?.width ?? 0,
|
|
1339
|
+
globalThis?.innerWidth ?? 0
|
|
1340
|
+
);
|
|
1341
|
+
const isMobileViewport = viewportWidth > 0 && viewportWidth <= _EPUBEngine.MOBILE_VIEWPORT_MAX_WIDTH_PX;
|
|
1342
|
+
if (!isMobileViewport) return null;
|
|
1343
|
+
let topbarOffset = 0;
|
|
1344
|
+
const shell = viewerRoot?.parentElement?.parentElement;
|
|
1345
|
+
if (shell) {
|
|
1346
|
+
const shellHeight = shell.getBoundingClientRect().height;
|
|
1347
|
+
const topbar = Array.from(shell.children).find(
|
|
1348
|
+
(child) => child.classList?.contains("papyrus-topbar")
|
|
1349
|
+
);
|
|
1350
|
+
const topbarHeight = topbar?.getBoundingClientRect().height ?? 0;
|
|
1351
|
+
const viewerLikelyIncludesTopbar = topbarHeight > 0 && shellHeight > 0 && measured >= shellHeight - 2;
|
|
1352
|
+
if (viewerLikelyIncludesTopbar) {
|
|
1353
|
+
topbarOffset = Math.ceil(topbarHeight);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const adjusted = measured - topbarOffset - _EPUBEngine.INTERNAL_VIEWPORT_PADDING_PX;
|
|
1357
|
+
return Math.max(280, Math.floor(adjusted));
|
|
1358
|
+
}
|
|
1359
|
+
getLinearSpineItems(items) {
|
|
1360
|
+
const linearItems = items.filter((item) => item?.linear !== "no");
|
|
1361
|
+
if (!linearItems.length) return items;
|
|
1362
|
+
const deduped = [];
|
|
1363
|
+
for (const item of linearItems) {
|
|
1364
|
+
const prev = deduped[deduped.length - 1];
|
|
1365
|
+
const prevHref = this.normalizeHref(prev?.href ?? "");
|
|
1366
|
+
const currentHref = this.normalizeHref(item?.href ?? "");
|
|
1367
|
+
if (prevHref && currentHref && prevHref === currentHref) continue;
|
|
1368
|
+
deduped.push(item);
|
|
1369
|
+
}
|
|
1370
|
+
return deduped.length ? deduped : linearItems;
|
|
1371
|
+
}
|
|
1372
|
+
hasCoverPage() {
|
|
1373
|
+
return Boolean(this.coverUrl);
|
|
1374
|
+
}
|
|
1375
|
+
isCoverPage(pageIndex) {
|
|
1376
|
+
return this.hasCoverPage() && pageIndex === 0;
|
|
1377
|
+
}
|
|
1378
|
+
toSpineIndex(pageIndex) {
|
|
1379
|
+
return pageIndex - (this.hasCoverPage() ? 1 : 0);
|
|
1380
|
+
}
|
|
1381
|
+
toPageIndexFromSpine(spineIndex) {
|
|
1382
|
+
return spineIndex + (this.hasCoverPage() ? 1 : 0);
|
|
1383
|
+
}
|
|
1384
|
+
getA4MinHeight(width) {
|
|
1385
|
+
if (!Number.isFinite(width) || width <= 0) return 900;
|
|
1386
|
+
return Math.max(480, Math.ceil(width * _EPUBEngine.A4_RATIO));
|
|
1387
|
+
}
|
|
1388
|
+
renderCoverPage(element, width, height, pageIndex) {
|
|
1389
|
+
this.disposeReader();
|
|
1390
|
+
element.innerHTML = "";
|
|
1391
|
+
element.style.width = `${width}px`;
|
|
1392
|
+
element.style.height = `${height}px`;
|
|
1393
|
+
const isMobileViewport = this.isMobileViewport(element, width);
|
|
1394
|
+
const isLandscapeViewport = width > height;
|
|
1395
|
+
const wrapper = document.createElement("div");
|
|
1396
|
+
wrapper.style.width = "100%";
|
|
1397
|
+
wrapper.style.minHeight = `${height}px`;
|
|
1398
|
+
wrapper.style.display = "flex";
|
|
1399
|
+
wrapper.style.alignItems = "center";
|
|
1400
|
+
wrapper.style.justifyContent = "center";
|
|
1401
|
+
wrapper.style.background = "#fff";
|
|
1402
|
+
wrapper.style.padding = isMobileViewport ? "0" : "16px";
|
|
1403
|
+
wrapper.style.boxSizing = "border-box";
|
|
1404
|
+
if (isMobileViewport) {
|
|
1405
|
+
wrapper.style.overflow = "hidden";
|
|
1406
|
+
}
|
|
1407
|
+
const img = document.createElement("img");
|
|
1408
|
+
img.src = this.coverUrl ?? "";
|
|
1409
|
+
img.alt = "Capa";
|
|
1410
|
+
img.style.maxWidth = "100%";
|
|
1411
|
+
img.style.maxHeight = "100%";
|
|
1412
|
+
img.style.width = isMobileViewport ? "100%" : "auto";
|
|
1413
|
+
img.style.height = isMobileViewport ? "100%" : "auto";
|
|
1414
|
+
img.style.display = "block";
|
|
1415
|
+
img.style.objectFit = isMobileViewport && !isLandscapeViewport ? "cover" : "contain";
|
|
1416
|
+
img.style.boxShadow = isMobileViewport ? "none" : "0 16px 32px rgba(0,0,0,0.15)";
|
|
1417
|
+
img.style.borderRadius = isMobileViewport ? "0" : "8px";
|
|
1418
|
+
img.onload = () => {
|
|
1419
|
+
if (isMobileViewport) {
|
|
1420
|
+
const targetHeight2 = Math.max(280, Math.floor(height));
|
|
1421
|
+
wrapper.style.minHeight = `${targetHeight2}px`;
|
|
1422
|
+
element.style.height = `${targetHeight2}px`;
|
|
1423
|
+
this.pageSizes.set(pageIndex, { width, height: targetHeight2 });
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const naturalWidth = img.naturalWidth || width;
|
|
1427
|
+
const naturalHeight = img.naturalHeight || height;
|
|
1428
|
+
if (naturalWidth <= 0 || naturalHeight <= 0) return;
|
|
1429
|
+
const displayedWidth = Math.min(naturalWidth, Math.max(1, width - 32));
|
|
1430
|
+
const scaledHeight = Math.round(
|
|
1431
|
+
naturalHeight / naturalWidth * displayedWidth
|
|
1432
|
+
);
|
|
1433
|
+
const targetHeight = Math.max(height, scaledHeight + 32);
|
|
1434
|
+
wrapper.style.minHeight = `${targetHeight}px`;
|
|
1435
|
+
element.style.height = `${targetHeight}px`;
|
|
1436
|
+
this.pageSizes.set(pageIndex, { width, height: targetHeight });
|
|
1437
|
+
};
|
|
1438
|
+
wrapper.appendChild(img);
|
|
1439
|
+
element.appendChild(wrapper);
|
|
1440
|
+
this.pageSizes.set(pageIndex, { width, height });
|
|
1441
|
+
}
|
|
1442
|
+
isMobileViewport(element, widthHint) {
|
|
1443
|
+
const viewerRoot = element.closest(".papyrus-viewer");
|
|
1444
|
+
const viewportWidth = Math.max(
|
|
1445
|
+
widthHint,
|
|
1446
|
+
viewerRoot?.clientWidth ?? 0,
|
|
1447
|
+
globalThis?.visualViewport?.width ?? 0,
|
|
1448
|
+
globalThis?.innerWidth ?? 0
|
|
1449
|
+
);
|
|
1450
|
+
const viewportHeight = Math.max(
|
|
1451
|
+
viewerRoot?.clientHeight ?? 0,
|
|
1452
|
+
globalThis?.visualViewport?.height ?? 0,
|
|
1453
|
+
globalThis?.innerHeight ?? 0
|
|
1454
|
+
);
|
|
1455
|
+
const isShortLandscape = viewportHeight > 0 && viewportHeight <= _EPUBEngine.MOBILE_SHORT_VIEWPORT_MAX_HEIGHT_PX && viewportWidth > viewportHeight;
|
|
1456
|
+
return Boolean(
|
|
1457
|
+
viewportWidth > 0 && (viewportWidth <= _EPUBEngine.MOBILE_VIEWPORT_MAX_WIDTH_PX || isShortLandscape)
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
getSingleImageForMobileLayout(doc) {
|
|
1461
|
+
const body = doc.body;
|
|
1462
|
+
if (!body) return null;
|
|
1463
|
+
const images = Array.from(
|
|
1464
|
+
body.querySelectorAll("img")
|
|
1465
|
+
);
|
|
1466
|
+
if (images.length !== 1) return null;
|
|
1467
|
+
const textLength = (body.textContent ?? "").replace(/\s+/g, "").length;
|
|
1468
|
+
if (textLength > 48) return null;
|
|
1469
|
+
return images[0] ?? null;
|
|
1470
|
+
}
|
|
292
1471
|
};
|
|
293
1472
|
// Annotate the CommonJS export names for ESM import in node:
|
|
294
1473
|
0 && (module.exports = {
|