@prose-reader/cbz 1.296.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/detectPageSpreadFromBasename.d.ts +5 -0
- package/dist/detectPageSpreadFromBasename.test.d.ts +1 -0
- package/dist/enhancer.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +359 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.cjs +362 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/pageSpreadSplitManifest.d.ts +33 -0
- package/dist/pageSpreadSplitManifest.test.d.ts +1 -0
- package/dist/pageSpreadSplitResource.d.ts +17 -0
- package/dist/pageSpreadSplitResource.test.d.ts +1 -0
- package/dist/streamer.d.ts +9 -0
- package/package.json +37 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { detectMimeTypeFromName, parseContentType, escapeXmlAttributeValue } from "@prose-reader/shared";
|
|
2
|
+
const MAX_DETECTED_PAGE_NUMBER = 2e3;
|
|
3
|
+
const numberFromPageLabel = (label) => {
|
|
4
|
+
const value = Number.parseInt(label, 10);
|
|
5
|
+
if (!Number.isFinite(value)) return void 0;
|
|
6
|
+
if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return void 0;
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
const detectPageLabelsFromBasename = (basenameWithoutExtension) => {
|
|
10
|
+
const explicitPageRangeMatch = /(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(
|
|
11
|
+
basenameWithoutExtension
|
|
12
|
+
);
|
|
13
|
+
if (explicitPageRangeMatch) return explicitPageRangeMatch;
|
|
14
|
+
return /(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_]\s*(0\d{1,4})(?=$|[^\d])/i.exec(
|
|
15
|
+
basenameWithoutExtension
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
const detectPageSpreadFromBasename = (basename) => {
|
|
19
|
+
const basenameWithoutExtension = basename.replace(/\.[^.]+$/, ``);
|
|
20
|
+
const match = detectPageLabelsFromBasename(basenameWithoutExtension);
|
|
21
|
+
if (!match) return void 0;
|
|
22
|
+
const [, firstPageLabel, secondPageLabel] = match;
|
|
23
|
+
if (firstPageLabel === void 0 || secondPageLabel === void 0) {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
const firstPageNumber = numberFromPageLabel(firstPageLabel);
|
|
27
|
+
const secondPageNumber = numberFromPageLabel(secondPageLabel);
|
|
28
|
+
if (firstPageNumber === void 0 || secondPageNumber === void 0) {
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
if (secondPageNumber !== firstPageNumber + 1) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
firstPageLabel,
|
|
36
|
+
secondPageLabel
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const PAGE_SPREAD_RESOURCE_PREFIX = `__prose-reader__/page-spread`;
|
|
40
|
+
const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`;
|
|
41
|
+
const supportedImageMediaTypes = /* @__PURE__ */ new Set([
|
|
42
|
+
`image/jpg`,
|
|
43
|
+
`image/jpeg`,
|
|
44
|
+
`image/png`,
|
|
45
|
+
`image/webp`
|
|
46
|
+
]);
|
|
47
|
+
const isPageSpreadSplitSupportedImage = (mimeType) => {
|
|
48
|
+
if (mimeType === void 0) return false;
|
|
49
|
+
return supportedImageMediaTypes.has(mimeType);
|
|
50
|
+
};
|
|
51
|
+
const encodeOriginalUriSegment = (uri) => encodeURIComponent(uri);
|
|
52
|
+
const createManifestResourceHref = ({
|
|
53
|
+
baseUrl = ``,
|
|
54
|
+
resourcePath
|
|
55
|
+
}) => {
|
|
56
|
+
if (!baseUrl && /^https?:\/\//.test(resourcePath)) {
|
|
57
|
+
return encodeURI(resourcePath);
|
|
58
|
+
}
|
|
59
|
+
const hrefBaseUrl = baseUrl ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}` : `file://`;
|
|
60
|
+
return encodeURI(`${hrefBaseUrl}${resourcePath}`);
|
|
61
|
+
};
|
|
62
|
+
const hasOpfExtension = (path) => path.toLowerCase().endsWith(`.opf`);
|
|
63
|
+
const isArchiveEpub = (archive) => archive.records.some(
|
|
64
|
+
(file) => !file.dir && (hasOpfExtension(file.basename) || hasOpfExtension(file.uri))
|
|
65
|
+
);
|
|
66
|
+
const buildVirtualPageSpreadResourcePath = ({
|
|
67
|
+
cropSide,
|
|
68
|
+
originalUri
|
|
69
|
+
}) => {
|
|
70
|
+
return `${PAGE_SPREAD_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`;
|
|
71
|
+
};
|
|
72
|
+
const spreadPropertiesForSide = (side) => side === `left` ? { pageSpreadLeft: true, pageSpreadRight: void 0 } : { pageSpreadLeft: void 0, pageSpreadRight: true };
|
|
73
|
+
const cropSidesInReadingOrder = (readingDirection) => readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`];
|
|
74
|
+
const createVirtualSpineItem = ({
|
|
75
|
+
baseUrl,
|
|
76
|
+
cropSide,
|
|
77
|
+
label,
|
|
78
|
+
originalSpineItem,
|
|
79
|
+
originalUri,
|
|
80
|
+
progressionWeight
|
|
81
|
+
}) => {
|
|
82
|
+
const resourcePath = buildVirtualPageSpreadResourcePath({
|
|
83
|
+
cropSide,
|
|
84
|
+
originalUri
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
...originalSpineItem,
|
|
88
|
+
id: `${originalSpineItem.id}.${label}`,
|
|
89
|
+
href: createManifestResourceHref({ baseUrl, resourcePath }),
|
|
90
|
+
mediaType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,
|
|
91
|
+
progressionWeight,
|
|
92
|
+
renditionLayout: `pre-paginated`,
|
|
93
|
+
...spreadPropertiesForSide(cropSide)
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const createVirtualManifestItem = ({
|
|
97
|
+
href,
|
|
98
|
+
id,
|
|
99
|
+
mediaType
|
|
100
|
+
}) => ({
|
|
101
|
+
href,
|
|
102
|
+
id,
|
|
103
|
+
mediaType
|
|
104
|
+
});
|
|
105
|
+
const getArchiveRecordForManifestItem = ({
|
|
106
|
+
archive,
|
|
107
|
+
baseUrl,
|
|
108
|
+
spineItem
|
|
109
|
+
}) => {
|
|
110
|
+
const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)];
|
|
111
|
+
const resourcePathCandidates = new Set(
|
|
112
|
+
hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl))
|
|
113
|
+
);
|
|
114
|
+
return archive.records.find(
|
|
115
|
+
(item) => !item.dir && resourcePathCandidates.has(item.uri)
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
const decodeManifestHref = (href) => {
|
|
119
|
+
try {
|
|
120
|
+
return decodeURI(href);
|
|
121
|
+
} catch {
|
|
122
|
+
return href;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const normalizeBaseUrl = (baseUrl) => baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`;
|
|
126
|
+
const getResourcePathCandidates = (href, baseUrl) => {
|
|
127
|
+
const candidates = [href];
|
|
128
|
+
if (href.startsWith(`file://`)) {
|
|
129
|
+
candidates.push(href.slice(`file://`.length));
|
|
130
|
+
}
|
|
131
|
+
if (baseUrl) {
|
|
132
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
133
|
+
if (href.startsWith(normalizedBaseUrl)) {
|
|
134
|
+
candidates.push(href.slice(normalizedBaseUrl.length));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return candidates;
|
|
138
|
+
};
|
|
139
|
+
const mediaTypeFromArchiveRecord = (record) => parseContentType(record?.encodingFormat ?? ``) || detectMimeTypeFromName(record?.basename ?? ``);
|
|
140
|
+
const mediaTypeFromArchiveRecordResourcePath = (record) => detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename);
|
|
141
|
+
const isPageSpreadSplitSupportedArchiveRecord = (record) => {
|
|
142
|
+
if (record === void 0 || record.dir) return false;
|
|
143
|
+
const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record);
|
|
144
|
+
if (!isPageSpreadSplitSupportedImage(resourcePathMediaType)) return false;
|
|
145
|
+
return isPageSpreadSplitSupportedImage(mediaTypeFromArchiveRecord(record));
|
|
146
|
+
};
|
|
147
|
+
const pageSpreadSplit = ({ archive, baseUrl }) => async (manifest) => {
|
|
148
|
+
if (isArchiveEpub(archive)) return manifest;
|
|
149
|
+
const virtualManifestItems = [];
|
|
150
|
+
const spineItems = manifest.spineItems.flatMap((spineItem) => {
|
|
151
|
+
const archiveRecord = getArchiveRecordForManifestItem({
|
|
152
|
+
archive,
|
|
153
|
+
baseUrl,
|
|
154
|
+
spineItem
|
|
155
|
+
});
|
|
156
|
+
if (!isPageSpreadSplitSupportedArchiveRecord(archiveRecord)) {
|
|
157
|
+
return [spineItem];
|
|
158
|
+
}
|
|
159
|
+
const detected = detectPageSpreadFromBasename(archiveRecord.basename);
|
|
160
|
+
if (detected === void 0) return [spineItem];
|
|
161
|
+
const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(
|
|
162
|
+
manifest.readingDirection
|
|
163
|
+
);
|
|
164
|
+
const splitProgressionWeight = spineItem.progressionWeight !== void 0 ? spineItem.progressionWeight / 2 : void 0;
|
|
165
|
+
const firstSpineItem = createVirtualSpineItem({
|
|
166
|
+
baseUrl,
|
|
167
|
+
cropSide: firstCropSide,
|
|
168
|
+
label: detected.firstPageLabel,
|
|
169
|
+
originalSpineItem: spineItem,
|
|
170
|
+
originalUri: archiveRecord.uri,
|
|
171
|
+
progressionWeight: splitProgressionWeight
|
|
172
|
+
});
|
|
173
|
+
const secondSpineItem = createVirtualSpineItem({
|
|
174
|
+
baseUrl,
|
|
175
|
+
cropSide: secondCropSide,
|
|
176
|
+
label: detected.secondPageLabel,
|
|
177
|
+
originalSpineItem: spineItem,
|
|
178
|
+
originalUri: archiveRecord.uri,
|
|
179
|
+
progressionWeight: splitProgressionWeight
|
|
180
|
+
});
|
|
181
|
+
virtualManifestItems.push(
|
|
182
|
+
createVirtualManifestItem(firstSpineItem),
|
|
183
|
+
createVirtualManifestItem(secondSpineItem)
|
|
184
|
+
);
|
|
185
|
+
return [firstSpineItem, secondSpineItem];
|
|
186
|
+
});
|
|
187
|
+
if (virtualManifestItems.length === 0) return manifest;
|
|
188
|
+
return {
|
|
189
|
+
...manifest,
|
|
190
|
+
spineItems: spineItems.map((spineItem, index) => ({
|
|
191
|
+
...spineItem,
|
|
192
|
+
index
|
|
193
|
+
})),
|
|
194
|
+
items: [...manifest.items, ...virtualManifestItems]
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
const imageDimensionsCache = /* @__PURE__ */ new WeakMap();
|
|
198
|
+
const decodeOriginalUriSegment = (encoded) => {
|
|
199
|
+
try {
|
|
200
|
+
return decodeURIComponent(encoded);
|
|
201
|
+
} catch {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const parseVirtualPageSpreadResourcePath = (resourcePath) => {
|
|
206
|
+
const prefixIndex = resourcePath.indexOf(`${PAGE_SPREAD_RESOURCE_PREFIX}/`);
|
|
207
|
+
if (prefixIndex < 0) return void 0;
|
|
208
|
+
const virtualPath = resourcePath.slice(prefixIndex);
|
|
209
|
+
const parts = virtualPath.split(`/`);
|
|
210
|
+
const encodedOriginalUri = parts[2];
|
|
211
|
+
const cropFileName = parts[3];
|
|
212
|
+
if (parts.length !== 4 || parts[0] !== `__prose-reader__` || parts[1] !== `page-spread` || encodedOriginalUri === void 0 || cropFileName === void 0) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
const cropSide = cropFileName.split(`.`)[0];
|
|
216
|
+
if (cropSide !== `left` && cropSide !== `right`) return void 0;
|
|
217
|
+
const originalUri = decodeOriginalUriSegment(encodedOriginalUri);
|
|
218
|
+
if (originalUri === void 0) return void 0;
|
|
219
|
+
return {
|
|
220
|
+
originalUri,
|
|
221
|
+
cropSide
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
const cropRectForSide = ({
|
|
225
|
+
cropSide,
|
|
226
|
+
imageHeight,
|
|
227
|
+
imageWidth
|
|
228
|
+
}) => {
|
|
229
|
+
const leftWidth = Math.floor(imageWidth / 2);
|
|
230
|
+
const rightWidth = imageWidth - leftWidth;
|
|
231
|
+
return cropSide === `left` ? { x: 0, width: leftWidth, height: imageHeight } : { x: leftWidth, width: rightWidth, height: imageHeight };
|
|
232
|
+
};
|
|
233
|
+
const getRelativeOriginalImageSrc = (originalUri) => {
|
|
234
|
+
if (/^https?:\/\//.test(originalUri)) return originalUri;
|
|
235
|
+
return `../../../${encodeURI(originalUri)}`;
|
|
236
|
+
};
|
|
237
|
+
const readImageDimensions = async (source) => {
|
|
238
|
+
if (typeof createImageBitmap !== `function`) {
|
|
239
|
+
throw new Error(`Page spread XHTML generation requires createImageBitmap`);
|
|
240
|
+
}
|
|
241
|
+
const bitmap = await createImageBitmap(source);
|
|
242
|
+
try {
|
|
243
|
+
return {
|
|
244
|
+
height: bitmap.height,
|
|
245
|
+
width: bitmap.width
|
|
246
|
+
};
|
|
247
|
+
} finally {
|
|
248
|
+
bitmap.close();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const createPageSpreadSplitXhtml = ({
|
|
252
|
+
cropSide,
|
|
253
|
+
imageDimensions,
|
|
254
|
+
originalUri
|
|
255
|
+
}) => {
|
|
256
|
+
if (imageDimensions.width < 2) {
|
|
257
|
+
throw new Error(`Page spread image is too narrow to split`);
|
|
258
|
+
}
|
|
259
|
+
const crop = cropRectForSide({
|
|
260
|
+
cropSide,
|
|
261
|
+
imageHeight: imageDimensions.height,
|
|
262
|
+
imageWidth: imageDimensions.width
|
|
263
|
+
});
|
|
264
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
265
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
266
|
+
<head>
|
|
267
|
+
<meta name="viewport" content="width=${crop.width}, height=${crop.height}" />
|
|
268
|
+
<style>
|
|
269
|
+
html,
|
|
270
|
+
body {
|
|
271
|
+
width: ${crop.width}px;
|
|
272
|
+
height: ${crop.height}px;
|
|
273
|
+
margin: 0;
|
|
274
|
+
overflow: hidden;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
img {
|
|
278
|
+
display: block;
|
|
279
|
+
width: ${imageDimensions.width}px;
|
|
280
|
+
height: ${imageDimensions.height}px;
|
|
281
|
+
max-width: none;
|
|
282
|
+
transform: translateX(-${crop.x}px);
|
|
283
|
+
user-select: none;
|
|
284
|
+
-webkit-user-drag: none;
|
|
285
|
+
}
|
|
286
|
+
</style>
|
|
287
|
+
</head>
|
|
288
|
+
<body>
|
|
289
|
+
<img src="${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}" alt="" />
|
|
290
|
+
</body>
|
|
291
|
+
</html>`;
|
|
292
|
+
};
|
|
293
|
+
const generatePageSpreadSplitResource = async ({
|
|
294
|
+
archive,
|
|
295
|
+
resourcePath
|
|
296
|
+
}) => {
|
|
297
|
+
const virtualResource = parseVirtualPageSpreadResourcePath(resourcePath);
|
|
298
|
+
if (virtualResource === void 0) return void 0;
|
|
299
|
+
const file = archive.records.find(
|
|
300
|
+
(file2) => file2.uri === virtualResource.originalUri && !file2.dir
|
|
301
|
+
);
|
|
302
|
+
if (file === void 0 || file.dir) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`no source file found for virtual page spread resourcePath:${resourcePath}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
const archiveCache = imageDimensionsCache.get(archive) ?? /* @__PURE__ */ new Map();
|
|
308
|
+
if (!imageDimensionsCache.has(archive)) {
|
|
309
|
+
imageDimensionsCache.set(archive, archiveCache);
|
|
310
|
+
}
|
|
311
|
+
const imageDimensions = archiveCache.get(virtualResource.originalUri) ?? await readImageDimensions(await file.blob());
|
|
312
|
+
archiveCache.set(virtualResource.originalUri, imageDimensions);
|
|
313
|
+
const body = createPageSpreadSplitXhtml({
|
|
314
|
+
cropSide: virtualResource.cropSide,
|
|
315
|
+
imageDimensions,
|
|
316
|
+
originalUri: virtualResource.originalUri
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
body,
|
|
320
|
+
params: {
|
|
321
|
+
contentType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
const pageSpreadSplitResourceHook = ({ archive, resourcePath }) => async (resource) => {
|
|
326
|
+
const pageSpreadResource = await generatePageSpreadSplitResource({
|
|
327
|
+
archive,
|
|
328
|
+
resourcePath
|
|
329
|
+
});
|
|
330
|
+
if (pageSpreadResource === void 0) return resource;
|
|
331
|
+
return {
|
|
332
|
+
...resource,
|
|
333
|
+
...pageSpreadResource,
|
|
334
|
+
params: {
|
|
335
|
+
...resource.params,
|
|
336
|
+
...pageSpreadResource.params
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
const streamerHooks = {
|
|
341
|
+
manifest: {
|
|
342
|
+
spine: [pageSpreadSplit]
|
|
343
|
+
},
|
|
344
|
+
resource: [pageSpreadSplitResourceHook]
|
|
345
|
+
};
|
|
346
|
+
export {
|
|
347
|
+
PAGE_SPREAD_RESOURCE_PREFIX,
|
|
348
|
+
PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,
|
|
349
|
+
buildVirtualPageSpreadResourcePath,
|
|
350
|
+
createPageSpreadSplitXhtml,
|
|
351
|
+
detectPageSpreadFromBasename,
|
|
352
|
+
isPageSpreadSplitSupportedArchiveRecord,
|
|
353
|
+
isPageSpreadSplitSupportedImage,
|
|
354
|
+
pageSpreadSplit,
|
|
355
|
+
pageSpreadSplitResourceHook,
|
|
356
|
+
parseVirtualPageSpreadResourcePath,
|
|
357
|
+
streamerHooks
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.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 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":["file"],"mappings":"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"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@prose-reader/shared")) : typeof define === "function" && define.amd ? define(["exports", "@prose-reader/shared"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["prose-reader-cbz"] = {}, global.shared));
|
|
3
|
+
})(this, (function(exports2, shared) {
|
|
4
|
+
"use strict";
|
|
5
|
+
const MAX_DETECTED_PAGE_NUMBER = 2e3;
|
|
6
|
+
const numberFromPageLabel = (label) => {
|
|
7
|
+
const value = Number.parseInt(label, 10);
|
|
8
|
+
if (!Number.isFinite(value)) return void 0;
|
|
9
|
+
if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return void 0;
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
const detectPageLabelsFromBasename = (basenameWithoutExtension) => {
|
|
13
|
+
const explicitPageRangeMatch = /(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(
|
|
14
|
+
basenameWithoutExtension
|
|
15
|
+
);
|
|
16
|
+
if (explicitPageRangeMatch) return explicitPageRangeMatch;
|
|
17
|
+
return /(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_]\s*(0\d{1,4})(?=$|[^\d])/i.exec(
|
|
18
|
+
basenameWithoutExtension
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
const detectPageSpreadFromBasename = (basename) => {
|
|
22
|
+
const basenameWithoutExtension = basename.replace(/\.[^.]+$/, ``);
|
|
23
|
+
const match = detectPageLabelsFromBasename(basenameWithoutExtension);
|
|
24
|
+
if (!match) return void 0;
|
|
25
|
+
const [, firstPageLabel, secondPageLabel] = match;
|
|
26
|
+
if (firstPageLabel === void 0 || secondPageLabel === void 0) {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
const firstPageNumber = numberFromPageLabel(firstPageLabel);
|
|
30
|
+
const secondPageNumber = numberFromPageLabel(secondPageLabel);
|
|
31
|
+
if (firstPageNumber === void 0 || secondPageNumber === void 0) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
if (secondPageNumber !== firstPageNumber + 1) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
firstPageLabel,
|
|
39
|
+
secondPageLabel
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const PAGE_SPREAD_RESOURCE_PREFIX = `__prose-reader__/page-spread`;
|
|
43
|
+
const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`;
|
|
44
|
+
const supportedImageMediaTypes = /* @__PURE__ */ new Set([
|
|
45
|
+
`image/jpg`,
|
|
46
|
+
`image/jpeg`,
|
|
47
|
+
`image/png`,
|
|
48
|
+
`image/webp`
|
|
49
|
+
]);
|
|
50
|
+
const isPageSpreadSplitSupportedImage = (mimeType) => {
|
|
51
|
+
if (mimeType === void 0) return false;
|
|
52
|
+
return supportedImageMediaTypes.has(mimeType);
|
|
53
|
+
};
|
|
54
|
+
const encodeOriginalUriSegment = (uri) => encodeURIComponent(uri);
|
|
55
|
+
const createManifestResourceHref = ({
|
|
56
|
+
baseUrl = ``,
|
|
57
|
+
resourcePath
|
|
58
|
+
}) => {
|
|
59
|
+
if (!baseUrl && /^https?:\/\//.test(resourcePath)) {
|
|
60
|
+
return encodeURI(resourcePath);
|
|
61
|
+
}
|
|
62
|
+
const hrefBaseUrl = baseUrl ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}` : `file://`;
|
|
63
|
+
return encodeURI(`${hrefBaseUrl}${resourcePath}`);
|
|
64
|
+
};
|
|
65
|
+
const hasOpfExtension = (path) => path.toLowerCase().endsWith(`.opf`);
|
|
66
|
+
const isArchiveEpub = (archive) => archive.records.some(
|
|
67
|
+
(file) => !file.dir && (hasOpfExtension(file.basename) || hasOpfExtension(file.uri))
|
|
68
|
+
);
|
|
69
|
+
const buildVirtualPageSpreadResourcePath = ({
|
|
70
|
+
cropSide,
|
|
71
|
+
originalUri
|
|
72
|
+
}) => {
|
|
73
|
+
return `${PAGE_SPREAD_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`;
|
|
74
|
+
};
|
|
75
|
+
const spreadPropertiesForSide = (side) => side === `left` ? { pageSpreadLeft: true, pageSpreadRight: void 0 } : { pageSpreadLeft: void 0, pageSpreadRight: true };
|
|
76
|
+
const cropSidesInReadingOrder = (readingDirection) => readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`];
|
|
77
|
+
const createVirtualSpineItem = ({
|
|
78
|
+
baseUrl,
|
|
79
|
+
cropSide,
|
|
80
|
+
label,
|
|
81
|
+
originalSpineItem,
|
|
82
|
+
originalUri,
|
|
83
|
+
progressionWeight
|
|
84
|
+
}) => {
|
|
85
|
+
const resourcePath = buildVirtualPageSpreadResourcePath({
|
|
86
|
+
cropSide,
|
|
87
|
+
originalUri
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
...originalSpineItem,
|
|
91
|
+
id: `${originalSpineItem.id}.${label}`,
|
|
92
|
+
href: createManifestResourceHref({ baseUrl, resourcePath }),
|
|
93
|
+
mediaType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE,
|
|
94
|
+
progressionWeight,
|
|
95
|
+
renditionLayout: `pre-paginated`,
|
|
96
|
+
...spreadPropertiesForSide(cropSide)
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
const createVirtualManifestItem = ({
|
|
100
|
+
href,
|
|
101
|
+
id,
|
|
102
|
+
mediaType
|
|
103
|
+
}) => ({
|
|
104
|
+
href,
|
|
105
|
+
id,
|
|
106
|
+
mediaType
|
|
107
|
+
});
|
|
108
|
+
const getArchiveRecordForManifestItem = ({
|
|
109
|
+
archive,
|
|
110
|
+
baseUrl,
|
|
111
|
+
spineItem
|
|
112
|
+
}) => {
|
|
113
|
+
const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)];
|
|
114
|
+
const resourcePathCandidates = new Set(
|
|
115
|
+
hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl))
|
|
116
|
+
);
|
|
117
|
+
return archive.records.find(
|
|
118
|
+
(item) => !item.dir && resourcePathCandidates.has(item.uri)
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
const decodeManifestHref = (href) => {
|
|
122
|
+
try {
|
|
123
|
+
return decodeURI(href);
|
|
124
|
+
} catch {
|
|
125
|
+
return href;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const normalizeBaseUrl = (baseUrl) => baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`;
|
|
129
|
+
const getResourcePathCandidates = (href, baseUrl) => {
|
|
130
|
+
const candidates = [href];
|
|
131
|
+
if (href.startsWith(`file://`)) {
|
|
132
|
+
candidates.push(href.slice(`file://`.length));
|
|
133
|
+
}
|
|
134
|
+
if (baseUrl) {
|
|
135
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
136
|
+
if (href.startsWith(normalizedBaseUrl)) {
|
|
137
|
+
candidates.push(href.slice(normalizedBaseUrl.length));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return candidates;
|
|
141
|
+
};
|
|
142
|
+
const mediaTypeFromArchiveRecord = (record) => shared.parseContentType(record?.encodingFormat ?? ``) || shared.detectMimeTypeFromName(record?.basename ?? ``);
|
|
143
|
+
const mediaTypeFromArchiveRecordResourcePath = (record) => shared.detectMimeTypeFromName(record.uri) || shared.detectMimeTypeFromName(record.basename);
|
|
144
|
+
const isPageSpreadSplitSupportedArchiveRecord = (record) => {
|
|
145
|
+
if (record === void 0 || record.dir) return false;
|
|
146
|
+
const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record);
|
|
147
|
+
if (!isPageSpreadSplitSupportedImage(resourcePathMediaType)) return false;
|
|
148
|
+
return isPageSpreadSplitSupportedImage(mediaTypeFromArchiveRecord(record));
|
|
149
|
+
};
|
|
150
|
+
const pageSpreadSplit = ({ archive, baseUrl }) => async (manifest) => {
|
|
151
|
+
if (isArchiveEpub(archive)) return manifest;
|
|
152
|
+
const virtualManifestItems = [];
|
|
153
|
+
const spineItems = manifest.spineItems.flatMap((spineItem) => {
|
|
154
|
+
const archiveRecord = getArchiveRecordForManifestItem({
|
|
155
|
+
archive,
|
|
156
|
+
baseUrl,
|
|
157
|
+
spineItem
|
|
158
|
+
});
|
|
159
|
+
if (!isPageSpreadSplitSupportedArchiveRecord(archiveRecord)) {
|
|
160
|
+
return [spineItem];
|
|
161
|
+
}
|
|
162
|
+
const detected = detectPageSpreadFromBasename(archiveRecord.basename);
|
|
163
|
+
if (detected === void 0) return [spineItem];
|
|
164
|
+
const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(
|
|
165
|
+
manifest.readingDirection
|
|
166
|
+
);
|
|
167
|
+
const splitProgressionWeight = spineItem.progressionWeight !== void 0 ? spineItem.progressionWeight / 2 : void 0;
|
|
168
|
+
const firstSpineItem = createVirtualSpineItem({
|
|
169
|
+
baseUrl,
|
|
170
|
+
cropSide: firstCropSide,
|
|
171
|
+
label: detected.firstPageLabel,
|
|
172
|
+
originalSpineItem: spineItem,
|
|
173
|
+
originalUri: archiveRecord.uri,
|
|
174
|
+
progressionWeight: splitProgressionWeight
|
|
175
|
+
});
|
|
176
|
+
const secondSpineItem = createVirtualSpineItem({
|
|
177
|
+
baseUrl,
|
|
178
|
+
cropSide: secondCropSide,
|
|
179
|
+
label: detected.secondPageLabel,
|
|
180
|
+
originalSpineItem: spineItem,
|
|
181
|
+
originalUri: archiveRecord.uri,
|
|
182
|
+
progressionWeight: splitProgressionWeight
|
|
183
|
+
});
|
|
184
|
+
virtualManifestItems.push(
|
|
185
|
+
createVirtualManifestItem(firstSpineItem),
|
|
186
|
+
createVirtualManifestItem(secondSpineItem)
|
|
187
|
+
);
|
|
188
|
+
return [firstSpineItem, secondSpineItem];
|
|
189
|
+
});
|
|
190
|
+
if (virtualManifestItems.length === 0) return manifest;
|
|
191
|
+
return {
|
|
192
|
+
...manifest,
|
|
193
|
+
spineItems: spineItems.map((spineItem, index) => ({
|
|
194
|
+
...spineItem,
|
|
195
|
+
index
|
|
196
|
+
})),
|
|
197
|
+
items: [...manifest.items, ...virtualManifestItems]
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
const imageDimensionsCache = /* @__PURE__ */ new WeakMap();
|
|
201
|
+
const decodeOriginalUriSegment = (encoded) => {
|
|
202
|
+
try {
|
|
203
|
+
return decodeURIComponent(encoded);
|
|
204
|
+
} catch {
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const parseVirtualPageSpreadResourcePath = (resourcePath) => {
|
|
209
|
+
const prefixIndex = resourcePath.indexOf(`${PAGE_SPREAD_RESOURCE_PREFIX}/`);
|
|
210
|
+
if (prefixIndex < 0) return void 0;
|
|
211
|
+
const virtualPath = resourcePath.slice(prefixIndex);
|
|
212
|
+
const parts = virtualPath.split(`/`);
|
|
213
|
+
const encodedOriginalUri = parts[2];
|
|
214
|
+
const cropFileName = parts[3];
|
|
215
|
+
if (parts.length !== 4 || parts[0] !== `__prose-reader__` || parts[1] !== `page-spread` || encodedOriginalUri === void 0 || cropFileName === void 0) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
const cropSide = cropFileName.split(`.`)[0];
|
|
219
|
+
if (cropSide !== `left` && cropSide !== `right`) return void 0;
|
|
220
|
+
const originalUri = decodeOriginalUriSegment(encodedOriginalUri);
|
|
221
|
+
if (originalUri === void 0) return void 0;
|
|
222
|
+
return {
|
|
223
|
+
originalUri,
|
|
224
|
+
cropSide
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
const cropRectForSide = ({
|
|
228
|
+
cropSide,
|
|
229
|
+
imageHeight,
|
|
230
|
+
imageWidth
|
|
231
|
+
}) => {
|
|
232
|
+
const leftWidth = Math.floor(imageWidth / 2);
|
|
233
|
+
const rightWidth = imageWidth - leftWidth;
|
|
234
|
+
return cropSide === `left` ? { x: 0, width: leftWidth, height: imageHeight } : { x: leftWidth, width: rightWidth, height: imageHeight };
|
|
235
|
+
};
|
|
236
|
+
const getRelativeOriginalImageSrc = (originalUri) => {
|
|
237
|
+
if (/^https?:\/\//.test(originalUri)) return originalUri;
|
|
238
|
+
return `../../../${encodeURI(originalUri)}`;
|
|
239
|
+
};
|
|
240
|
+
const readImageDimensions = async (source) => {
|
|
241
|
+
if (typeof createImageBitmap !== `function`) {
|
|
242
|
+
throw new Error(`Page spread XHTML generation requires createImageBitmap`);
|
|
243
|
+
}
|
|
244
|
+
const bitmap = await createImageBitmap(source);
|
|
245
|
+
try {
|
|
246
|
+
return {
|
|
247
|
+
height: bitmap.height,
|
|
248
|
+
width: bitmap.width
|
|
249
|
+
};
|
|
250
|
+
} finally {
|
|
251
|
+
bitmap.close();
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const createPageSpreadSplitXhtml = ({
|
|
255
|
+
cropSide,
|
|
256
|
+
imageDimensions,
|
|
257
|
+
originalUri
|
|
258
|
+
}) => {
|
|
259
|
+
if (imageDimensions.width < 2) {
|
|
260
|
+
throw new Error(`Page spread image is too narrow to split`);
|
|
261
|
+
}
|
|
262
|
+
const crop = cropRectForSide({
|
|
263
|
+
cropSide,
|
|
264
|
+
imageHeight: imageDimensions.height,
|
|
265
|
+
imageWidth: imageDimensions.width
|
|
266
|
+
});
|
|
267
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
268
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
269
|
+
<head>
|
|
270
|
+
<meta name="viewport" content="width=${crop.width}, height=${crop.height}" />
|
|
271
|
+
<style>
|
|
272
|
+
html,
|
|
273
|
+
body {
|
|
274
|
+
width: ${crop.width}px;
|
|
275
|
+
height: ${crop.height}px;
|
|
276
|
+
margin: 0;
|
|
277
|
+
overflow: hidden;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
img {
|
|
281
|
+
display: block;
|
|
282
|
+
width: ${imageDimensions.width}px;
|
|
283
|
+
height: ${imageDimensions.height}px;
|
|
284
|
+
max-width: none;
|
|
285
|
+
transform: translateX(-${crop.x}px);
|
|
286
|
+
user-select: none;
|
|
287
|
+
-webkit-user-drag: none;
|
|
288
|
+
}
|
|
289
|
+
</style>
|
|
290
|
+
</head>
|
|
291
|
+
<body>
|
|
292
|
+
<img src="${shared.escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}" alt="" />
|
|
293
|
+
</body>
|
|
294
|
+
</html>`;
|
|
295
|
+
};
|
|
296
|
+
const generatePageSpreadSplitResource = async ({
|
|
297
|
+
archive,
|
|
298
|
+
resourcePath
|
|
299
|
+
}) => {
|
|
300
|
+
const virtualResource = parseVirtualPageSpreadResourcePath(resourcePath);
|
|
301
|
+
if (virtualResource === void 0) return void 0;
|
|
302
|
+
const file = archive.records.find(
|
|
303
|
+
(file2) => file2.uri === virtualResource.originalUri && !file2.dir
|
|
304
|
+
);
|
|
305
|
+
if (file === void 0 || file.dir) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`no source file found for virtual page spread resourcePath:${resourcePath}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const archiveCache = imageDimensionsCache.get(archive) ?? /* @__PURE__ */ new Map();
|
|
311
|
+
if (!imageDimensionsCache.has(archive)) {
|
|
312
|
+
imageDimensionsCache.set(archive, archiveCache);
|
|
313
|
+
}
|
|
314
|
+
const imageDimensions = archiveCache.get(virtualResource.originalUri) ?? await readImageDimensions(await file.blob());
|
|
315
|
+
archiveCache.set(virtualResource.originalUri, imageDimensions);
|
|
316
|
+
const body = createPageSpreadSplitXhtml({
|
|
317
|
+
cropSide: virtualResource.cropSide,
|
|
318
|
+
imageDimensions,
|
|
319
|
+
originalUri: virtualResource.originalUri
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
body,
|
|
323
|
+
params: {
|
|
324
|
+
contentType: PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
const pageSpreadSplitResourceHook = ({ archive, resourcePath }) => async (resource) => {
|
|
329
|
+
const pageSpreadResource = await generatePageSpreadSplitResource({
|
|
330
|
+
archive,
|
|
331
|
+
resourcePath
|
|
332
|
+
});
|
|
333
|
+
if (pageSpreadResource === void 0) return resource;
|
|
334
|
+
return {
|
|
335
|
+
...resource,
|
|
336
|
+
...pageSpreadResource,
|
|
337
|
+
params: {
|
|
338
|
+
...resource.params,
|
|
339
|
+
...pageSpreadResource.params
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
const streamerHooks = {
|
|
344
|
+
manifest: {
|
|
345
|
+
spine: [pageSpreadSplit]
|
|
346
|
+
},
|
|
347
|
+
resource: [pageSpreadSplitResourceHook]
|
|
348
|
+
};
|
|
349
|
+
exports2.PAGE_SPREAD_RESOURCE_PREFIX = PAGE_SPREAD_RESOURCE_PREFIX;
|
|
350
|
+
exports2.PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE;
|
|
351
|
+
exports2.buildVirtualPageSpreadResourcePath = buildVirtualPageSpreadResourcePath;
|
|
352
|
+
exports2.createPageSpreadSplitXhtml = createPageSpreadSplitXhtml;
|
|
353
|
+
exports2.detectPageSpreadFromBasename = detectPageSpreadFromBasename;
|
|
354
|
+
exports2.isPageSpreadSplitSupportedArchiveRecord = isPageSpreadSplitSupportedArchiveRecord;
|
|
355
|
+
exports2.isPageSpreadSplitSupportedImage = isPageSpreadSplitSupportedImage;
|
|
356
|
+
exports2.pageSpreadSplit = pageSpreadSplit;
|
|
357
|
+
exports2.pageSpreadSplitResourceHook = pageSpreadSplitResourceHook;
|
|
358
|
+
exports2.parseVirtualPageSpreadResourcePath = parseVirtualPageSpreadResourcePath;
|
|
359
|
+
exports2.streamerHooks = streamerHooks;
|
|
360
|
+
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
361
|
+
}));
|
|
362
|
+
//# sourceMappingURL=index.umd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.cjs","sources":["../src/detectPageSpreadFromBasename.ts","../src/pageSpreadSplitManifest.ts","../src/pageSpreadSplitResource.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 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":["parseContentType","detectMimeTypeFromName","escapeXmlAttributeValue","file"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Manifest } from '@prose-reader/shared';
|
|
2
|
+
import { Archive } from '@prose-reader/streamer';
|
|
3
|
+
export { type DetectedPageSpread, detectPageSpreadFromBasename, } from './detectPageSpreadFromBasename';
|
|
4
|
+
export type PageSpreadCropSide = "left" | "right";
|
|
5
|
+
export type VirtualPageSpreadResource = {
|
|
6
|
+
originalUri: string;
|
|
7
|
+
cropSide: PageSpreadCropSide;
|
|
8
|
+
};
|
|
9
|
+
export declare const PAGE_SPREAD_RESOURCE_PREFIX = "__prose-reader__/page-spread";
|
|
10
|
+
export declare const PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE = "application/xhtml+xml";
|
|
11
|
+
export declare const isPageSpreadSplitSupportedImage: (mimeType: string | undefined) => boolean;
|
|
12
|
+
type ArchiveRecord = Archive["records"][number];
|
|
13
|
+
type ArchiveFileRecord = Extract<ArchiveRecord, {
|
|
14
|
+
dir: false;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const buildVirtualPageSpreadResourcePath: ({ cropSide, originalUri, }: {
|
|
17
|
+
originalUri: string;
|
|
18
|
+
cropSide: PageSpreadCropSide;
|
|
19
|
+
}) => string;
|
|
20
|
+
export declare const getArchiveRecordForManifestItem: ({ archive, baseUrl, spineItem, }: {
|
|
21
|
+
archive: Archive;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
spineItem: Manifest["spineItems"][number];
|
|
24
|
+
}) => ArchiveRecord | undefined;
|
|
25
|
+
export declare const mediaTypeFromArchiveRecord: (record: {
|
|
26
|
+
basename: string;
|
|
27
|
+
encodingFormat?: string;
|
|
28
|
+
} | undefined) => string | undefined;
|
|
29
|
+
export declare const isPageSpreadSplitSupportedArchiveRecord: (record: ArchiveRecord | undefined) => record is ArchiveFileRecord;
|
|
30
|
+
export declare const pageSpreadSplit: ({ archive, baseUrl }: {
|
|
31
|
+
archive: Archive;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
}) => (manifest: Manifest) => Promise<Manifest>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Archive, HookResource } from '@prose-reader/streamer';
|
|
2
|
+
import { PageSpreadCropSide, VirtualPageSpreadResource } from './pageSpreadSplitManifest';
|
|
3
|
+
type ImageDimensions = {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const parseVirtualPageSpreadResourcePath: (resourcePath: string) => VirtualPageSpreadResource | undefined;
|
|
8
|
+
export declare const createPageSpreadSplitXhtml: ({ cropSide, imageDimensions, originalUri, }: {
|
|
9
|
+
cropSide: PageSpreadCropSide;
|
|
10
|
+
imageDimensions: ImageDimensions;
|
|
11
|
+
originalUri: string;
|
|
12
|
+
}) => string;
|
|
13
|
+
export declare const pageSpreadSplitResourceHook: ({ archive, resourcePath }: {
|
|
14
|
+
archive: Archive;
|
|
15
|
+
resourcePath: string;
|
|
16
|
+
}) => (resource: HookResource) => Promise<HookResource>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StreamerManifestHookFactory, StreamerResourceHookFactory } from '@prose-reader/streamer';
|
|
2
|
+
export declare const streamerHooks: {
|
|
3
|
+
manifest: {
|
|
4
|
+
spine: StreamerManifestHookFactory[];
|
|
5
|
+
};
|
|
6
|
+
resource: StreamerResourceHookFactory[];
|
|
7
|
+
};
|
|
8
|
+
export { buildVirtualPageSpreadResourcePath, detectPageSpreadFromBasename, isPageSpreadSplitSupportedArchiveRecord, isPageSpreadSplitSupportedImage, PAGE_SPREAD_RESOURCE_PREFIX, PAGE_SPREAD_SPLIT_DOCUMENT_MEDIA_TYPE, type PageSpreadCropSide, pageSpreadSplit, type VirtualPageSpreadResource, } from './pageSpreadSplitManifest';
|
|
9
|
+
export { createPageSpreadSplitXhtml, pageSpreadSplitResourceHook, parseVirtualPageSpreadResourcePath, } from './pageSpreadSplitResource';
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prose-reader/cbz",
|
|
3
|
+
"version": "1.296.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"/dist"
|
|
7
|
+
],
|
|
8
|
+
"main": "./dist/index.umd.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"registry": "https://registry.npmjs.org/"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.umd.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "vite",
|
|
23
|
+
"start": "vite build --watch --mode development",
|
|
24
|
+
"build": "tsc && vite build",
|
|
25
|
+
"preview": "vite preview",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"jsdom": "^27.0.0",
|
|
30
|
+
"typescript": "*",
|
|
31
|
+
"vite": "^7"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@prose-reader/shared": "^1.296.0",
|
|
35
|
+
"@prose-reader/streamer": "^1.296.0"
|
|
36
|
+
}
|
|
37
|
+
}
|