@prose-reader/cbz 1.300.0 → 1.301.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/index.js CHANGED
@@ -1,18 +1,19 @@
1
- import { updateEpubCfiSpineItemref as w, getEpubCfiSpineItemref as b } from "@prose-reader/cfi";
2
- import { detectMimeTypeFromName as c, parseContentType as U, escapeXmlAttributeValue as $ } from "@prose-reader/shared";
3
- const M = 2e3, m = (e) => {
1
+ import { updateEpubCfiSpineItemref as w, getEpubCfiSpineItemref as U } from "@prose-reader/cfi";
2
+ import { createXmlSafeId as P } from "@prose-reader/streamer";
3
+ import { detectMimeTypeFromName as c, parseContentType as $, escapeXmlAttributeValue as M } from "@prose-reader/shared";
4
+ const C = 2e3, m = (e) => {
4
5
  const t = Number.parseInt(e, 10);
5
- if (Number.isFinite(t) && !(t < 0 || t > M))
6
+ if (Number.isFinite(t) && !(t < 0 || t > C))
6
7
  return t;
7
- }, C = (e) => {
8
- const t = /(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(
8
+ }, A = (e) => {
9
+ const t = /(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(
9
10
  e
10
11
  );
11
- return t || /(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_]\s*(0\d{1,4})(?=$|[^\d])/i.exec(
12
+ return t || /(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(
12
13
  e
13
14
  );
14
- }, A = (e) => {
15
- const t = e.replace(/\.[^.]+$/, ""), r = C(t);
15
+ }, T = (e) => {
16
+ const t = e.replace(/\.[^.]+$/, ""), r = A(t);
16
17
  if (!r) return;
17
18
  const [, i, o] = r;
18
19
  if (i === void 0 || o === void 0)
@@ -23,12 +24,12 @@ const M = 2e3, m = (e) => {
23
24
  firstPageLabel: i,
24
25
  secondPageLabel: o
25
26
  };
26
- }, P = "__prose-reader__/page-spread", x = "application/xhtml+xml", T = /* @__PURE__ */ new Set([
27
+ }, x = "__prose-reader__/page-spread", E = "application/xhtml+xml", F = /* @__PURE__ */ new Set([
27
28
  "image/jpg",
28
29
  "image/jpeg",
29
30
  "image/png",
30
31
  "image/webp"
31
- ]), S = (e) => e === void 0 ? !1 : T.has(e), F = (e) => encodeURIComponent(e), O = ({
32
+ ]), S = (e) => e === void 0 ? !1 : F.has(e), O = (e) => encodeURIComponent(e), L = ({
32
33
  baseUrl: e = "",
33
34
  resourcePath: t
34
35
  }) => {
@@ -36,12 +37,12 @@ const M = 2e3, m = (e) => {
36
37
  return encodeURI(t);
37
38
  const r = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
38
39
  return encodeURI(`${r}${t}`);
39
- }, v = (e) => e.toLowerCase().endsWith(".opf"), L = (e) => e.records.some(
40
+ }, v = (e) => e.toLowerCase().endsWith(".opf"), N = (e) => e.records.some(
40
41
  (t) => !t.dir && (v(t.basename) || v(t.uri))
41
- ), N = ({
42
+ ), W = ({
42
43
  cropSide: e,
43
44
  originalUri: t
44
- }) => `${P}/${F(t)}/${e}.xhtml`, W = (e) => e === "left" ? { pageSpreadLeft: !0, pageSpreadRight: void 0 } : { pageSpreadLeft: void 0, pageSpreadRight: !0 }, B = (e) => e === "rtl" ? ["right", "left"] : ["left", "right"], R = ({
45
+ }) => `${x}/${O(t)}/${e}.xhtml`, B = (e) => e === "left" ? { pageSpreadLeft: !0, pageSpreadRight: void 0 } : { pageSpreadLeft: void 0, pageSpreadRight: !0 }, k = (e) => e === "rtl" ? ["right", "left"] : ["left", "right"], R = ({
45
46
  baseUrl: e,
46
47
  cropSide: t,
47
48
  label: r,
@@ -49,18 +50,18 @@ const M = 2e3, m = (e) => {
49
50
  originalUri: o,
50
51
  progressionWeight: n
51
52
  }) => {
52
- const s = N({
53
+ const s = W({
53
54
  cropSide: t,
54
55
  originalUri: o
55
56
  });
56
57
  return {
57
58
  ...i,
58
- id: `${i.id}.${r}`,
59
- href: O({ baseUrl: e, resourcePath: s }),
60
- mediaType: x,
59
+ id: P(`${i.id}.${r}`),
60
+ href: L({ baseUrl: e, resourcePath: s }),
61
+ mediaType: E,
61
62
  progressionWeight: n,
62
63
  renditionLayout: "pre-paginated",
63
- ...W(t)
64
+ ...B(t)
64
65
  };
65
66
  }, I = ({
66
67
  href: e,
@@ -70,58 +71,58 @@ const M = 2e3, m = (e) => {
70
71
  href: e,
71
72
  id: t,
72
73
  mediaType: r
73
- }), k = ({
74
+ }), H = ({
74
75
  archive: e,
75
76
  baseUrl: t,
76
77
  spineItem: r
77
78
  }) => {
78
- const i = [r.href, H(r.href)], o = new Set(
79
- i.flatMap((n) => D(n, t))
79
+ const i = [r.href, V(r.href)], o = new Set(
80
+ i.flatMap((n) => X(n, t))
80
81
  );
81
82
  return e.records.find(
82
83
  (n) => !n.dir && o.has(n.uri)
83
84
  );
84
- }, H = (e) => {
85
+ }, V = (e) => {
85
86
  try {
86
87
  return decodeURI(e);
87
88
  } catch {
88
89
  return e;
89
90
  }
90
- }, V = (e) => e.endsWith("/") ? e : `${e}/`, D = (e, t) => {
91
+ }, D = (e) => e.endsWith("/") ? e : `${e}/`, X = (e, t) => {
91
92
  const r = [e];
92
93
  if (e.startsWith("file://") && r.push(e.slice(7)), t) {
93
- const i = V(t);
94
+ const i = D(t);
94
95
  e.startsWith(i) && r.push(e.slice(i.length));
95
96
  }
96
97
  return r;
97
- }, X = (e) => U(e?.encodingFormat ?? "") || c(e?.basename ?? ""), G = (e) => c(e.uri) || c(e.basename), z = (e) => {
98
+ }, G = (e) => $(e?.encodingFormat ?? "") || c(e?.basename ?? ""), z = (e) => c(e.uri) || c(e.basename), j = (e) => {
98
99
  if (e === void 0 || e.dir) return !1;
99
- const t = G(e);
100
- return S(t) ? S(X(e)) : !1;
101
- }, j = ({ archive: e, baseUrl: t }) => async (r) => {
102
- if (L(e)) return r;
100
+ const t = z(e);
101
+ return S(t) ? S(G(e)) : !1;
102
+ }, q = ({ archive: e, baseUrl: t }) => async (r) => {
103
+ if (N(e)) return r;
103
104
  const i = [], o = r.spineItems.flatMap((n) => {
104
- const s = k({
105
+ const s = H({
105
106
  archive: e,
106
107
  baseUrl: t,
107
108
  spineItem: n
108
109
  });
109
- if (!z(s))
110
+ if (!j(s))
110
111
  return [n];
111
- const a = A(s.basename);
112
+ const a = T(s.basename);
112
113
  if (a === void 0) return [n];
113
- const [_, y] = B(
114
+ const [y, b] = k(
114
115
  r.readingDirection
115
116
  ), l = n.progressionWeight !== void 0 ? n.progressionWeight / 2 : void 0, h = R({
116
117
  baseUrl: t,
117
- cropSide: _,
118
+ cropSide: y,
118
119
  label: a.firstPageLabel,
119
120
  originalSpineItem: n,
120
121
  originalUri: s.uri,
121
122
  progressionWeight: l
122
123
  }), f = R({
123
124
  baseUrl: t,
124
- cropSide: y,
125
+ cropSide: b,
125
126
  label: a.secondPageLabel,
126
127
  originalSpineItem: n,
127
128
  originalUri: s.uri,
@@ -140,34 +141,34 @@ const M = 2e3, m = (e) => {
140
141
  })),
141
142
  items: [...r.items, ...i]
142
143
  };
143
- }, d = /* @__PURE__ */ new WeakMap(), q = (e) => {
144
+ }, d = /* @__PURE__ */ new WeakMap(), Y = (e) => {
144
145
  try {
145
146
  return decodeURIComponent(e);
146
147
  } catch {
147
148
  return;
148
149
  }
149
150
  }, p = (e) => {
150
- const t = e.indexOf(`${P}/`);
151
+ const t = e.indexOf(`${x}/`);
151
152
  if (t < 0) return;
152
153
  const i = e.slice(t).split("/"), o = i[2], n = i[3];
153
154
  if (i.length !== 4 || i[0] !== "__prose-reader__" || i[1] !== "page-spread" || o === void 0 || n === void 0)
154
155
  return;
155
156
  const s = n.split(".")[0];
156
157
  if (s !== "left" && s !== "right") return;
157
- const a = q(o);
158
+ const a = Y(o);
158
159
  if (a !== void 0)
159
160
  return {
160
161
  originalUri: a,
161
162
  cropSide: s
162
163
  };
163
- }, Y = ({
164
+ }, Z = ({
164
165
  cropSide: e,
165
166
  imageHeight: t,
166
167
  imageWidth: r
167
168
  }) => {
168
169
  const i = Math.floor(r / 2), o = r - i;
169
170
  return e === "left" ? { x: 0, width: i, height: t } : { x: i, width: o, height: t };
170
- }, Z = (e) => /^https?:\/\//.test(e) ? e : `../../../${encodeURI(e)}`, J = async (e) => {
171
+ }, J = (e) => /^https?:\/\//.test(e) ? e : `../../../${encodeURI(e)}`, K = async (e) => {
171
172
  if (typeof createImageBitmap != "function")
172
173
  throw new Error("Page spread XHTML generation requires createImageBitmap");
173
174
  const t = await createImageBitmap(e);
@@ -179,14 +180,14 @@ const M = 2e3, m = (e) => {
179
180
  } finally {
180
181
  t.close();
181
182
  }
182
- }, K = ({
183
+ }, Q = ({
183
184
  cropSide: e,
184
185
  imageDimensions: t,
185
186
  originalUri: r
186
187
  }) => {
187
188
  if (t.width < 2)
188
189
  throw new Error("Page spread image is too narrow to split");
189
- const i = Y({
190
+ const i = Z({
190
191
  cropSide: e,
191
192
  imageHeight: t.height,
192
193
  imageWidth: t.width
@@ -216,10 +217,10 @@ const M = 2e3, m = (e) => {
216
217
  </style>
217
218
  </head>
218
219
  <body>
219
- <img src="${$(Z(r))}" alt="" />
220
+ <img src="${M(J(r))}" alt="" />
220
221
  </body>
221
222
  </html>`;
222
- }, Q = async ({
223
+ }, ee = async ({
223
224
  archive: e,
224
225
  resourcePath: t
225
226
  }) => {
@@ -234,19 +235,19 @@ const M = 2e3, m = (e) => {
234
235
  );
235
236
  const o = d.get(e) ?? /* @__PURE__ */ new Map();
236
237
  d.has(e) || d.set(e, o);
237
- const n = o.get(r.originalUri) ?? await J(await i.blob());
238
+ const n = o.get(r.originalUri) ?? await K(await i.blob());
238
239
  return o.set(r.originalUri, n), {
239
- body: K({
240
+ body: Q({
240
241
  cropSide: r.cropSide,
241
242
  imageDimensions: n,
242
243
  originalUri: r.originalUri
243
244
  }),
244
245
  params: {
245
- contentType: x
246
+ contentType: E
246
247
  }
247
248
  };
248
- }, ee = ({ archive: e, resourcePath: t }) => async (r) => {
249
- const i = await Q({
249
+ }, te = ({ archive: e, resourcePath: t }) => async (r) => {
250
+ const i = await ee({
250
251
  archive: e,
251
252
  resourcePath: t
252
253
  });
@@ -258,26 +259,26 @@ const M = 2e3, m = (e) => {
258
259
  ...i.params
259
260
  }
260
261
  };
261
- }, g = "vnd.prose-reader.cbz.virtual-spine-id", E = (e) => {
262
+ }, g = "vnd.prose-reader.cbz.virtual-spine-id", _ = (e) => {
262
263
  try {
263
264
  return decodeURIComponent(e);
264
265
  } catch {
265
266
  return e;
266
267
  }
267
- }, te = (e) => {
268
+ }, re = (e) => {
268
269
  try {
269
270
  return decodeURI(e);
270
271
  } catch {
271
272
  return e;
272
273
  }
273
274
  }, u = (e) => {
274
- const t = p(e) ?? p(te(e));
275
+ const t = p(e) ?? p(re(e));
275
276
  if (t)
276
277
  return {
277
278
  ...t,
278
- originalUri: E(t.originalUri)
279
+ originalUri: _(t.originalUri)
279
280
  };
280
- }, re = (e, t) => {
281
+ }, ie = (e, t) => {
281
282
  const r = /* @__PURE__ */ new Set();
282
283
  let i = 0;
283
284
  for (const o of e.spineItemsManager.items) {
@@ -290,10 +291,10 @@ const M = 2e3, m = (e) => {
290
291
  return i;
291
292
  r.has(n.originalUri) || (r.add(n.originalUri), i++);
292
293
  }
293
- }, ie = (e, t, r) => {
294
+ }, ne = (e, t, r) => {
294
295
  const i = u(r.href);
295
296
  if (!i) return;
296
- const o = re(
297
+ const o = ie(
297
298
  e,
298
299
  i.originalUri
299
300
  );
@@ -302,14 +303,14 @@ const M = 2e3, m = (e) => {
302
303
  extensions: {
303
304
  [g]: encodeURIComponent(r.id)
304
305
  },
305
- spineId: i.originalUri,
306
+ spineId: P(i.originalUri),
306
307
  spineIndex: o
307
308
  });
308
- }, ne = (e, t) => {
309
- const r = b(t)?.extensions?.[g];
309
+ }, oe = (e, t) => {
310
+ const r = U(t)?.extensions?.[g];
310
311
  if (!r) return;
311
312
  const i = e.spineItemsManager.get(
312
- E(r)
313
+ _(r)
313
314
  );
314
315
  if (!(!i || !u(i.item.href)))
315
316
  return w(t, {
@@ -319,13 +320,13 @@ const M = 2e3, m = (e) => {
319
320
  spineId: i.item.id,
320
321
  spineIndex: i.item.index
321
322
  });
322
- }, ae = (e) => (t) => {
323
+ }, ce = (e) => (t) => {
323
324
  const r = e(t), i = r.hookManager.register(
324
325
  "cfi.afterGenerate",
325
- ({ cfi: s, spineItem: a }) => ie(r, s, a)
326
+ ({ cfi: s, spineItem: a }) => ne(r, s, a)
326
327
  ), o = r.hookManager.register(
327
328
  "cfi.beforeResolve",
328
- ({ cfi: s }) => ne(r, s)
329
+ ({ cfi: s }) => oe(r, s)
329
330
  );
330
331
  return {
331
332
  ...r,
@@ -334,24 +335,24 @@ const M = 2e3, m = (e) => {
334
335
  i(), o(), r.destroy();
335
336
  }
336
337
  };
337
- }, de = {
338
+ }, pe = {
338
339
  manifest: {
339
- spine: [j]
340
+ spine: [q]
340
341
  },
341
- resource: [ee]
342
+ resource: [te]
342
343
  };
343
344
  export {
344
- P as PAGE_SPREAD_RESOURCE_PREFIX,
345
- x as PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,
346
- N as buildVirtualPageSpreadResourcePath,
347
- ae as cbzEnhancer,
348
- K as createPageSpreadSplitXhtml,
349
- A as detectPageSpreadFromBasename,
350
- z as isPageSpreadSplitSupportedArchiveRecord,
345
+ x as PAGE_SPREAD_RESOURCE_PREFIX,
346
+ E as PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,
347
+ W as buildVirtualPageSpreadResourcePath,
348
+ ce as cbzEnhancer,
349
+ Q as createPageSpreadSplitXhtml,
350
+ T as detectPageSpreadFromBasename,
351
+ j as isPageSpreadSplitSupportedArchiveRecord,
351
352
  S as isPageSpreadSplitSupportedImage,
352
- j as pageSpreadSplit,
353
- ee as pageSpreadSplitResourceHook,
353
+ q as pageSpreadSplit,
354
+ te as pageSpreadSplitResourceHook,
354
355
  p as parseVirtualPageSpreadResourcePath,
355
- de as streamerHooks
356
+ pe as streamerHooks
356
357
  };
357
358
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/streamer.ts"],"sourcesContent":["/**\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 } from \"@prose-reader/streamer\"\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: `${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 if (virtualManifestItems.length === 0) return manifest\n\n return {\n ...manifest,\n spineItems: spineItems.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 { 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: 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 type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\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"],"names":["MAX_DETECTED_PAGE_NUMBER","numberFromPageLabel","label","value","detectPageLabelsFromBasename","basenameWithoutExtension","explicitPageRangeMatch","detectPageSpreadFromBasename","basename","match","firstPageLabel","secondPageLabel","firstPageNumber","secondPageNumber","PAGE_SPREAD_RESOURCE_PREFIX","PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE","supportedImageMediaTypes","isPageSpreadSplitSupportedImage","mimeType","encodeOriginalUriSegment","uri","createManifestResourceHref","baseUrl","resourcePath","hrefBaseUrl","hasOpfExtension","path","isArchiveEpub","archive","file","buildVirtualPageSpreadResourcePath","cropSide","originalUri","spreadPropertiesForSide","side","cropSidesInReadingOrder","readingDirection","createVirtualSpineItem","originalSpineItem","progressionWeight","createVirtualManifestItem","href","id","mediaType","getArchiveRecordForManifestItem","spineItem","hrefCandidates","decodeManifestHref","resourcePathCandidates","getResourcePathCandidates","item","normalizeBaseUrl","candidates","normalizedBaseUrl","mediaTypeFromArchiveRecord","record","parseContentType","detectMimeTypeFromName","mediaTypeFromArchiveRecordResourcePath","isPageSpreadSplitSupportedArchiveRecord","resourcePathMediaType","pageSpreadSplit","manifest","virtualManifestItems","spineItems","archiveRecord","detected","firstCropSide","secondCropSide","splitProgressionWeight","firstSpineItem","secondSpineItem","index","imageDimensionsCache","decodeOriginalUriSegment","encoded","parseVirtualPageSpreadResourcePath","prefixIndex","parts","encodedOriginalUri","cropFileName","cropRectForSide","imageHeight","imageWidth","leftWidth","rightWidth","getRelativeOriginalImageSrc","readImageDimensions","source","bitmap","createPageSpreadSplitXhtml","imageDimensions","crop","escapeXmlAttributeValue","generatePageSpreadSplitResource","virtualResource","archiveCache","pageSpreadSplitResourceHook","resource","pageSpreadResource","VIRTUAL_SPINE_ID_EXTENSION","decodeURIComponentSafe","decodeURISafe","parseVirtualPageSpreadFromHref","getOriginalSpineIndex","reader","seenVirtualOriginalUris","originalSpineIndex","restoreOriginalSpineReference","cfi","updateEpubCfiSpineItemref","restoreVirtualSpineReference","virtualSpineId","getEpubCfiSpineItemref","virtualSpineItem","cbzEnhancer","next","options","unregisterGenerateHook","unregisterResolveHook","streamerHooks"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/streamer.ts"],"sourcesContent":["/**\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 { 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 if (virtualManifestItems.length === 0) return manifest\n\n return {\n ...manifest,\n spineItems: spineItems.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 type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\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"],"names":["MAX_DETECTED_PAGE_NUMBER","numberFromPageLabel","label","value","detectPageLabelsFromBasename","basenameWithoutExtension","explicitPageRangeMatch","detectPageSpreadFromBasename","basename","match","firstPageLabel","secondPageLabel","firstPageNumber","secondPageNumber","PAGE_SPREAD_RESOURCE_PREFIX","PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE","supportedImageMediaTypes","isPageSpreadSplitSupportedImage","mimeType","encodeOriginalUriSegment","uri","createManifestResourceHref","baseUrl","resourcePath","hrefBaseUrl","hasOpfExtension","path","isArchiveEpub","archive","file","buildVirtualPageSpreadResourcePath","cropSide","originalUri","spreadPropertiesForSide","side","cropSidesInReadingOrder","readingDirection","createVirtualSpineItem","originalSpineItem","progressionWeight","createXmlSafeId","createVirtualManifestItem","href","id","mediaType","getArchiveRecordForManifestItem","spineItem","hrefCandidates","decodeManifestHref","resourcePathCandidates","getResourcePathCandidates","item","normalizeBaseUrl","candidates","normalizedBaseUrl","mediaTypeFromArchiveRecord","record","parseContentType","detectMimeTypeFromName","mediaTypeFromArchiveRecordResourcePath","isPageSpreadSplitSupportedArchiveRecord","resourcePathMediaType","pageSpreadSplit","manifest","virtualManifestItems","spineItems","archiveRecord","detected","firstCropSide","secondCropSide","splitProgressionWeight","firstSpineItem","secondSpineItem","index","imageDimensionsCache","decodeOriginalUriSegment","encoded","parseVirtualPageSpreadResourcePath","prefixIndex","parts","encodedOriginalUri","cropFileName","cropRectForSide","imageHeight","imageWidth","leftWidth","rightWidth","getRelativeOriginalImageSrc","readImageDimensions","source","bitmap","createPageSpreadSplitXhtml","imageDimensions","crop","escapeXmlAttributeValue","generatePageSpreadSplitResource","virtualResource","archiveCache","pageSpreadSplitResourceHook","resource","pageSpreadResource","VIRTUAL_SPINE_ID_EXTENSION","decodeURIComponentSafe","decodeURISafe","parseVirtualPageSpreadFromHref","getOriginalSpineIndex","reader","seenVirtualOriginalUris","originalSpineIndex","restoreOriginalSpineReference","cfi","updateEpubCfiSpineItemref","restoreVirtualSpineReference","virtualSpineId","getEpubCfiSpineItemref","virtualSpineItem","cbzEnhancer","next","options","unregisterGenerateHook","unregisterResolveHook","streamerHooks"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- (function(s,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("@prose-reader/cfi"),require("@prose-reader/shared")):typeof define=="function"&&define.amd?define(["exports","@prose-reader/cfi","@prose-reader/shared"],c):(s=typeof globalThis<"u"?globalThis:s||self,c(s["prose-reader-cbz"]={},s.cfi,s.shared))})(this,(function(s,c,p){"use strict";const v=e=>{const t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>2e3))return t},$=e=>{const t=/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e);return t||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e)},R=e=>{const t=e.replace(/\.[^.]+$/,""),r=$(t);if(!r)return;const[,i,o]=r;if(i===void 0||o===void 0)return;const n=v(i),a=v(o);if(!(n===void 0||a===void 0)&&a===n+1)return{firstPageLabel:i,secondPageLabel:o}},g="__prose-reader__/page-spread",l="application/xhtml+xml",x=new Set(["image/jpg","image/jpeg","image/png","image/webp"]),h=e=>e===void 0?!1:x.has(e),F=e=>encodeURIComponent(e),N=({baseUrl:e="",resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);const r=e?`${e}${e.endsWith("/")?"":"/"}`:"file://";return encodeURI(`${r}${t}`)},P=e=>e.toLowerCase().endsWith(".opf"),O=e=>e.records.some(t=>!t.dir&&(P(t.basename)||P(t.uri))),I=({cropSide:e,originalUri:t})=>`${g}/${F(t)}/${e}.xhtml`,D=e=>e==="left"?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},L=e=>e==="rtl"?["right","left"]:["left","right"],E=({baseUrl:e,cropSide:t,label:r,originalSpineItem:i,originalUri:o,progressionWeight:n})=>{const a=I({cropSide:t,originalUri:o});return{...i,id:`${i.id}.${r}`,href:N({baseUrl:e,resourcePath:a}),mediaType:l,progressionWeight:n,renditionLayout:"pre-paginated",...D(t)}},_=({href:e,id:t,mediaType:r})=>({href:e,id:t,mediaType:r}),W=({archive:e,baseUrl:t,spineItem:r})=>{const i=[r.href,B(r.href)],o=new Set(i.flatMap(n=>H(n,t)));return e.records.find(n=>!n.dir&&o.has(n.uri))},B=e=>{try{return decodeURI(e)}catch{return e}},k=e=>e.endsWith("/")?e:`${e}/`,H=(e,t)=>{const r=[e];if(e.startsWith("file://")&&r.push(e.slice(7)),t){const i=k(t);e.startsWith(i)&&r.push(e.slice(i.length))}return r},V=e=>p.parseContentType(e?.encodingFormat??"")||p.detectMimeTypeFromName(e?.basename??""),X=e=>p.detectMimeTypeFromName(e.uri)||p.detectMimeTypeFromName(e.basename),w=e=>{if(e===void 0||e.dir)return!1;const t=X(e);return h(t)?h(V(e)):!1},y=({archive:e,baseUrl:t})=>async r=>{if(O(e))return r;const i=[],o=r.spineItems.flatMap(n=>{const a=W({archive:e,baseUrl:t,spineItem:n});if(!w(a))return[n];const d=R(a.basename);if(d===void 0)return[n];const[re,ie]=L(r.readingDirection),T=n.progressionWeight!==void 0?n.progressionWeight/2:void 0,C=E({baseUrl:t,cropSide:re,label:d.firstPageLabel,originalSpineItem:n,originalUri:a.uri,progressionWeight:T}),A=E({baseUrl:t,cropSide:ie,label:d.secondPageLabel,originalSpineItem:n,originalUri:a.uri,progressionWeight:T});return i.push(_(C),_(A)),[C,A]});return i.length===0?r:{...r,spineItems:o.map((n,a)=>({...n,index:a})),items:[...r.items,...i]}},f=new WeakMap,G=e=>{try{return decodeURIComponent(e)}catch{return}},u=e=>{const t=e.indexOf(`${g}/`);if(t<0)return;const i=e.slice(t).split("/"),o=i[2],n=i[3];if(i.length!==4||i[0]!=="__prose-reader__"||i[1]!=="page-spread"||o===void 0||n===void 0)return;const a=n.split(".")[0];if(a!=="left"&&a!=="right")return;const d=G(o);if(d!==void 0)return{originalUri:d,cropSide:a}},z=({cropSide:e,imageHeight:t,imageWidth:r})=>{const i=Math.floor(r/2),o=r-i;return e==="left"?{x:0,width:i,height:t}:{x:i,width:o,height:t}},j=e=>/^https?:\/\//.test(e)?e:`../../../${encodeURI(e)}`,q=async e=>{if(typeof createImageBitmap!="function")throw new Error("Page spread XHTML generation requires createImageBitmap");const t=await createImageBitmap(e);try{return{height:t.height,width:t.width}}finally{t.close()}},b=({cropSide:e,imageDimensions:t,originalUri:r})=>{if(t.width<2)throw new Error("Page spread image is too narrow to split");const i=z({cropSide:e,imageHeight:t.height,imageWidth:t.width});return`<?xml version="1.0" encoding="UTF-8"?>
1
+ (function(a,c){typeof exports=="object"&&typeof module<"u"?c(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"],c):(a=typeof globalThis<"u"?globalThis:a||self,c(a["prose-reader-cbz"]={},a.cfi,a.streamer,a.shared))})(this,(function(a,c,v,p){"use strict";const R=e=>{const t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>2e3))return t},x=e=>{const t=/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e);return t||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e)},P=e=>{const t=e.replace(/\.[^.]+$/,""),r=x(t);if(!r)return;const[,i,o]=r;if(i===void 0||o===void 0)return;const n=R(i),s=R(o);if(!(n===void 0||s===void 0)&&s===n+1)return{firstPageLabel:i,secondPageLabel:o}},g="__prose-reader__/page-spread",l="application/xhtml+xml",F=new Set(["image/jpg","image/jpeg","image/png","image/webp"]),h=e=>e===void 0?!1:F.has(e),N=e=>encodeURIComponent(e),O=({baseUrl:e="",resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);const r=e?`${e}${e.endsWith("/")?"":"/"}`:"file://";return encodeURI(`${r}${t}`)},I=e=>e.toLowerCase().endsWith(".opf"),D=e=>e.records.some(t=>!t.dir&&(I(t.basename)||I(t.uri))),E=({cropSide:e,originalUri:t})=>`${g}/${N(t)}/${e}.xhtml`,L=e=>e==="left"?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},W=e=>e==="rtl"?["right","left"]:["left","right"],_=({baseUrl:e,cropSide:t,label:r,originalSpineItem:i,originalUri:o,progressionWeight:n})=>{const s=E({cropSide:t,originalUri:o});return{...i,id:v.createXmlSafeId(`${i.id}.${r}`),href:O({baseUrl:e,resourcePath:s}),mediaType:l,progressionWeight:n,renditionLayout:"pre-paginated",...L(t)}},w=({href:e,id:t,mediaType:r})=>({href:e,id:t,mediaType:r}),B=({archive:e,baseUrl:t,spineItem:r})=>{const i=[r.href,X(r.href)],o=new Set(i.flatMap(n=>H(n,t)));return e.records.find(n=>!n.dir&&o.has(n.uri))},X=e=>{try{return decodeURI(e)}catch{return e}},k=e=>e.endsWith("/")?e:`${e}/`,H=(e,t)=>{const r=[e];if(e.startsWith("file://")&&r.push(e.slice(7)),t){const i=k(t);e.startsWith(i)&&r.push(e.slice(i.length))}return r},V=e=>p.parseContentType(e?.encodingFormat??"")||p.detectMimeTypeFromName(e?.basename??""),G=e=>p.detectMimeTypeFromName(e.uri)||p.detectMimeTypeFromName(e.basename),y=e=>{if(e===void 0||e.dir)return!1;const t=G(e);return h(t)?h(V(e)):!1},b=({archive:e,baseUrl:t})=>async r=>{if(D(e))return r;const i=[],o=r.spineItems.flatMap(n=>{const s=B({archive:e,baseUrl:t,spineItem:n});if(!y(s))return[n];const d=P(s.basename);if(d===void 0)return[n];const[ie,ne]=W(r.readingDirection),C=n.progressionWeight!==void 0?n.progressionWeight/2:void 0,A=_({baseUrl:t,cropSide:ie,label:d.firstPageLabel,originalSpineItem:n,originalUri:s.uri,progressionWeight:C}),$=_({baseUrl:t,cropSide:ne,label:d.secondPageLabel,originalSpineItem:n,originalUri:s.uri,progressionWeight:C});return i.push(w(A),w($)),[A,$]});return i.length===0?r:{...r,spineItems:o.map((n,s)=>({...n,index:s})),items:[...r.items,...i]}},f=new WeakMap,z=e=>{try{return decodeURIComponent(e)}catch{return}},u=e=>{const t=e.indexOf(`${g}/`);if(t<0)return;const i=e.slice(t).split("/"),o=i[2],n=i[3];if(i.length!==4||i[0]!=="__prose-reader__"||i[1]!=="page-spread"||o===void 0||n===void 0)return;const s=n.split(".")[0];if(s!=="left"&&s!=="right")return;const d=z(o);if(d!==void 0)return{originalUri:d,cropSide:s}},j=({cropSide:e,imageHeight:t,imageWidth:r})=>{const i=Math.floor(r/2),o=r-i;return e==="left"?{x:0,width:i,height:t}:{x:i,width:o,height:t}},q=e=>/^https?:\/\//.test(e)?e:`../../../${encodeURI(e)}`,Y=async e=>{if(typeof createImageBitmap!="function")throw new Error("Page spread XHTML generation requires createImageBitmap");const t=await createImageBitmap(e);try{return{height:t.height,width:t.width}}finally{t.close()}},U=({cropSide:e,imageDimensions:t,originalUri:r})=>{if(t.width<2)throw new Error("Page spread image is too narrow to split");const i=j({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}" />
@@ -23,7 +23,7 @@
23
23
  </style>
24
24
  </head>
25
25
  <body>
26
- <img src="${p.escapeXmlAttributeValue(j(r))}" alt="" />
26
+ <img src="${p.escapeXmlAttributeValue(q(r))}" alt="" />
27
27
  </body>
28
- </html>`},Y=async({archive:e,resourcePath:t})=>{const r=u(t);if(r===void 0)return;const i=e.records.find(d=>d.uri===r.originalUri&&!d.dir);if(i===void 0||i.dir)throw new Error(`no source file found for virtual page spread resourcePath:${t}`);const o=f.get(e)??new Map;f.has(e)||f.set(e,o);const n=o.get(r.originalUri)??await q(await i.blob());return o.set(r.originalUri,n),{body:b({cropSide:r.cropSide,imageDimensions:n,originalUri:r.originalUri}),params:{contentType:l}}},U=({archive:e,resourcePath:t})=>async r=>{const i=await Y({archive:e,resourcePath:t});return i===void 0?r:{...r,...i,params:{...r.params,...i.params}}},m="vnd.prose-reader.cbz.virtual-spine-id",M=e=>{try{return decodeURIComponent(e)}catch{return e}},Z=e=>{try{return decodeURI(e)}catch{return e}},S=e=>{const t=u(e)??u(Z(e));if(t)return{...t,originalUri:M(t.originalUri)}},J=(e,t)=>{const r=new Set;let i=0;for(const o of e.spineItemsManager.items){const n=S(o.item.href);if(!n){i++;continue}if(n.originalUri===t)return i;r.has(n.originalUri)||(r.add(n.originalUri),i++)}},K=(e,t,r)=>{const i=S(r.href);if(!i)return;const o=J(e,i.originalUri);if(o!==void 0)return c.updateEpubCfiSpineItemref(t,{extensions:{[m]:encodeURIComponent(r.id)},spineId:i.originalUri,spineIndex:o})},Q=(e,t)=>{const r=c.getEpubCfiSpineItemref(t)?.extensions?.[m];if(!r)return;const i=e.spineItemsManager.get(M(r));if(!(!i||!S(i.item.href)))return c.updateEpubCfiSpineItemref(t,{extensions:{[m]:void 0},spineId:i.item.id,spineIndex:i.item.index})},ee=e=>t=>{const r=e(t),i=r.hookManager.register("cfi.afterGenerate",({cfi:a,spineItem:d})=>K(r,a,d)),o=r.hookManager.register("cfi.beforeResolve",({cfi:a})=>Q(r,a));return{...r,__PROSE_READER_ENHANCER_CBZ:!0,destroy:()=>{i(),o(),r.destroy()}}},te={manifest:{spine:[y]},resource:[U]};s.PAGE_SPREAD_RESOURCE_PREFIX=g,s.PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE=l,s.buildVirtualPageSpreadResourcePath=I,s.cbzEnhancer=ee,s.createPageSpreadSplitXhtml=b,s.detectPageSpreadFromBasename=R,s.isPageSpreadSplitSupportedArchiveRecord=w,s.isPageSpreadSplitSupportedImage=h,s.pageSpreadSplit=y,s.pageSpreadSplitResourceHook=U,s.parseVirtualPageSpreadResourcePath=u,s.streamerHooks=te,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
28
+ </html>`},Z=async({archive:e,resourcePath:t})=>{const r=u(t);if(r===void 0)return;const i=e.records.find(d=>d.uri===r.originalUri&&!d.dir);if(i===void 0||i.dir)throw new Error(`no source file found for virtual page spread resourcePath:${t}`);const o=f.get(e)??new Map;f.has(e)||f.set(e,o);const n=o.get(r.originalUri)??await Y(await i.blob());return o.set(r.originalUri,n),{body:U({cropSide:r.cropSide,imageDimensions:n,originalUri:r.originalUri}),params:{contentType:l}}},M=({archive:e,resourcePath:t})=>async r=>{const i=await Z({archive:e,resourcePath:t});return i===void 0?r:{...r,...i,params:{...r.params,...i.params}}},m="vnd.prose-reader.cbz.virtual-spine-id",T=e=>{try{return decodeURIComponent(e)}catch{return e}},J=e=>{try{return decodeURI(e)}catch{return e}},S=e=>{const t=u(e)??u(J(e));if(t)return{...t,originalUri:T(t.originalUri)}},K=(e,t)=>{const r=new Set;let i=0;for(const o of e.spineItemsManager.items){const n=S(o.item.href);if(!n){i++;continue}if(n.originalUri===t)return i;r.has(n.originalUri)||(r.add(n.originalUri),i++)}},Q=(e,t,r)=>{const i=S(r.href);if(!i)return;const o=K(e,i.originalUri);if(o!==void 0)return c.updateEpubCfiSpineItemref(t,{extensions:{[m]:encodeURIComponent(r.id)},spineId:v.createXmlSafeId(i.originalUri),spineIndex:o})},ee=(e,t)=>{const r=c.getEpubCfiSpineItemref(t)?.extensions?.[m];if(!r)return;const i=e.spineItemsManager.get(T(r));if(!(!i||!S(i.item.href)))return c.updateEpubCfiSpineItemref(t,{extensions:{[m]:void 0},spineId:i.item.id,spineIndex:i.item.index})},te=e=>t=>{const r=e(t),i=r.hookManager.register("cfi.afterGenerate",({cfi:s,spineItem:d})=>Q(r,s,d)),o=r.hookManager.register("cfi.beforeResolve",({cfi:s})=>ee(r,s));return{...r,__PROSE_READER_ENHANCER_CBZ:!0,destroy:()=>{i(),o(),r.destroy()}}},re={manifest:{spine:[b]},resource:[M]};a.PAGE_SPREAD_RESOURCE_PREFIX=g,a.PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE=l,a.buildVirtualPageSpreadResourcePath=E,a.cbzEnhancer=te,a.createPageSpreadSplitXhtml=U,a.detectPageSpreadFromBasename=P,a.isPageSpreadSplitSupportedArchiveRecord=y,a.isPageSpreadSplitSupportedImage=h,a.pageSpreadSplit=b,a.pageSpreadSplitResourceHook=M,a.parseVirtualPageSpreadResourcePath=u,a.streamerHooks=re,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
29
29
  //# sourceMappingURL=index.umd.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.cjs","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/streamer.ts"],"sourcesContent":["/**\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 } from \"@prose-reader/streamer\"\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: `${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 if (virtualManifestItems.length === 0) return manifest\n\n return {\n ...manifest,\n spineItems: spineItems.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 { 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: 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 type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\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"],"names":["numberFromPageLabel","label","value","detectPageLabelsFromBasename","basenameWithoutExtension","explicitPageRangeMatch","detectPageSpreadFromBasename","basename","match","firstPageLabel","secondPageLabel","firstPageNumber","secondPageNumber","PAGE_SPREAD_RESOURCE_PREFIX","PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE","supportedImageMediaTypes","isPageSpreadSplitSupportedImage","mimeType","encodeOriginalUriSegment","uri","createManifestResourceHref","baseUrl","resourcePath","hrefBaseUrl","hasOpfExtension","path","isArchiveEpub","archive","file","buildVirtualPageSpreadResourcePath","cropSide","originalUri","spreadPropertiesForSide","side","cropSidesInReadingOrder","readingDirection","createVirtualSpineItem","originalSpineItem","progressionWeight","createVirtualManifestItem","href","id","mediaType","getArchiveRecordForManifestItem","spineItem","hrefCandidates","decodeManifestHref","resourcePathCandidates","getResourcePathCandidates","item","normalizeBaseUrl","candidates","normalizedBaseUrl","mediaTypeFromArchiveRecord","record","parseContentType","detectMimeTypeFromName","mediaTypeFromArchiveRecordResourcePath","isPageSpreadSplitSupportedArchiveRecord","resourcePathMediaType","pageSpreadSplit","manifest","virtualManifestItems","spineItems","archiveRecord","detected","firstCropSide","secondCropSide","splitProgressionWeight","firstSpineItem","secondSpineItem","index","imageDimensionsCache","decodeOriginalUriSegment","encoded","parseVirtualPageSpreadResourcePath","prefixIndex","parts","encodedOriginalUri","cropFileName","cropRectForSide","imageHeight","imageWidth","leftWidth","rightWidth","getRelativeOriginalImageSrc","readImageDimensions","source","bitmap","createPageSpreadSplitXhtml","imageDimensions","crop","escapeXmlAttributeValue","generatePageSpreadSplitResource","virtualResource","archiveCache","pageSpreadSplitResourceHook","resource","pageSpreadResource","VIRTUAL_SPINE_ID_EXTENSION","decodeURIComponentSafe","decodeURISafe","parseVirtualPageSpreadFromHref","getOriginalSpineIndex","reader","seenVirtualOriginalUris","originalSpineIndex","restoreOriginalSpineReference","cfi","updateEpubCfiSpineItemref","restoreVirtualSpineReference","virtualSpineId","getEpubCfiSpineItemref","virtualSpineItem","cbzEnhancer","next","options","unregisterGenerateHook","unregisterResolveHook","streamerHooks"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.cjs","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.ts","../src/enhancer.ts","../src/streamer.ts"],"sourcesContent":["/**\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 { 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 if (virtualManifestItems.length === 0) return manifest\n\n return {\n ...manifest,\n spineItems: spineItems.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 type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { pageSpreadSplit } from \"./pageSpreadSplitManifest\"\nimport { pageSpreadSplitResourceHook } from \"./pageSpreadSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n spine: [pageSpreadSplit],\n },\n resource: [pageSpreadSplitResourceHook],\n}\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"],"names":["numberFromPageLabel","label","value","detectPageLabelsFromBasename","basenameWithoutExtension","explicitPageRangeMatch","detectPageSpreadFromBasename","basename","match","firstPageLabel","secondPageLabel","firstPageNumber","secondPageNumber","PAGE_SPREAD_RESOURCE_PREFIX","PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE","supportedImageMediaTypes","isPageSpreadSplitSupportedImage","mimeType","encodeOriginalUriSegment","uri","createManifestResourceHref","baseUrl","resourcePath","hrefBaseUrl","hasOpfExtension","path","isArchiveEpub","archive","file","buildVirtualPageSpreadResourcePath","cropSide","originalUri","spreadPropertiesForSide","side","cropSidesInReadingOrder","readingDirection","createVirtualSpineItem","originalSpineItem","progressionWeight","createXmlSafeId","createVirtualManifestItem","href","id","mediaType","getArchiveRecordForManifestItem","spineItem","hrefCandidates","decodeManifestHref","resourcePathCandidates","getResourcePathCandidates","item","normalizeBaseUrl","candidates","normalizedBaseUrl","mediaTypeFromArchiveRecord","record","parseContentType","detectMimeTypeFromName","mediaTypeFromArchiveRecordResourcePath","isPageSpreadSplitSupportedArchiveRecord","resourcePathMediaType","pageSpreadSplit","manifest","virtualManifestItems","spineItems","archiveRecord","detected","firstCropSide","secondCropSide","splitProgressionWeight","firstSpineItem","secondSpineItem","index","imageDimensionsCache","decodeOriginalUriSegment","encoded","parseVirtualPageSpreadResourcePath","prefixIndex","parts","encodedOriginalUri","cropFileName","cropRectForSide","imageHeight","imageWidth","leftWidth","rightWidth","getRelativeOriginalImageSrc","readImageDimensions","source","bitmap","createPageSpreadSplitXhtml","imageDimensions","crop","escapeXmlAttributeValue","generatePageSpreadSplitResource","virtualResource","archiveCache","pageSpreadSplitResourceHook","resource","pageSpreadResource","VIRTUAL_SPINE_ID_EXTENSION","decodeURIComponentSafe","decodeURISafe","parseVirtualPageSpreadFromHref","getOriginalSpineIndex","reader","seenVirtualOriginalUris","originalSpineIndex","restoreOriginalSpineReference","cfi","updateEpubCfiSpineItemref","restoreVirtualSpineReference","virtualSpineId","getEpubCfiSpineItemref","virtualSpineItem","cbzEnhancer","next","options","unregisterGenerateHook","unregisterResolveHook","streamerHooks"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/cbz",
3
- "version": "1.300.0",
3
+ "version": "1.301.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": "5c5abbe4e50639e6f620eae1d4f4e51104b7cc47"
39
+ "gitHead": "c8e649c793f0b56c63d9fe3d50d5b5cef4fcbf23"
40
40
  }