@prose-reader/cbz 1.317.0 → 1.319.0
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/detectPanoramaFromBasename.d.ts +5 -0
- package/dist/enhancer.d.ts +7 -0
- package/dist/index.js +47 -46
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +2 -2
- package/dist/index.umd.cjs.map +1 -1
- package/dist/panoramaSplitManifest.d.ts +33 -0
- package/dist/panoramaSplitResource.d.ts +17 -0
- package/dist/streamer.d.ts +2 -2
- package/package.json +2 -2
- package/dist/detectPageSpreadFromBasename.d.ts +0 -5
- package/dist/pageSpreadSplitManifest.d.ts +0 -33
- package/dist/pageSpreadSplitResource.d.ts +0 -17
- /package/dist/{detectPageSpreadFromBasename.test.d.ts → detectPanoramaFromBasename.test.d.ts} +0 -0
- /package/dist/{pageSpreadSplitManifest.test.d.ts → panoramaSplitManifest.test.d.ts} +0 -0
- /package/dist/{pageSpreadSplitResource.test.d.ts → panoramaSplitResource.test.d.ts} +0 -0
package/dist/enhancer.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Reader } from '@prose-reader/core';
|
|
2
2
|
export type CbzEnhancerAPI = {
|
|
3
3
|
__PROSE_READER_ENHANCER_CBZ: true;
|
|
4
|
+
cbz: {
|
|
5
|
+
isPanoramaSpineItem: (spineItem: {
|
|
6
|
+
item: {
|
|
7
|
+
href: string;
|
|
8
|
+
};
|
|
9
|
+
}) => boolean;
|
|
10
|
+
};
|
|
4
11
|
};
|
|
5
12
|
export declare const cbzEnhancer: <InheritOptions, InheritOutput extends Reader>(next: (options: InheritOptions) => InheritOutput) => (options: InheritOptions) => InheritOutput & CbzEnhancerAPI;
|
package/dist/index.js
CHANGED
|
@@ -51,12 +51,12 @@ var s = (e) => e === "left" ? {
|
|
|
51
51
|
firstPageLabel: n,
|
|
52
52
|
secondPageLabel: r
|
|
53
53
|
};
|
|
54
|
-
}, _ = "__prose-reader__/
|
|
54
|
+
}, _ = "__prose-reader__/panorama", v = "application/xhtml+xml", y = /* @__PURE__ */ new Set([
|
|
55
55
|
"image/jpg",
|
|
56
56
|
"image/jpeg",
|
|
57
57
|
"image/png",
|
|
58
58
|
"image/webp"
|
|
59
|
-
]), b = (e) => e
|
|
59
|
+
]), b = (e) => e !== void 0 && y.has(e), x = (e) => encodeURIComponent(e), S = ({ baseUrl: e = "", resourcePath: t }) => {
|
|
60
60
|
if (!e && /^https?:\/\//.test(t)) return encodeURI(t);
|
|
61
61
|
let n = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
|
|
62
62
|
return encodeURI(`${n}${t}`);
|
|
@@ -103,7 +103,7 @@ var s = (e) => e === "left" ? {
|
|
|
103
103
|
e.startsWith(r) && n.push(e.slice(r.length));
|
|
104
104
|
}
|
|
105
105
|
return n;
|
|
106
|
-
},
|
|
106
|
+
}, re = (e) => o(e?.encodingFormat ?? "") || r(e?.basename ?? ""), j = (e) => r(e.uri) || r(e.basename), M = (e) => e === void 0 || e.dir || !b(j(e)) ? !1 : b(re(e)), N = ({ archive: e, baseUrl: t }) => async (n) => {
|
|
107
107
|
if (w(e)) return n;
|
|
108
108
|
let r = [], i = n.spineItems.flatMap((i) => {
|
|
109
109
|
let a = A({
|
|
@@ -111,7 +111,7 @@ var s = (e) => e === "left" ? {
|
|
|
111
111
|
baseUrl: t,
|
|
112
112
|
spineItem: i
|
|
113
113
|
});
|
|
114
|
-
if (!
|
|
114
|
+
if (!M(a)) return [i];
|
|
115
115
|
let o = g(a.basename);
|
|
116
116
|
if (o === void 0) return [i];
|
|
117
117
|
let [s, c] = D(n.readingDirection), l = i.progressionWeight === void 0 ? void 0 : i.progressionWeight / 2, u = O({
|
|
@@ -142,25 +142,25 @@ var s = (e) => e === "left" ? {
|
|
|
142
142
|
})),
|
|
143
143
|
items: [...n.items, ...r]
|
|
144
144
|
};
|
|
145
|
-
},
|
|
145
|
+
}, P = /* @__PURE__ */ new WeakMap(), F = (e) => {
|
|
146
146
|
try {
|
|
147
147
|
return decodeURIComponent(e);
|
|
148
148
|
} catch {
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
|
-
},
|
|
151
|
+
}, I = (e) => {
|
|
152
152
|
let t = e.indexOf(`${_}/`);
|
|
153
153
|
if (t < 0) return;
|
|
154
154
|
let n = e.slice(t).split("/"), r = n[2], i = n[3];
|
|
155
|
-
if (n.length !== 4 || n[0] !== "__prose-reader__" || n[1] !== "
|
|
155
|
+
if (n.length !== 4 || n[0] !== "__prose-reader__" || n[1] !== "panorama" || r === void 0 || i === void 0) return;
|
|
156
156
|
let a = i.split(".")[0];
|
|
157
157
|
if (a !== "left" && a !== "right") return;
|
|
158
|
-
let o =
|
|
158
|
+
let o = F(r);
|
|
159
159
|
if (o !== void 0) return {
|
|
160
160
|
originalUri: o,
|
|
161
161
|
cropSide: a
|
|
162
162
|
};
|
|
163
|
-
},
|
|
163
|
+
}, L = ({ cropSide: e, imageHeight: t, imageWidth: n }) => {
|
|
164
164
|
let r = Math.floor(n / 2), i = n - r;
|
|
165
165
|
return e === "left" ? {
|
|
166
166
|
x: 0,
|
|
@@ -171,8 +171,8 @@ var s = (e) => e === "left" ? {
|
|
|
171
171
|
width: i,
|
|
172
172
|
height: t
|
|
173
173
|
};
|
|
174
|
-
},
|
|
175
|
-
if (typeof createImageBitmap != "function") throw Error("
|
|
174
|
+
}, R = (e) => /^https?:\/\//.test(e) ? e : `../../../${encodeURI(e)}`, z = async (e) => {
|
|
175
|
+
if (typeof createImageBitmap != "function") throw Error("Panorama XHTML generation requires createImageBitmap");
|
|
176
176
|
let t = await createImageBitmap(e);
|
|
177
177
|
try {
|
|
178
178
|
return {
|
|
@@ -182,9 +182,9 @@ var s = (e) => e === "left" ? {
|
|
|
182
182
|
} finally {
|
|
183
183
|
t.close();
|
|
184
184
|
}
|
|
185
|
-
},
|
|
186
|
-
if (t.width < 2) throw Error("
|
|
187
|
-
let r =
|
|
185
|
+
}, B = ({ cropSide: e, imageDimensions: t, originalUri: n }) => {
|
|
186
|
+
if (t.width < 2) throw Error("Panorama image is too narrow to split");
|
|
187
|
+
let r = L({
|
|
188
188
|
cropSide: e,
|
|
189
189
|
imageHeight: t.height,
|
|
190
190
|
imageWidth: t.width
|
|
@@ -214,27 +214,27 @@ var s = (e) => e === "left" ? {
|
|
|
214
214
|
</style>
|
|
215
215
|
</head>
|
|
216
216
|
<body>
|
|
217
|
-
<img src="${i(
|
|
217
|
+
<img src="${i(R(n))}" alt="" />
|
|
218
218
|
</body>
|
|
219
219
|
</html>`;
|
|
220
|
-
},
|
|
221
|
-
let n =
|
|
220
|
+
}, V = async ({ archive: e, resourcePath: t }) => {
|
|
221
|
+
let n = I(t);
|
|
222
222
|
if (n === void 0) return;
|
|
223
223
|
let r = e.records.find((e) => e.uri === n.originalUri && !e.dir);
|
|
224
|
-
if (r === void 0 || r.dir) throw Error(`no source file found for virtual
|
|
225
|
-
let i =
|
|
226
|
-
|
|
227
|
-
let a = i.get(n.originalUri) ?? await
|
|
224
|
+
if (r === void 0 || r.dir) throw Error(`no source file found for virtual panorama resourcePath:${t}`);
|
|
225
|
+
let i = P.get(e) ?? /* @__PURE__ */ new Map();
|
|
226
|
+
P.has(e) || P.set(e, i);
|
|
227
|
+
let a = i.get(n.originalUri) ?? await z(await r.blob());
|
|
228
228
|
return i.set(n.originalUri, a), {
|
|
229
|
-
body:
|
|
229
|
+
body: B({
|
|
230
230
|
cropSide: n.cropSide,
|
|
231
231
|
imageDimensions: a,
|
|
232
232
|
originalUri: n.originalUri
|
|
233
233
|
}),
|
|
234
234
|
params: { contentType: v }
|
|
235
235
|
};
|
|
236
|
-
},
|
|
237
|
-
let r = await
|
|
236
|
+
}, H = ({ archive: e, resourcePath: t }) => async (n) => {
|
|
237
|
+
let r = await V({
|
|
238
238
|
archive: e,
|
|
239
239
|
resourcePath: t
|
|
240
240
|
});
|
|
@@ -246,28 +246,28 @@ var s = (e) => e === "left" ? {
|
|
|
246
246
|
...r.params
|
|
247
247
|
}
|
|
248
248
|
};
|
|
249
|
-
},
|
|
249
|
+
}, U = "vnd.prose-reader.cbz.virtual-spine-id", W = (e) => {
|
|
250
250
|
try {
|
|
251
251
|
return decodeURIComponent(e);
|
|
252
252
|
} catch {
|
|
253
253
|
return e;
|
|
254
254
|
}
|
|
255
|
-
},
|
|
255
|
+
}, G = (e) => {
|
|
256
256
|
try {
|
|
257
257
|
return decodeURI(e);
|
|
258
258
|
} catch {
|
|
259
259
|
return e;
|
|
260
260
|
}
|
|
261
|
-
},
|
|
262
|
-
let t =
|
|
261
|
+
}, K = (e) => {
|
|
262
|
+
let t = I(e) ?? I(G(e));
|
|
263
263
|
if (t) return {
|
|
264
264
|
...t,
|
|
265
|
-
originalUri:
|
|
265
|
+
originalUri: W(t.originalUri)
|
|
266
266
|
};
|
|
267
|
-
}, J = (e, t) => {
|
|
267
|
+
}, q = (e) => K(e.item.href) !== void 0, J = (e, t) => {
|
|
268
268
|
let n = /* @__PURE__ */ new Set(), r = 0;
|
|
269
269
|
for (let i of e.spineItemsManager.items) {
|
|
270
|
-
let e =
|
|
270
|
+
let e = K(i.item.href);
|
|
271
271
|
if (!e) {
|
|
272
272
|
r++;
|
|
273
273
|
continue;
|
|
@@ -276,46 +276,47 @@ var s = (e) => e === "left" ? {
|
|
|
276
276
|
n.has(e.originalUri) || (n.add(e.originalUri), r++);
|
|
277
277
|
}
|
|
278
278
|
}, Y = (e, r, i) => {
|
|
279
|
-
let a =
|
|
279
|
+
let a = K(i.href);
|
|
280
280
|
if (!a) return;
|
|
281
281
|
let o = J(e, a.originalUri);
|
|
282
282
|
if (o !== void 0) return t(r, {
|
|
283
|
-
extensions: { [
|
|
283
|
+
extensions: { [U]: encodeURIComponent(i.id) },
|
|
284
284
|
spineId: n(a.originalUri),
|
|
285
285
|
spineIndex: o
|
|
286
286
|
});
|
|
287
|
-
},
|
|
288
|
-
let i = e(r)?.extensions?.[
|
|
287
|
+
}, ie = (n, r) => {
|
|
288
|
+
let i = e(r)?.extensions?.[U];
|
|
289
289
|
if (!i) return;
|
|
290
|
-
let a = n.spineItemsManager.get(
|
|
291
|
-
if (!(!a || !
|
|
292
|
-
extensions: { [
|
|
290
|
+
let a = n.spineItemsManager.get(W(i));
|
|
291
|
+
if (!(!a || !K(a.item.href))) return t(r, {
|
|
292
|
+
extensions: { [U]: void 0 },
|
|
293
293
|
spineId: a.item.id,
|
|
294
294
|
spineIndex: a.item.index
|
|
295
295
|
});
|
|
296
|
-
},
|
|
297
|
-
let n = e(t), r = n.hookManager.register("cfi.afterGenerate", ({ cfi: e, spineItem: t }) => Y(n, e, t)), i = n.hookManager.register("cfi.beforeResolve", ({ cfi: e }) =>
|
|
296
|
+
}, X = (e) => (t) => {
|
|
297
|
+
let n = e(t), r = n.hookManager.register("cfi.afterGenerate", ({ cfi: e, spineItem: t }) => Y(n, e, t)), i = n.hookManager.register("cfi.beforeResolve", ({ cfi: e }) => ie(n, e)), a = () => {
|
|
298
298
|
r(), i(), n.destroy();
|
|
299
299
|
};
|
|
300
300
|
return {
|
|
301
301
|
...n,
|
|
302
302
|
__PROSE_READER_ENHANCER_CBZ: !0,
|
|
303
|
+
cbz: { isPanoramaSpineItem: q },
|
|
303
304
|
destroy: a
|
|
304
305
|
};
|
|
305
|
-
}, Z = new Set(["application/vnd.comicbook+zip", "application/x-cbz"]),
|
|
306
|
+
}, Z = /* @__PURE__ */ new Set(["application/vnd.comicbook+zip", "application/x-cbz"]), ae = (e) => e.toLowerCase().endsWith(".cbz"), Q = (e) => {
|
|
306
307
|
let t = o(e.encodingFormat ?? "");
|
|
307
|
-
return !!(t && Z.has(t) ||
|
|
308
|
+
return !!(t && Z.has(t) || ae(e.filename ?? ""));
|
|
308
309
|
}, $ = ({ archive: e }) => (t) => t.readingDirection !== void 0 || !Q(e) ? t : {
|
|
309
310
|
...t,
|
|
310
311
|
readingDirection: "rtl"
|
|
311
|
-
},
|
|
312
|
+
}, oe = {
|
|
312
313
|
manifest: {
|
|
313
314
|
content: [$],
|
|
314
|
-
spine: [
|
|
315
|
+
spine: [N]
|
|
315
316
|
},
|
|
316
|
-
resource: [
|
|
317
|
+
resource: [H]
|
|
317
318
|
};
|
|
318
319
|
//#endregion
|
|
319
|
-
export { Z as CBZ_MIME_TYPES, _ as
|
|
320
|
+
export { Z as CBZ_MIME_TYPES, _ as PANORAMA_RESOURCE_PREFIX, v as PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE, T as buildVirtualPanoramaResourcePath, X as cbzEnhancer, B as createPanoramaSplitXhtml, g as detectPanoramaFromBasename, $ as detectReadingDirectionManifest, Q as isCbzArchive, M as isPanoramaSplitSupportedArchiveRecord, b as isPanoramaSplitSupportedImage, N as panoramaSplit, H as panoramaSplitResourceHook, I as parseVirtualPanoramaResourcePath, oe as streamerHooks };
|
|
320
321
|
|
|
321
322
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPageSpread = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPageSpreadFromBasename = (\n basename: string,\n): DetectedPageSpread | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPageSpreadFromBasename } from \"./detectPageSpreadFromBasename\"\n\nexport {\n type DetectedPageSpread,\n detectPageSpreadFromBasename,\n} from \"./detectPageSpreadFromBasename\"\n\nexport type PageSpreadCropSide = \"left\" | \"right\"\n\nexport type VirtualPageSpreadResource = {\n originalUri: string\n cropSide: PageSpreadCropSide\n}\n\nexport const PAGE_SPREAD_RESOURCE_PREFIX = `__prose-reader__/page-spread`\nexport const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPageSpreadSplitSupportedImage = (\n mimeType: string | undefined,\n) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPageSpreadResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PageSpreadCropSide\n}) => {\n return `${PAGE_SPREAD_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PageSpreadCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PageSpreadCropSide, PageSpreadCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PageSpreadCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPageSpreadResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPageSpreadSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPageSpreadSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPageSpreadSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const pageSpreadSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPageSpreadSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPageSpreadFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PAGE_SPREAD_RESOURCE_PREFIX,\n PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PageSpreadCropSide,\n type VirtualPageSpreadResource,\n} from \"./pageSpreadSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPageSpreadResourcePath = (\n resourcePath: string,\n): VirtualPageSpreadResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PAGE_SPREAD_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `page-spread` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PageSpreadCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Page spread XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPageSpreadSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PageSpreadCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Page spread image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePageSpreadSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPageSpreadResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual page spread resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPageSpreadSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const pageSpreadSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const pageSpreadResource = await generatePageSpreadSplitResource({\n archive,\n resourcePath,\n })\n\n if (pageSpreadResource === undefined) return resource\n\n return {\n ...resource,\n ...pageSpreadResource,\n params: {\n ...resource.params,\n ...pageSpreadResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPageSpreadResourcePath } from \"./pageSpreadSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPageSpreadFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPageSpreadResourcePath(href) ??\n parseVirtualPageSpreadResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPageSpreadFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPageSpreadFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPageSpreadFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPageSpreadResourcePath,\n detectPageSpreadFromBasename,\n isPageSpreadSplitSupportedArchiveRecord,\n isPageSpreadSplitSupportedImage,\n PAGE_SPREAD_RESOURCE_PREFIX,\n PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PageSpreadCropSide,\n pageSpreadSplit,\n type VirtualPageSpreadResource,\n} from \"./pageSpreadSplitManifest\"\n\nexport {\n createPageSpreadSplitXhtml,\n pageSpreadSplitResourceHook,\n parseVirtualPageSpreadResourcePath,\n} from \"./pageSpreadSplitResource\"\n"],"mappings":";;;;AAKA,IAAM,KACJ,MAEA,MAAS,SACL;CAAE,gBAAgB;CAAM,iBAAiB,KAAA;AAAU,IACnD;CAAE,gBAAgB,KAAA;CAAW,iBAAiB;AAAK,GAEnD,KAA4B,EAChC,UACA,0BAIoB;CACpB,IAAM,IAAa,IAAQ,KAAM;CAEjC,OAAO,MAAqB,QACxB,IACE,UACA,SACF,IACE,SACA;AACR,GAEM,KACJ,MACoB,MAAqB,QAAQ,SAAS,SAEtD,KAAgB,EACpB,cACA,oBAII;CACJ,IAAI,MAAc,KAAA,KAAa,MAAe,KAAA,GAAW,OAAO;CAEhE,IAAM,IAAY,EAAsB,CAAS,GAC3C,IAAa,EAAsB,CAAU;CAEnD,OACE,MAAc,KAAA,KACd,MAAe,KAAA,KACf,MAAc;AAElB,GAEM,KAAgC,EACpC,cACA,mBACA,qBACA,oBAOA,EAAsB,CAAS,MAC7B,EAAyB;CACvB,OAAO;CACP;AACF,CAAC,KACH,EAAsB,CAAU,MAC9B,EAAyB;CACvB,OAAO,IAAiB;CACxB;AACF,CAAC,GAEQ,KAAkC,EAC7C,qBACA,oBAII;CACJ,IAAM,IAAc,EAAW;CAiC/B,OA9BE,MAAgB,KAAA,KAChB,EAAsB,CAAW,MAAM,KAAA,KAKb,EAAW,WAAW,GAAW,MAAU;EACrE,IAAM,IAAa,EAAW,IAAQ;EAatC,OAVE,CAAC,EAAa;GACZ;GACA;EACF,CAAC,KAKC,MAAe,KAAA,IAAkB,KAE9B,CAAC,EAA6B;GACnC;GACA,gBAAgB;GAChB;GACA;EACF,CAAC;CACH,CAEI,KAAuB,IAAU,IAE9B,CACL;EACE,GAAG;EACH,GAAG,EACD,EAA8B,CAAgB,CAChD;CACF,GACA,GAAG,EAAW,MAAM,CAAC,CACvB;AACF,GC/GM,IAA2B,KAE3B,KAAuB,MAAsC;CACjE,IAAM,IAAQ,OAAO,SAAS,GAAO,EAAE;CAElC,WAAO,SAAS,CAAK,KACtB,MAAQ,KAAK,IAAQ,IAEzB,OAAO;AACT,GAEM,KAAgC,MAElC,0EAA0E,KACxE,CAGA,KAEG,8DAA8D,KACnE,CACF,GAGW,KACX,MACmC;CAEnC,IAAM,IAAQ,EADmB,EAAS,QAAQ,YAAY,EACnB,CAAwB;CAEnE,IAAI,CAAC,GAAO;CAEZ,IAAM,GAAG,GAAgB,KAAmB;CAE5C,IAAI,MAAmB,KAAA,KAAa,MAAoB,KAAA,GACtD;CAGF,IAAM,IAAkB,EAAoB,CAAc,GACpD,IAAmB,EAAoB,CAAe;CAExD,YAAoB,KAAA,KAAa,MAAqB,KAAA,MAItD,MAAqB,IAAkB,GAI3C,OAAO;EACL;EACA;CACF;AACF,GC7Ca,IAA8B,gCAC9B,IAAwC,yBAE/C,IAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;AACF,CAAC,GAEY,KACX,MAEI,MAAa,KAAA,IAAkB,KAE5B,EAAyB,IAAI,CAAQ,GAQxC,KAA4B,MAAgB,mBAAmB,CAAG,GAElE,KAA8B,EAClC,aAAU,IACV,sBAII;CACJ,IAAI,CAAC,KAAW,eAAe,KAAK,CAAY,GAC9C,OAAO,UAAU,CAAY;CAG/B,IAAM,IAAc,IAChB,GAAG,IAAU,EAAQ,SAAS,GAAG,IAAI,KAAK,QAC1C;CAEJ,OAAO,UAAU,GAAG,IAAc,GAAc;AAClD,GAEM,KAAmB,MAAiB,EAAK,YAAY,EAAE,SAAS,MAAM,GAEtE,KAAiB,MACrB,EAAQ,QAAQ,MACb,MACC,CAAC,EAAK,QACL,EAAgB,EAAK,QAAQ,KAAK,EAAgB,EAAK,GAAG,EAC/D,GAEW,KAAsC,EACjD,aACA,qBAKO,GAAG,EAA4B,GAAG,EAAyB,CAAW,EAAE,GAAG,EAAS,SAGvF,KACJ,MAEA,MAAS,SACL;CAAE,gBAAgB;CAAM,iBAAiB,KAAA;AAAU,IACnD;CAAE,gBAAgB,KAAA;CAAW,iBAAiB;AAAK,GAEnD,KACJ,MAEA,MAAqB,QAAQ,CAAC,SAAS,MAAM,IAAI,CAAC,QAAQ,OAAO,GAE7D,KAA0B,EAC9B,YACA,aACA,UACA,sBACA,gBACA,2BAQe;CACf,IAAM,IAAe,EAAmC;EACtD;EACA;CACF,CAAC;CAED,OAAO;EACL,GAAG;EACH,IAAI,EAAgB,GAAG,EAAkB,GAAG,GAAG,GAAO;EACtD,MAAM,EAA2B;GAAE;GAAS;EAAa,CAAC;EAC1D,WAAW;EACX;EACA,iBAAiB;EACjB,GAAG,EAAwB,CAAQ;CACrC;AACF,GAEM,KAA6B,EACjC,SACA,OACA,oBACoE;CACpE;CACA;CACA;AACF,IAEa,KAAmC,EAC9C,YACA,YACA,mBAK+B;CAC/B,IAAM,IAAiB,CAAC,EAAU,MAAM,GAAmB,EAAU,IAAI,CAAC,GACpE,IAAyB,IAAI,IACjC,EAAe,SAAS,MAAS,GAA0B,GAAM,CAAO,CAAC,CAC3E;CAEA,OAAO,EAAQ,QAAQ,MACpB,MAAS,CAAC,EAAK,OAAO,EAAuB,IAAI,EAAK,GAAG,CAC5D;AACF,GAEM,MAAsB,MAAiB;CAC3C,IAAI;EACF,OAAO,UAAU,CAAI;CACvB,QAAQ;EACN,OAAO;CACT;AACF,GAEM,MAAoB,MACxB,EAAQ,SAAS,GAAG,IAAI,IAAU,GAAG,EAAQ,IAEzC,MAA6B,GAAc,MAAoB;CACnE,IAAM,IAAa,CAAC,CAAI;CAMxB,IAJI,EAAK,WAAW,SAAS,KAC3B,EAAW,KAAK,EAAK,MAAM,CAAgB,CAAC,GAG1C,GAAS;EACX,IAAM,IAAoB,GAAiB,CAAO;EAElD,AAAI,EAAK,WAAW,CAAiB,KACnC,EAAW,KAAK,EAAK,MAAM,EAAkB,MAAM,CAAC;CAExD;CAEA,OAAO;AACT,GAEa,KACX,MAOA,EAAiB,GAAQ,kBAAkB,EAAE,KAC7C,EAAuB,GAAQ,YAAY,EAAE,GAEzC,KACJ,MAEA,EAAuB,EAAO,GAAG,KAAK,EAAuB,EAAO,QAAQ,GAEjE,KACX,MAEI,MAAW,KAAA,KAAa,EAAO,OAI/B,CAAC,EAFyB,EAAuC,CAEhC,CAAqB,IAAU,KAE7D,EAAgC,EAA2B,CAAM,CAAC,GAG9D,KACV,EAAE,YAAS,iBACZ,OAAO,MAA0C;CAC/C,IAAI,EAAc,CAAO,GAAG,OAAO;CAEnC,IAAM,IAAuC,CAAC,GACxC,IAAa,EAAS,WAAW,SAAS,MAAc;EAC5D,IAAM,IAAgB,EAAgC;GACpD;GACA;GACA;EACF,CAAC;EAED,IAAI,CAAC,EAAwC,CAAa,GACxD,OAAO,CAAC,CAAS;EAGnB,IAAM,IAAW,EAA6B,EAAc,QAAQ;EAEpE,IAAI,MAAa,KAAA,GAAW,OAAO,CAAC,CAAS;EAE7C,IAAM,CAAC,GAAe,KAAkB,EACtC,EAAS,gBACX,GACM,IACJ,EAAU,sBAAsB,KAAA,IAE5B,KAAA,IADA,EAAU,oBAAoB,GAE9B,IAAiB,EAAuB;GAC5C;GACA,UAAU;GACV,OAAO,EAAS;GAChB,mBAAmB;GACnB,aAAa,EAAc;GAC3B,mBAAmB;EACrB,CAAC,GACK,IAAkB,EAAuB;GAC7C;GACA,UAAU;GACV,OAAO,EAAS;GAChB,mBAAmB;GACnB,aAAa,EAAc;GAC3B,mBAAmB;EACrB,CAAC;EAOD,OALA,EAAqB,KACnB,EAA0B,CAAc,GACxC,EAA0B,CAAe,CAC3C,GAEO,CAAC,GAAgB,CAAe;CACzC,CAAC,GAEK,IAAoB,EAA+B;EACvD,kBAAkB,EAAS;EAC3B;CACF,CAAC;CAMD,OAJI,EAAqB,WAAW,KAAK,MAAsB,IACtD,IAGF;EACL,GAAG;EACH,YAAY,EAAkB,KAAK,GAAW,OAAW;GACvD,GAAG;GACH;EACF,EAAE;EACF,OAAO,CAAC,GAAG,EAAS,OAAO,GAAG,CAAoB;CACpD;AACF,GCtQI,oBAAuB,IAAI,QAG/B,GAEI,KAA4B,MAAwC;CACxE,IAAI;EACF,OAAO,mBAAmB,CAAO;CACnC,QAAQ;EACN;CACF;AACF,GAEa,KACX,MAC0C;CAC1C,IAAM,IAAc,EAAa,QAAQ,GAAG,EAA4B,EAAE;CAE1E,IAAI,IAAc,GAAG;CAGrB,IAAM,IADc,EAAa,MAAM,CACzB,EAAY,MAAM,GAAG,GAE7B,IAAqB,EAAM,IAC3B,IAAe,EAAM;CAE3B,IACE,EAAM,WAAW,KACjB,EAAM,OAAO,sBACb,EAAM,OAAO,iBACb,MAAuB,KAAA,KACvB,MAAiB,KAAA,GAEjB;CAGF,IAAM,IAAW,EAAa,MAAM,GAAG,EAAE;CAEzC,IAAI,MAAa,UAAU,MAAa,SAAS;CAEjD,IAAM,IAAc,EAAyB,CAAkB;CAE3D,UAAgB,KAAA,GAEpB,OAAO;EACL;EACA;CACF;AACF,GAEM,KAAmB,EACvB,aACA,gBACA,oBAKc;CACd,IAAM,IAAY,KAAK,MAAM,IAAa,CAAC,GACrC,IAAa,IAAa;CAEhC,OAAO,MAAa,SAChB;EAAE,GAAG;EAAG,OAAO;EAAW,QAAQ;CAAY,IAC9C;EAAE,GAAG;EAAW,OAAO;EAAY,QAAQ;CAAY;AAC7D,GAMM,KAA+B,MAC/B,eAAe,KAAK,CAAW,IAAU,IAEtC,YAAY,UAAU,CAAW,KAGpC,IAAsB,OAAO,MAA2C;CAC5E,IAAI,OAAO,qBAAsB,YAC/B,MAAU,MAAM,yDAAyD;CAG3E,IAAM,IAAS,MAAM,kBAAkB,CAAM;CAE7C,IAAI;EACF,OAAO;GACL,QAAQ,EAAO;GACf,OAAO,EAAO;EAChB;CACF,UAAU;EACR,EAAO,MAAM;CACf;AACF,GAEa,KAA8B,EACzC,aACA,oBACA,qBAKY;CACZ,IAAI,EAAgB,QAAQ,GAC1B,MAAU,MAAM,0CAA0C;CAG5D,IAAM,IAAO,EAAgB;EAC3B;EACA,aAAa,EAAgB;EAC7B,YAAY,EAAgB;CAC9B,CAAC;CAED,OAAO;;;2CAGkC,EAAK,MAAM,WAAW,EAAK,OAAO;;;;iBAI5D,EAAK,MAAM;kBACV,EAAK,OAAO;;;;;;;iBAOb,EAAgB,MAAM;kBACrB,EAAgB,OAAO;;iCAER,EAAK,EAAE;;;;;;;gBAOxB,EAAwB,EAA4B,CAAW,CAAC,EAAE;;;AAGlF,GAEM,IAAkC,OAAO,EAC7C,YACA,sBAIuC;CACvC,IAAM,IAAkB,EAAmC,CAAY;CAEvE,IAAI,MAAoB,KAAA,GAAW;CAEnC,IAAM,IAAO,EAAQ,QAAQ,MAC1B,MAAS,EAAK,QAAQ,EAAgB,eAAe,CAAC,EAAK,GAC9D;CAEA,IAAI,MAAS,KAAA,KAAa,EAAK,KAC7B,MAAU,MACR,6DAA6D,GAC/D;CAGF,IAAM,IAAe,EAAqB,IAAI,CAAO,qBAAK,IAAI,IAAI;CAElE,AAAK,EAAqB,IAAI,CAAO,KACnC,EAAqB,IAAI,GAAS,CAAY;CAGhD,IAAM,IACJ,EAAa,IAAI,EAAgB,WAAW,KAC3C,MAAM,EAAoB,MAAM,EAAK,KAAK,CAAC;CAU9C,OARA,EAAa,IAAI,EAAgB,aAAa,CAAe,GAQtD;EACL,MAPW,EAA2B;GACtC,UAAU,EAAgB;GAC1B;GACA,aAAa,EAAgB;EAC/B,CAGE;EACA,QAAQ,EACN,aAAa,EACf;CACF;AACF,GAEa,KACV,EAAE,YAAS,sBACZ,OAAO,MAAkD;CACvD,IAAM,IAAqB,MAAM,EAAgC;EAC/D;EACA;CACF,CAAC;CAID,OAFI,MAAuB,KAAA,IAAkB,IAEtC;EACL,GAAG;EACH,GAAG;EACH,QAAQ;GACN,GAAG,EAAS;GACZ,GAAG,EAAmB;EACxB;CACF;AACF,GC3NI,IAA6B,yCAQ7B,KAA0B,MAAkB;CAChD,IAAI;EACF,OAAO,mBAAmB,CAAK;CACjC,QAAQ;EACN,OAAO;CACT;AACF,GAEM,KAAiB,MAAkB;CACvC,IAAI;EACF,OAAO,UAAU,CAAK;CACxB,QAAQ;EACN,OAAO;CACT;AACF,GAEM,KAAkC,MAAiB;CACvD,IAAM,IACJ,EAAmC,CAAI,KACvC,EAAmC,EAAc,CAAI,CAAC;CAEnD,OAEL,OAAO;EACL,GAAG;EACH,aAAa,EAAuB,EAAgB,WAAW;CACjE;AACF,GAEM,KAAyB,GAAgB,MAAwB;CACrE,IAAM,oBAA0B,IAAI,IAAY,GAC5C,IAAqB;CAEzB,KAAK,IAAM,KAAa,EAAO,kBAAkB,OAAO;EACtD,IAAM,IAAkB,EAA+B,EAAU,KAAK,IAAI;EAE1E,IAAI,CAAC,GAAiB;GACpB;GACA;EACF;EAEA,IAAI,EAAgB,gBAAgB,GAClC,OAAO;EAGT,AAAK,EAAwB,IAAI,EAAgB,WAAW,MAC1D,EAAwB,IAAI,EAAgB,WAAW,GACvD;CAEJ;AAGF,GAEM,KACJ,GACA,GACA,MACG;CACH,IAAM,IAAkB,EAA+B,EAAU,IAAI;CAErE,IAAI,CAAC,GAAiB;CAEtB,IAAM,IAAqB,EACzB,GACA,EAAgB,WAClB;CAEI,UAAuB,KAAA,GAE3B,OAAO,EAA0B,GAAK;EACpC,YAAY,GACT,IAA6B,mBAAmB,EAAU,EAAE,EAC/D;EACA,SAAS,EAAgB,EAAgB,WAAW;EACpD,YAAY;CACd,CAAC;AACH,GAEM,KAAgC,GAAgB,MAAgB;CACpE,IAAM,IACJ,EAAuB,CAAG,GAAG,aAAa;CAE5C,IAAI,CAAC,GAAgB;CAErB,IAAM,IAAmB,EAAO,kBAAkB,IAChD,EAAuB,CAAc,CACvC;CAGE,OAAC,KACD,CAAC,EAA+B,EAAiB,KAAK,IAAI,IAK5D,OAAO,EAA0B,GAAK;EACpC,YAAY,GACT,IAA6B,KAAA,EAChC;EACA,SAAS,EAAiB,KAAK;EAC/B,YAAY,EAAiB,KAAK;CACpC,CAAC;AACH,GAEa,MAET,OAED,MAA4D;CAC3D,IAAM,IAAS,EAAK,CAAO,GAErB,IAAyB,EAAO,YAAY,SAChD,sBACC,EAAE,QAAK,mBACN,EAA8B,GAAQ,GAAK,CAAS,CACxD,GAEM,IAAwB,EAAO,YAAY,SAC/C,sBACC,EAAE,aAAU,EAA6B,GAAQ,CAAG,CACvD,GAEM,UAAgB;EAGpB,AAFA,EAAuB,GACvB,EAAsB,GACtB,EAAO,QAAQ;CACjB;CAEA,OAAO;EACL,GAAG;EACH,6BAA6B;EAC7B;CACF;AACF,GCpJW,IAAsC,IAAI,IAAI,CACzD,iCACA,mBACF,CAAC,GAEK,MAAmB,MAAiB,EAAK,YAAY,EAAE,SAAS,MAAM,GAE/D,KAAgB,MAAqB;CAChD,IAAM,IAAW,EAAiB,EAAQ,kBAAkB,EAAE;CAK9D,OAFA,GADI,KAAY,EAAe,IAAI,CAAQ,KACvC,GAAgB,EAAQ,YAAY,EAAE;AAG5C,GCda,KACV,EAAE,kBACF,MACK,EAAS,qBAAqB,KAAA,KAC9B,CAAC,EAAa,CAAO,IAAU,IAE5B;CACL,GAAG;CACH,kBAAkB;AACpB,GCJS,KAMT;CACF,UAAU;EACR,SAAS,CAAC,CAA8B;EACxC,OAAO,CAAC,CAAe;CACzB;CACA,UAAU,CAAC,CAA2B;AACxC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|
package/dist/index.umd.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@prose-reader/cfi"),require("@prose-reader/streamer"),require("@prose-reader/shared")):typeof define==`function`&&define.amd?define([`exports`,`@prose-reader/cfi`,`@prose-reader/streamer`,`@prose-reader/shared`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`prose-reader-cbz`]={},e._prose_reader_cfi,e._prose_reader_streamer,e._prose_reader_shared))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var i=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},a=({index:e,readingDirection:t})=>{let n=e%2==0;return t===`rtl`?n?`right`:`left`:n?`left`:`right`},o=e=>e===`rtl`?`left`:`right`,s=({firstItem:e,secondItem:t})=>{if(e===void 0||t===void 0)return!1;let n=(0,r.getItemSpreadPosition)(e),i=(0,r.getItemSpreadPosition)(t);return n!==void 0&&i!==void 0&&n!==i},c=({firstItem:e,firstItemIndex:t,readingDirection:n,secondItem:i})=>(0,r.getItemSpreadPosition)(e)===a({index:t,readingDirection:n})&&(0,r.getItemSpreadPosition)(i)===a({index:t+1,readingDirection:n}),l=({readingDirection:e,spineItems:t})=>{let n=t[0];return n===void 0||(0,r.getItemSpreadPosition)(n)!==void 0||t.findIndex((n,r)=>{let i=t[r+1];return!s({firstItem:n,secondItem:i})||i===void 0?!1:!c({firstItem:n,firstItemIndex:r,readingDirection:e,secondItem:i})})<=0?t:[{...n,...i(o(e))},...t.slice(1)]},u=2e3,d=e=>{let t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>u))return t},f=e=>/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e)||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e),p=e=>{let t=f(e.replace(/\.[^.]+$/,``));if(!t)return;let[,n,r]=t;if(n===void 0||r===void 0)return;let i=d(n),a=d(r);if(!(i===void 0||a===void 0)&&a===i+1)return{firstPageLabel:n,secondPageLabel:r}},m=`__prose-reader__/
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@prose-reader/cfi"),require("@prose-reader/streamer"),require("@prose-reader/shared")):typeof define==`function`&&define.amd?define([`exports`,`@prose-reader/cfi`,`@prose-reader/streamer`,`@prose-reader/shared`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`prose-reader-cbz`]={},e._prose_reader_cfi,e._prose_reader_streamer,e._prose_reader_shared))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var i=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},a=({index:e,readingDirection:t})=>{let n=e%2==0;return t===`rtl`?n?`right`:`left`:n?`left`:`right`},o=e=>e===`rtl`?`left`:`right`,s=({firstItem:e,secondItem:t})=>{if(e===void 0||t===void 0)return!1;let n=(0,r.getItemSpreadPosition)(e),i=(0,r.getItemSpreadPosition)(t);return n!==void 0&&i!==void 0&&n!==i},c=({firstItem:e,firstItemIndex:t,readingDirection:n,secondItem:i})=>(0,r.getItemSpreadPosition)(e)===a({index:t,readingDirection:n})&&(0,r.getItemSpreadPosition)(i)===a({index:t+1,readingDirection:n}),l=({readingDirection:e,spineItems:t})=>{let n=t[0];return n===void 0||(0,r.getItemSpreadPosition)(n)!==void 0||t.findIndex((n,r)=>{let i=t[r+1];return!s({firstItem:n,secondItem:i})||i===void 0?!1:!c({firstItem:n,firstItemIndex:r,readingDirection:e,secondItem:i})})<=0?t:[{...n,...i(o(e))},...t.slice(1)]},u=2e3,d=e=>{let t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>u))return t},f=e=>/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e)||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e),p=e=>{let t=f(e.replace(/\.[^.]+$/,``));if(!t)return;let[,n,r]=t;if(n===void 0||r===void 0)return;let i=d(n),a=d(r);if(!(i===void 0||a===void 0)&&a===i+1)return{firstPageLabel:n,secondPageLabel:r}},m=`__prose-reader__/panorama`,h=`application/xhtml+xml`,g=new Set([`image/jpg`,`image/jpeg`,`image/png`,`image/webp`]),_=e=>e!==void 0&&g.has(e),v=e=>encodeURIComponent(e),y=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return encodeURI(`${n}${t}`)},b=e=>e.toLowerCase().endsWith(`.opf`),x=e=>e.records.some(e=>!e.dir&&(b(e.basename)||b(e.uri))),S=({cropSide:e,originalUri:t})=>`${m}/${v(t)}/${e}.xhtml`,C=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},w=e=>e===`rtl`?[`right`,`left`]:[`left`,`right`],T=({baseUrl:e,cropSide:t,label:r,originalSpineItem:i,originalUri:a,progressionWeight:o})=>{let s=S({cropSide:t,originalUri:a});return{...i,id:(0,n.createXmlSafeId)(`${i.id}.${r}`),href:y({baseUrl:e,resourcePath:s}),mediaType:h,progressionWeight:o,renditionLayout:`pre-paginated`,...C(t)}},E=({href:e,id:t,mediaType:n})=>({href:e,id:t,mediaType:n}),D=({archive:e,baseUrl:t,spineItem:n})=>{let r=[n.href,O(n.href)],i=new Set(r.flatMap(e=>A(e,t)));return e.records.find(e=>!e.dir&&i.has(e.uri))},O=e=>{try{return decodeURI(e)}catch{return e}},k=e=>e.endsWith(`/`)?e:`${e}/`,A=(e,t)=>{let n=[e];if(e.startsWith(`file://`)&&n.push(e.slice(7)),t){let r=k(t);e.startsWith(r)&&n.push(e.slice(r.length))}return n},ee=e=>(0,r.parseContentType)(e?.encodingFormat??``)||(0,r.detectMimeTypeFromName)(e?.basename??``),te=e=>(0,r.detectMimeTypeFromName)(e.uri)||(0,r.detectMimeTypeFromName)(e.basename),j=e=>e===void 0||e.dir||!_(te(e))?!1:_(ee(e)),M=({archive:e,baseUrl:t})=>async n=>{if(x(e))return n;let r=[],i=n.spineItems.flatMap(i=>{let a=D({archive:e,baseUrl:t,spineItem:i});if(!j(a))return[i];let o=p(a.basename);if(o===void 0)return[i];let[s,c]=w(n.readingDirection),l=i.progressionWeight===void 0?void 0:i.progressionWeight/2,u=T({baseUrl:t,cropSide:s,label:o.firstPageLabel,originalSpineItem:i,originalUri:a.uri,progressionWeight:l}),d=T({baseUrl:t,cropSide:c,label:o.secondPageLabel,originalSpineItem:i,originalUri:a.uri,progressionWeight:l});return r.push(E(u),E(d)),[u,d]}),a=l({readingDirection:n.readingDirection,spineItems:i});return r.length===0&&a===i?n:{...n,spineItems:a.map((e,t)=>({...e,index:t})),items:[...n.items,...r]}},N=new WeakMap,P=e=>{try{return decodeURIComponent(e)}catch{return}},F=e=>{let t=e.indexOf(`${m}/`);if(t<0)return;let n=e.slice(t).split(`/`),r=n[2],i=n[3];if(n.length!==4||n[0]!==`__prose-reader__`||n[1]!==`panorama`||r===void 0||i===void 0)return;let a=i.split(`.`)[0];if(a!==`left`&&a!==`right`)return;let o=P(r);if(o!==void 0)return{originalUri:o,cropSide:a}},I=({cropSide:e,imageHeight:t,imageWidth:n})=>{let r=Math.floor(n/2),i=n-r;return e===`left`?{x:0,width:r,height:t}:{x:r,width:i,height:t}},L=e=>/^https?:\/\//.test(e)?e:`../../../${encodeURI(e)}`,R=async e=>{if(typeof createImageBitmap!=`function`)throw Error(`Panorama XHTML generation requires createImageBitmap`);let t=await createImageBitmap(e);try{return{height:t.height,width:t.width}}finally{t.close()}},z=({cropSide:e,imageDimensions:t,originalUri:n})=>{if(t.width<2)throw Error(`Panorama image is too narrow to split`);let i=I({cropSide:e,imageHeight:t.height,imageWidth:t.width});return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
3
|
<head>
|
|
4
4
|
<meta name="viewport" content="width=${i.width}, height=${i.height}" />
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
<body>
|
|
26
26
|
<img src="${(0,r.escapeXmlAttributeValue)(L(n))}" alt="" />
|
|
27
27
|
</body>
|
|
28
|
-
</html>`},B=async({archive:e,resourcePath:t})=>{let n=F(t);if(n===void 0)return;let r=e.records.find(e=>e.uri===n.originalUri&&!e.dir);if(r===void 0||r.dir)throw Error(`no source file found for virtual
|
|
28
|
+
</html>`},B=async({archive:e,resourcePath:t})=>{let n=F(t);if(n===void 0)return;let r=e.records.find(e=>e.uri===n.originalUri&&!e.dir);if(r===void 0||r.dir)throw Error(`no source file found for virtual panorama resourcePath:${t}`);let i=N.get(e)??new Map;N.has(e)||N.set(e,i);let a=i.get(n.originalUri)??await R(await r.blob());return i.set(n.originalUri,a),{body:z({cropSide:n.cropSide,imageDimensions:a,originalUri:n.originalUri}),params:{contentType:h}}},V=({archive:e,resourcePath:t})=>async n=>{let r=await B({archive:e,resourcePath:t});return r===void 0?n:{...n,...r,params:{...n.params,...r.params}}},H=`vnd.prose-reader.cbz.virtual-spine-id`,U=e=>{try{return decodeURIComponent(e)}catch{return e}},W=e=>{try{return decodeURI(e)}catch{return e}},G=e=>{let t=F(e)??F(W(e));if(t)return{...t,originalUri:U(t.originalUri)}},K=e=>G(e.item.href)!==void 0,q=(e,t)=>{let n=new Set,r=0;for(let i of e.spineItemsManager.items){let e=G(i.item.href);if(!e){r++;continue}if(e.originalUri===t)return r;n.has(e.originalUri)||(n.add(e.originalUri),r++)}},J=(e,r,i)=>{let a=G(i.href);if(!a)return;let o=q(e,a.originalUri);if(o!==void 0)return(0,t.updateEpubCfiSpineItemref)(r,{extensions:{[H]:encodeURIComponent(i.id)},spineId:(0,n.createXmlSafeId)(a.originalUri),spineIndex:o})},Y=(e,n)=>{let r=(0,t.getEpubCfiSpineItemref)(n)?.extensions?.[H];if(!r)return;let i=e.spineItemsManager.get(U(r));if(!(!i||!G(i.item.href)))return(0,t.updateEpubCfiSpineItemref)(n,{extensions:{[H]:void 0},spineId:i.item.id,spineIndex:i.item.index})},X=e=>t=>{let n=e(t),r=n.hookManager.register(`cfi.afterGenerate`,({cfi:e,spineItem:t})=>J(n,e,t)),i=n.hookManager.register(`cfi.beforeResolve`,({cfi:e})=>Y(n,e)),a=()=>{r(),i(),n.destroy()};return{...n,__PROSE_READER_ENHANCER_CBZ:!0,cbz:{isPanoramaSpineItem:K},destroy:a}},Z=new Set([`application/vnd.comicbook+zip`,`application/x-cbz`]),ne=e=>e.toLowerCase().endsWith(`.cbz`),Q=e=>{let t=(0,r.parseContentType)(e.encodingFormat??``);return!!(t&&Z.has(t)||ne(e.filename??``))},$=({archive:e})=>t=>t.readingDirection!==void 0||!Q(e)?t:{...t,readingDirection:`rtl`},re={manifest:{content:[$],spine:[M]},resource:[V]};e.CBZ_MIME_TYPES=Z,e.PANORAMA_RESOURCE_PREFIX=m,e.PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE=h,e.buildVirtualPanoramaResourcePath=S,e.cbzEnhancer=X,e.createPanoramaSplitXhtml=z,e.detectPanoramaFromBasename=p,e.detectReadingDirectionManifest=$,e.isCbzArchive=Q,e.isPanoramaSplitSupportedArchiveRecord=j,e.isPanoramaSplitSupportedImage=_,e.panoramaSplit=M,e.panoramaSplitResourceHook=V,e.parseVirtualPanoramaResourcePath=F,e.streamerHooks=re});
|
|
29
29
|
//# sourceMappingURL=index.umd.cjs.map
|
package/dist/index.umd.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.cjs","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPageSpread = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPageSpreadFromBasename = (\n basename: string,\n): DetectedPageSpread | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPageSpreadFromBasename } from \"./detectPageSpreadFromBasename\"\n\nexport {\n type DetectedPageSpread,\n detectPageSpreadFromBasename,\n} from \"./detectPageSpreadFromBasename\"\n\nexport type PageSpreadCropSide = \"left\" | \"right\"\n\nexport type VirtualPageSpreadResource = {\n originalUri: string\n cropSide: PageSpreadCropSide\n}\n\nexport const PAGE_SPREAD_RESOURCE_PREFIX = `__prose-reader__/page-spread`\nexport const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPageSpreadSplitSupportedImage = (\n mimeType: string | undefined,\n) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPageSpreadResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PageSpreadCropSide\n}) => {\n return `${PAGE_SPREAD_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PageSpreadCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PageSpreadCropSide, PageSpreadCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PageSpreadCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPageSpreadResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPageSpreadSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPageSpreadSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPageSpreadSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const pageSpreadSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPageSpreadSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPageSpreadFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PAGE_SPREAD_RESOURCE_PREFIX,\n PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PageSpreadCropSide,\n type VirtualPageSpreadResource,\n} from \"./pageSpreadSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPageSpreadResourcePath = (\n resourcePath: string,\n): VirtualPageSpreadResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PAGE_SPREAD_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `page-spread` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PageSpreadCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Page spread XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPageSpreadSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PageSpreadCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Page spread image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePageSpreadSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPageSpreadResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual page spread resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPageSpreadSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const pageSpreadSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const pageSpreadResource = await generatePageSpreadSplitResource({\n archive,\n resourcePath,\n })\n\n if (pageSpreadResource === undefined) return resource\n\n return {\n ...resource,\n ...pageSpreadResource,\n params: {\n ...resource.params,\n ...pageSpreadResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPageSpreadResourcePath } from \"./pageSpreadSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPageSpreadFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPageSpreadResourcePath(href) ??\n parseVirtualPageSpreadResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPageSpreadFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPageSpreadFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPageSpreadFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPageSpreadResourcePath,\n detectPageSpreadFromBasename,\n isPageSpreadSplitSupportedArchiveRecord,\n isPageSpreadSplitSupportedImage,\n PAGE_SPREAD_RESOURCE_PREFIX,\n PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PageSpreadCropSide,\n pageSpreadSplit,\n type VirtualPageSpreadResource,\n} from \"./pageSpreadSplitManifest\"\n\nexport {\n createPageSpreadSplitXhtml,\n pageSpreadSplitResourceHook,\n parseVirtualPageSpreadResourcePath,\n} from \"./pageSpreadSplitResource\"\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.cjs","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Manifest } from '@prose-reader/shared';
|
|
2
|
+
import { Archive } from '@prose-reader/streamer';
|
|
3
|
+
export { type DetectedPanorama, detectPanoramaFromBasename, } from './detectPanoramaFromBasename';
|
|
4
|
+
export type PanoramaCropSide = "left" | "right";
|
|
5
|
+
export type VirtualPanoramaResource = {
|
|
6
|
+
originalUri: string;
|
|
7
|
+
cropSide: PanoramaCropSide;
|
|
8
|
+
};
|
|
9
|
+
export declare const PANORAMA_RESOURCE_PREFIX = "__prose-reader__/panorama";
|
|
10
|
+
export declare const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = "application/xhtml+xml";
|
|
11
|
+
export declare const isPanoramaSplitSupportedImage: (mimeType: string | undefined) => boolean;
|
|
12
|
+
type ArchiveRecord = Archive["records"][number];
|
|
13
|
+
type ArchiveFileRecord = Extract<ArchiveRecord, {
|
|
14
|
+
dir: false;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const buildVirtualPanoramaResourcePath: ({ cropSide, originalUri, }: {
|
|
17
|
+
originalUri: string;
|
|
18
|
+
cropSide: PanoramaCropSide;
|
|
19
|
+
}) => string;
|
|
20
|
+
export declare const getArchiveRecordForManifestItem: ({ archive, baseUrl, spineItem, }: {
|
|
21
|
+
archive: Archive;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
spineItem: Manifest["spineItems"][number];
|
|
24
|
+
}) => ArchiveRecord | undefined;
|
|
25
|
+
export declare const mediaTypeFromArchiveRecord: (record: {
|
|
26
|
+
basename: string;
|
|
27
|
+
encodingFormat?: string;
|
|
28
|
+
} | undefined) => string | undefined;
|
|
29
|
+
export declare const isPanoramaSplitSupportedArchiveRecord: (record: ArchiveRecord | undefined) => record is ArchiveFileRecord;
|
|
30
|
+
export declare const panoramaSplit: ({ archive, baseUrl }: {
|
|
31
|
+
archive: Archive;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
}) => (manifest: Manifest) => Promise<Manifest>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Archive, HookResource } from '@prose-reader/streamer';
|
|
2
|
+
import { PanoramaCropSide, VirtualPanoramaResource } from './panoramaSplitManifest';
|
|
3
|
+
type ImageDimensions = {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const parseVirtualPanoramaResourcePath: (resourcePath: string) => VirtualPanoramaResource | undefined;
|
|
8
|
+
export declare const createPanoramaSplitXhtml: ({ cropSide, imageDimensions, originalUri, }: {
|
|
9
|
+
cropSide: PanoramaCropSide;
|
|
10
|
+
imageDimensions: ImageDimensions;
|
|
11
|
+
originalUri: string;
|
|
12
|
+
}) => string;
|
|
13
|
+
export declare const panoramaSplitResourceHook: ({ archive, resourcePath }: {
|
|
14
|
+
archive: Archive;
|
|
15
|
+
resourcePath: string;
|
|
16
|
+
}) => (resource: HookResource) => Promise<HookResource>;
|
|
17
|
+
export {};
|
package/dist/streamer.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export declare const streamerHooks: {
|
|
|
8
8
|
};
|
|
9
9
|
export { detectReadingDirectionManifest } from './detectReadingDirectionManifest';
|
|
10
10
|
export { CBZ_MIME_TYPES, isCbzArchive } from './isCbzArchive';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
11
|
+
export { buildVirtualPanoramaResourcePath, detectPanoramaFromBasename, isPanoramaSplitSupportedArchiveRecord, isPanoramaSplitSupportedImage, PANORAMA_RESOURCE_PREFIX, PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE, type PanoramaCropSide, panoramaSplit, type VirtualPanoramaResource, } from './panoramaSplitManifest';
|
|
12
|
+
export { createPanoramaSplitXhtml, panoramaSplitResourceHook, parseVirtualPanoramaResourcePath, } from './panoramaSplitResource';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prose-reader/cbz",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.319.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"/dist"
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"@prose-reader/shared": "^1.296.0",
|
|
37
37
|
"@prose-reader/streamer": "^1.296.0"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "348f1339ba2628c81b516bbbdce9dc0cf253a6a7"
|
|
40
40
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Manifest } from '@prose-reader/shared';
|
|
2
|
-
import { Archive } from '@prose-reader/streamer';
|
|
3
|
-
export { type DetectedPageSpread, detectPageSpreadFromBasename, } from './detectPageSpreadFromBasename';
|
|
4
|
-
export type PageSpreadCropSide = "left" | "right";
|
|
5
|
-
export type VirtualPageSpreadResource = {
|
|
6
|
-
originalUri: string;
|
|
7
|
-
cropSide: PageSpreadCropSide;
|
|
8
|
-
};
|
|
9
|
-
export declare const PAGE_SPREAD_RESOURCE_PREFIX = "__prose-reader__/page-spread";
|
|
10
|
-
export declare const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = "application/xhtml+xml";
|
|
11
|
-
export declare const isPageSpreadSplitSupportedImage: (mimeType: string | undefined) => boolean;
|
|
12
|
-
type ArchiveRecord = Archive["records"][number];
|
|
13
|
-
type ArchiveFileRecord = Extract<ArchiveRecord, {
|
|
14
|
-
dir: false;
|
|
15
|
-
}>;
|
|
16
|
-
export declare const buildVirtualPageSpreadResourcePath: ({ cropSide, originalUri, }: {
|
|
17
|
-
originalUri: string;
|
|
18
|
-
cropSide: PageSpreadCropSide;
|
|
19
|
-
}) => string;
|
|
20
|
-
export declare const getArchiveRecordForManifestItem: ({ archive, baseUrl, spineItem, }: {
|
|
21
|
-
archive: Archive;
|
|
22
|
-
baseUrl: string;
|
|
23
|
-
spineItem: Manifest["spineItems"][number];
|
|
24
|
-
}) => ArchiveRecord | undefined;
|
|
25
|
-
export declare const mediaTypeFromArchiveRecord: (record: {
|
|
26
|
-
basename: string;
|
|
27
|
-
encodingFormat?: string;
|
|
28
|
-
} | undefined) => string | undefined;
|
|
29
|
-
export declare const isPageSpreadSplitSupportedArchiveRecord: (record: ArchiveRecord | undefined) => record is ArchiveFileRecord;
|
|
30
|
-
export declare const pageSpreadSplit: ({ archive, baseUrl }: {
|
|
31
|
-
archive: Archive;
|
|
32
|
-
baseUrl: string;
|
|
33
|
-
}) => (manifest: Manifest) => Promise<Manifest>;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Archive, HookResource } from '@prose-reader/streamer';
|
|
2
|
-
import { PageSpreadCropSide, VirtualPageSpreadResource } from './pageSpreadSplitManifest';
|
|
3
|
-
type ImageDimensions = {
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
};
|
|
7
|
-
export declare const parseVirtualPageSpreadResourcePath: (resourcePath: string) => VirtualPageSpreadResource | undefined;
|
|
8
|
-
export declare const createPageSpreadSplitXhtml: ({ cropSide, imageDimensions, originalUri, }: {
|
|
9
|
-
cropSide: PageSpreadCropSide;
|
|
10
|
-
imageDimensions: ImageDimensions;
|
|
11
|
-
originalUri: string;
|
|
12
|
-
}) => string;
|
|
13
|
-
export declare const pageSpreadSplitResourceHook: ({ archive, resourcePath }: {
|
|
14
|
-
archive: Archive;
|
|
15
|
-
resourcePath: string;
|
|
16
|
-
}) => (resource: HookResource) => Promise<HookResource>;
|
|
17
|
-
export {};
|
/package/dist/{detectPageSpreadFromBasename.test.d.ts → detectPanoramaFromBasename.test.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|