@prose-reader/cbz 1.319.0 → 1.321.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.cjs +29 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +43 -46
- package/dist/index.js.map +1 -1
- package/dist/isCbzArchive.d.ts +1 -1
- package/dist/panoramaSplitManifest.d.ts +1 -1
- package/dist/panoramaSplitResource.d.ts +2 -1
- package/package.json +6 -5
- package/dist/index.umd.cjs +0 -29
- package/dist/index.umd.cjs.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/cfi"),t=require("@prose-reader/shared"),n=require("@prose-reader/archive-reader"),r=require("@prose-reader/streamer");var i=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},a=({index:e,readingDirection:t})=>{let n=e%2==0;return t===`rtl`?n?`right`:`left`:n?`left`:`right`},o=e=>e===`rtl`?`left`:`right`,s=({firstItem:e,secondItem:n})=>{if(e===void 0||n===void 0)return!1;let r=(0,t.getItemSpreadPosition)(e),i=(0,t.getItemSpreadPosition)(n);return r!==void 0&&i!==void 0&&r!==i},c=({firstItem:e,firstItemIndex:n,readingDirection:r,secondItem:i})=>(0,t.getItemSpreadPosition)(e)===a({index:n,readingDirection:r})&&(0,t.getItemSpreadPosition)(i)===a({index:n+1,readingDirection:r}),l=({readingDirection:e,spineItems:n})=>{let r=n[0];return r===void 0||(0,t.getItemSpreadPosition)(r)!==void 0||n.findIndex((t,r)=>{let i=n[r+1];return!s({firstItem:t,secondItem:i})||i===void 0?!1:!c({firstItem:t,firstItemIndex:r,readingDirection:e,secondItem:i})})<=0?n:[{...r,...i(o(e))},...n.slice(1)]},u=2e3,d=e=>{let t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>u))return t},f=e=>/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e)||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e),p=e=>{let t=f(e.replace(/\.[^.]+$/,``));if(!t)return;let[,n,r]=t;if(n===void 0||r===void 0)return;let i=d(n),a=d(r);if(!(i===void 0||a===void 0)&&a===i+1)return{firstPageLabel:n,secondPageLabel:r}},m=`__prose-reader__/panorama`,h=`application/xhtml+xml`,g=new Set([`image/jpg`,`image/jpeg`,`image/png`,`image/webp`]),_=e=>e!==void 0&&g.has(e),v=e=>encodeURIComponent(e),y=({cropSide:e,originalUri:t})=>`${m}/${v(t)}/${e}.xhtml`,b=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},x=e=>e===`rtl`?[`right`,`left`]:[`left`,`right`],S=({baseUrl:e,cropSide:n,label:i,originalSpineItem:a,originalUri:o,progressionWeight:s})=>{let c=y({cropSide:n,originalUri:o});return{...a,id:(0,t.createXmlSafeId)(`${a.id}.${i}`),href:(0,r.createManifestResourceHref)({baseUrl:e,resourcePath:c}),mediaType:h,progressionWeight:s,renditionLayout:`pre-paginated`,...b(n)}},C=({href:e,id:t,mediaType:n})=>({href:e,id:t,mediaType:n}),w=({archive:e,baseUrl:t,spineItem:n})=>{let r=[n.href,T(n.href)],i=new Set(r.flatMap(e=>D(e,t)));return e.records.find(e=>!e.dir&&i.has(e.uri))},T=e=>{try{return decodeURI(e)}catch{return e}},E=e=>e.endsWith(`/`)?e:`${e}/`,D=(e,t)=>{let n=[e];if(e.startsWith(`file://`)&&n.push(e.slice(7)),t){let r=E(t);e.startsWith(r)&&n.push(e.slice(r.length))}return n},O=e=>(0,t.parseContentType)(e?.encodingFormat??``)||(0,t.detectMimeTypeFromName)(e?.basename??``),k=e=>(0,t.detectMimeTypeFromName)(e.uri)||(0,t.detectMimeTypeFromName)(e.basename),A=e=>e===void 0||e.dir||!_(k(e))?!1:_(O(e)),j=({archive:e,baseUrl:t})=>async r=>{if((0,n.isArchiveEpub)(e))return r;let i=[],a=r.spineItems.flatMap(n=>{let a=w({archive:e,baseUrl:t,spineItem:n});if(!A(a))return[n];let o=p(a.basename);if(o===void 0)return[n];let[s,c]=x(r.readingDirection),l=n.progressionWeight===void 0?void 0:n.progressionWeight/2,u=S({baseUrl:t,cropSide:s,label:o.firstPageLabel,originalSpineItem:n,originalUri:a.uri,progressionWeight:l}),d=S({baseUrl:t,cropSide:c,label:o.secondPageLabel,originalSpineItem:n,originalUri:a.uri,progressionWeight:l});return i.push(C(u),C(d)),[u,d]}),o=l({readingDirection:r.readingDirection,spineItems:a});return i.length===0&&o===a?r:{...r,spineItems:o.map((e,t)=>({...e,index:t})),items:[...r.items,...i]}},M=new WeakMap,N=e=>{try{return decodeURIComponent(e)}catch{return}},P=e=>{let t=e.indexOf(`${m}/`);if(t<0)return;let n=e.slice(t).split(`/`),r=n[2],i=n[3];if(n.length!==4||n[0]!==`__prose-reader__`||n[1]!==`panorama`||r===void 0||i===void 0)return;let a=i.split(`.`)[0];if(a!==`left`&&a!==`right`)return;let o=N(r);if(o!==void 0)return{originalUri:o,cropSide:a}},F=({cropSide:e,imageHeight:t,imageWidth:n})=>{let r=Math.floor(n/2),i=n-r;return e===`left`?{x:0,width:r,height:t}:{x:r,width:i,height:t}},I=e=>/^https?:\/\//.test(e)?e:`../../../${encodeURI(e)}`,L=async e=>{if(typeof createImageBitmap!=`function`)throw Error(`Panorama XHTML generation requires createImageBitmap`);let t=await createImageBitmap(e);try{return{height:t.height,width:t.width}}finally{t.close()}},R=({cropSide:e,imageDimensions:n,originalUri:r})=>{if(n.width<2)throw Error(`Panorama image is too narrow to split`);let i=F({cropSide:e,imageHeight:n.height,imageWidth:n.width});return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=${i.width}, height=${i.height}" />
|
|
5
|
+
<style>
|
|
6
|
+
html,
|
|
7
|
+
body {
|
|
8
|
+
width: ${i.width}px;
|
|
9
|
+
height: ${i.height}px;
|
|
10
|
+
margin: 0;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
img {
|
|
15
|
+
display: block;
|
|
16
|
+
width: ${n.width}px;
|
|
17
|
+
height: ${n.height}px;
|
|
18
|
+
max-width: none;
|
|
19
|
+
transform: translateX(-${i.x}px);
|
|
20
|
+
user-select: none;
|
|
21
|
+
-webkit-user-drag: none;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<img src="${(0,t.escapeXmlAttributeValue)(I(r))}" alt="" />
|
|
27
|
+
</body>
|
|
28
|
+
</html>`},z=async({archive:e,resourcePath:t})=>{let n=P(t);if(n===void 0)return;let r=e.records.find(e=>e.uri===n.originalUri&&!e.dir);if(r===void 0||r.dir)throw Error(`no source file found for virtual panorama resourcePath:${t}`);let i=M.get(e)??new Map;M.has(e)||M.set(e,i);let a=i.get(n.originalUri)??await L(await r.blob());return i.set(n.originalUri,a),{body:R({cropSide:n.cropSide,imageDimensions:a,originalUri:n.originalUri}),params:{contentType:h}}},B=({archive:e,resourcePath:t})=>async n=>{let r=await z({archive:e,resourcePath:t});return r===void 0?n:{...n,...r,params:{...n.params,...r.params}}},V=`vnd.prose-reader.cbz.virtual-spine-id`,H=e=>{try{return decodeURIComponent(e)}catch{return e}},U=e=>{try{return decodeURI(e)}catch{return e}},W=e=>{let t=P(e)??P(U(e));if(t)return{...t,originalUri:H(t.originalUri)}},G=e=>W(e.item.href)!==void 0,K=(e,t)=>{let n=new Set,r=0;for(let i of e.spineItemsManager.items){let e=W(i.item.href);if(!e){r++;continue}if(e.originalUri===t)return r;n.has(e.originalUri)||(n.add(e.originalUri),r++)}},q=(n,r,i)=>{let a=W(i.href);if(!a)return;let o=K(n,a.originalUri);if(o!==void 0)return(0,e.updateEpubCfiSpineItemref)(r,{extensions:{[V]:encodeURIComponent(i.id)},spineId:(0,t.createXmlSafeId)(a.originalUri),spineIndex:o})},J=(t,n)=>{let r=(0,e.getEpubCfiSpineItemref)(n)?.extensions?.[V];if(!r)return;let i=t.spineItemsManager.get(H(r));if(!(!i||!W(i.item.href)))return(0,e.updateEpubCfiSpineItemref)(n,{extensions:{[V]:void 0},spineId:i.item.id,spineIndex:i.item.index})},Y=e=>t=>{let n=e(t),r=n.hookManager.register(`cfi.afterGenerate`,({cfi:e,spineItem:t})=>q(n,e,t)),i=n.hookManager.register(`cfi.beforeResolve`,({cfi:e})=>J(n,e)),a=()=>{r(),i(),n.destroy()};return{...n,__PROSE_READER_ENHANCER_CBZ:!0,cbz:{isPanoramaSpineItem:G},destroy:a}},X=new Set([`application/vnd.comicbook+zip`,`application/x-cbz`]),Z=e=>e.toLowerCase().endsWith(`.cbz`),Q=e=>{let n=(0,t.parseContentType)(e.encodingFormat??``);return!!(n&&X.has(n)||Z(e.filename??``))},$=({archive:e})=>t=>t.readingDirection!==void 0||!Q(e)?t:{...t,readingDirection:`rtl`},ee={manifest:{content:[$],spine:[j]},resource:[B]};exports.CBZ_MIME_TYPES=X,exports.PANORAMA_RESOURCE_PREFIX=m,exports.PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE=h,exports.buildVirtualPanoramaResourcePath=y,exports.cbzEnhancer=Y,exports.createPanoramaSplitXhtml=R,exports.detectPanoramaFromBasename=p,exports.detectReadingDirectionManifest=$,exports.isCbzArchive=Q,exports.isPanoramaSplitSupportedArchiveRecord=A,exports.isPanoramaSplitSupportedImage=_,exports.panoramaSplit=j,exports.panoramaSplitResourceHook=B,exports.parseVirtualPanoramaResourcePath=P,exports.streamerHooks=ee;
|
|
29
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import { type Archive, isArchiveEpub } from \"@prose-reader/archive-reader\"\nimport {\n createXmlSafeId,\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { createManifestResourceHref } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/shared\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { parseContentType } from \"@prose-reader/shared\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import { getEpubCfiSpineItemref as e, updateEpubCfiSpineItemref as t } from "@prose-reader/cfi";
|
|
2
|
-
import { createXmlSafeId as n } from "@prose-reader/
|
|
3
|
-
import {
|
|
2
|
+
import { createXmlSafeId as n, detectMimeTypeFromName as r, escapeXmlAttributeValue as i, getItemSpreadPosition as a, parseContentType as o } from "@prose-reader/shared";
|
|
3
|
+
import { isArchiveEpub as s } from "@prose-reader/archive-reader";
|
|
4
|
+
import { createManifestResourceHref as c } from "@prose-reader/streamer";
|
|
4
5
|
//#region src/alignSpineItemsForSpreadParity.ts
|
|
5
|
-
var
|
|
6
|
+
var l = (e) => e === "left" ? {
|
|
6
7
|
pageSpreadLeft: !0,
|
|
7
8
|
pageSpreadRight: void 0
|
|
8
9
|
} : {
|
|
9
10
|
pageSpreadLeft: void 0,
|
|
10
11
|
pageSpreadRight: !0
|
|
11
|
-
},
|
|
12
|
+
}, u = ({ index: e, readingDirection: t }) => {
|
|
12
13
|
let n = e % 2 == 0;
|
|
13
14
|
return t === "rtl" ? n ? "right" : "left" : n ? "left" : "right";
|
|
14
|
-
},
|
|
15
|
+
}, d = (e) => e === "rtl" ? "left" : "right", f = ({ firstItem: e, secondItem: t }) => {
|
|
15
16
|
if (e === void 0 || t === void 0) return !1;
|
|
16
17
|
let n = a(e), r = a(t);
|
|
17
18
|
return n !== void 0 && r !== void 0 && n !== r;
|
|
18
|
-
},
|
|
19
|
+
}, p = ({ firstItem: e, firstItemIndex: t, readingDirection: n, secondItem: r }) => a(e) === u({
|
|
19
20
|
index: t,
|
|
20
21
|
readingDirection: n
|
|
21
|
-
}) && a(r) ===
|
|
22
|
+
}) && a(r) === u({
|
|
22
23
|
index: t + 1,
|
|
23
24
|
readingDirection: n
|
|
24
|
-
}),
|
|
25
|
+
}), m = ({ readingDirection: e, spineItems: t }) => {
|
|
25
26
|
let n = t[0];
|
|
26
27
|
return n === void 0 || a(n) !== void 0 || t.findIndex((n, r) => {
|
|
27
28
|
let i = t[r + 1];
|
|
28
|
-
return !
|
|
29
|
+
return !f({
|
|
29
30
|
firstItem: n,
|
|
30
31
|
secondItem: i
|
|
31
|
-
}) || i === void 0 ? !1 : !
|
|
32
|
+
}) || i === void 0 ? !1 : !p({
|
|
32
33
|
firstItem: n,
|
|
33
34
|
firstItemIndex: r,
|
|
34
35
|
readingDirection: e,
|
|
@@ -36,58 +37,54 @@ var s = (e) => e === "left" ? {
|
|
|
36
37
|
});
|
|
37
38
|
}) <= 0 ? t : [{
|
|
38
39
|
...n,
|
|
39
|
-
...
|
|
40
|
+
...l(d(e))
|
|
40
41
|
}, ...t.slice(1)];
|
|
41
|
-
},
|
|
42
|
+
}, h = 2e3, g = (e) => {
|
|
42
43
|
let t = Number.parseInt(e, 10);
|
|
43
|
-
if (Number.isFinite(t) && !(t < 0 || t >
|
|
44
|
-
},
|
|
45
|
-
let t =
|
|
44
|
+
if (Number.isFinite(t) && !(t < 0 || t > h)) return t;
|
|
45
|
+
}, _ = (e) => /(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e) || /(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e), v = (e) => {
|
|
46
|
+
let t = _(e.replace(/\.[^.]+$/, ""));
|
|
46
47
|
if (!t) return;
|
|
47
48
|
let [, n, r] = t;
|
|
48
49
|
if (n === void 0 || r === void 0) return;
|
|
49
|
-
let i =
|
|
50
|
+
let i = g(n), a = g(r);
|
|
50
51
|
if (!(i === void 0 || a === void 0) && a === i + 1) return {
|
|
51
52
|
firstPageLabel: n,
|
|
52
53
|
secondPageLabel: r
|
|
53
54
|
};
|
|
54
|
-
},
|
|
55
|
+
}, y = "__prose-reader__/panorama", b = "application/xhtml+xml", x = /* @__PURE__ */ new Set([
|
|
55
56
|
"image/jpg",
|
|
56
57
|
"image/jpeg",
|
|
57
58
|
"image/png",
|
|
58
59
|
"image/webp"
|
|
59
|
-
]),
|
|
60
|
-
if (!e && /^https?:\/\//.test(t)) return encodeURI(t);
|
|
61
|
-
let n = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
|
|
62
|
-
return encodeURI(`${n}${t}`);
|
|
63
|
-
}, C = (e) => e.toLowerCase().endsWith(".opf"), w = (e) => e.records.some((e) => !e.dir && (C(e.basename) || C(e.uri))), T = ({ cropSide: e, originalUri: t }) => `${_}/${x(t)}/${e}.xhtml`, E = (e) => e === "left" ? {
|
|
60
|
+
]), S = (e) => e !== void 0 && x.has(e), C = (e) => encodeURIComponent(e), w = ({ cropSide: e, originalUri: t }) => `${y}/${C(t)}/${e}.xhtml`, T = (e) => e === "left" ? {
|
|
64
61
|
pageSpreadLeft: !0,
|
|
65
62
|
pageSpreadRight: void 0
|
|
66
63
|
} : {
|
|
67
64
|
pageSpreadLeft: void 0,
|
|
68
65
|
pageSpreadRight: !0
|
|
69
|
-
},
|
|
70
|
-
let s =
|
|
66
|
+
}, E = (e) => e === "rtl" ? ["right", "left"] : ["left", "right"], D = ({ baseUrl: e, cropSide: t, label: r, originalSpineItem: i, originalUri: a, progressionWeight: o }) => {
|
|
67
|
+
let s = w({
|
|
71
68
|
cropSide: t,
|
|
72
69
|
originalUri: a
|
|
73
70
|
});
|
|
74
71
|
return {
|
|
75
72
|
...i,
|
|
76
73
|
id: n(`${i.id}.${r}`),
|
|
77
|
-
href:
|
|
74
|
+
href: c({
|
|
78
75
|
baseUrl: e,
|
|
79
76
|
resourcePath: s
|
|
80
77
|
}),
|
|
81
|
-
mediaType:
|
|
78
|
+
mediaType: b,
|
|
82
79
|
progressionWeight: o,
|
|
83
80
|
renditionLayout: "pre-paginated",
|
|
84
|
-
...
|
|
81
|
+
...T(t)
|
|
85
82
|
};
|
|
86
|
-
},
|
|
83
|
+
}, O = ({ href: e, id: t, mediaType: n }) => ({
|
|
87
84
|
href: e,
|
|
88
85
|
id: t,
|
|
89
86
|
mediaType: n
|
|
90
|
-
}),
|
|
87
|
+
}), k = ({ archive: e, baseUrl: t, spineItem: n }) => {
|
|
91
88
|
let r = [n.href, ee(n.href)], i = new Set(r.flatMap((e) => ne(e, t)));
|
|
92
89
|
return e.records.find((e) => !e.dir && i.has(e.uri));
|
|
93
90
|
}, ee = (e) => {
|
|
@@ -103,25 +100,25 @@ var s = (e) => e === "left" ? {
|
|
|
103
100
|
e.startsWith(r) && n.push(e.slice(r.length));
|
|
104
101
|
}
|
|
105
102
|
return n;
|
|
106
|
-
},
|
|
107
|
-
if (
|
|
103
|
+
}, A = (e) => o(e?.encodingFormat ?? "") || r(e?.basename ?? ""), j = (e) => r(e.uri) || r(e.basename), M = (e) => e === void 0 || e.dir || !S(j(e)) ? !1 : S(A(e)), N = ({ archive: e, baseUrl: t }) => async (n) => {
|
|
104
|
+
if (s(e)) return n;
|
|
108
105
|
let r = [], i = n.spineItems.flatMap((i) => {
|
|
109
|
-
let a =
|
|
106
|
+
let a = k({
|
|
110
107
|
archive: e,
|
|
111
108
|
baseUrl: t,
|
|
112
109
|
spineItem: i
|
|
113
110
|
});
|
|
114
111
|
if (!M(a)) return [i];
|
|
115
|
-
let o =
|
|
112
|
+
let o = v(a.basename);
|
|
116
113
|
if (o === void 0) return [i];
|
|
117
|
-
let [s, c] =
|
|
114
|
+
let [s, c] = E(n.readingDirection), l = i.progressionWeight === void 0 ? void 0 : i.progressionWeight / 2, u = D({
|
|
118
115
|
baseUrl: t,
|
|
119
116
|
cropSide: s,
|
|
120
117
|
label: o.firstPageLabel,
|
|
121
118
|
originalSpineItem: i,
|
|
122
119
|
originalUri: a.uri,
|
|
123
120
|
progressionWeight: l
|
|
124
|
-
}), d =
|
|
121
|
+
}), d = D({
|
|
125
122
|
baseUrl: t,
|
|
126
123
|
cropSide: c,
|
|
127
124
|
label: o.secondPageLabel,
|
|
@@ -129,8 +126,8 @@ var s = (e) => e === "left" ? {
|
|
|
129
126
|
originalUri: a.uri,
|
|
130
127
|
progressionWeight: l
|
|
131
128
|
});
|
|
132
|
-
return r.push(
|
|
133
|
-
}), a =
|
|
129
|
+
return r.push(O(u), O(d)), [u, d];
|
|
130
|
+
}), a = m({
|
|
134
131
|
readingDirection: n.readingDirection,
|
|
135
132
|
spineItems: i
|
|
136
133
|
});
|
|
@@ -149,7 +146,7 @@ var s = (e) => e === "left" ? {
|
|
|
149
146
|
return;
|
|
150
147
|
}
|
|
151
148
|
}, I = (e) => {
|
|
152
|
-
let t = e.indexOf(`${
|
|
149
|
+
let t = e.indexOf(`${y}/`);
|
|
153
150
|
if (t < 0) return;
|
|
154
151
|
let n = e.slice(t).split("/"), r = n[2], i = n[3];
|
|
155
152
|
if (n.length !== 4 || n[0] !== "__prose-reader__" || n[1] !== "panorama" || r === void 0 || i === void 0) return;
|
|
@@ -231,7 +228,7 @@ var s = (e) => e === "left" ? {
|
|
|
231
228
|
imageDimensions: a,
|
|
232
229
|
originalUri: n.originalUri
|
|
233
230
|
}),
|
|
234
|
-
params: { contentType:
|
|
231
|
+
params: { contentType: b }
|
|
235
232
|
};
|
|
236
233
|
}, H = ({ archive: e, resourcePath: t }) => async (n) => {
|
|
237
234
|
let r = await V({
|
|
@@ -284,7 +281,7 @@ var s = (e) => e === "left" ? {
|
|
|
284
281
|
spineId: n(a.originalUri),
|
|
285
282
|
spineIndex: o
|
|
286
283
|
});
|
|
287
|
-
},
|
|
284
|
+
}, X = (n, r) => {
|
|
288
285
|
let i = e(r)?.extensions?.[U];
|
|
289
286
|
if (!i) return;
|
|
290
287
|
let a = n.spineItemsManager.get(W(i));
|
|
@@ -293,8 +290,8 @@ var s = (e) => e === "left" ? {
|
|
|
293
290
|
spineId: a.item.id,
|
|
294
291
|
spineIndex: a.item.index
|
|
295
292
|
});
|
|
296
|
-
},
|
|
297
|
-
let n = e(t), r = n.hookManager.register("cfi.afterGenerate", ({ cfi: e, spineItem: t }) => Y(n, e, t)), i = n.hookManager.register("cfi.beforeResolve", ({ cfi: e }) =>
|
|
293
|
+
}, re = (e) => (t) => {
|
|
294
|
+
let n = e(t), r = n.hookManager.register("cfi.afterGenerate", ({ cfi: e, spineItem: t }) => Y(n, e, t)), i = n.hookManager.register("cfi.beforeResolve", ({ cfi: e }) => X(n, e)), a = () => {
|
|
298
295
|
r(), i(), n.destroy();
|
|
299
296
|
};
|
|
300
297
|
return {
|
|
@@ -303,13 +300,13 @@ var s = (e) => e === "left" ? {
|
|
|
303
300
|
cbz: { isPanoramaSpineItem: q },
|
|
304
301
|
destroy: a
|
|
305
302
|
};
|
|
306
|
-
}, Z = /* @__PURE__ */ new Set(["application/vnd.comicbook+zip", "application/x-cbz"]),
|
|
303
|
+
}, Z = /* @__PURE__ */ new Set(["application/vnd.comicbook+zip", "application/x-cbz"]), ie = (e) => e.toLowerCase().endsWith(".cbz"), Q = (e) => {
|
|
307
304
|
let t = o(e.encodingFormat ?? "");
|
|
308
|
-
return !!(t && Z.has(t) ||
|
|
305
|
+
return !!(t && Z.has(t) || ie(e.filename ?? ""));
|
|
309
306
|
}, $ = ({ archive: e }) => (t) => t.readingDirection !== void 0 || !Q(e) ? t : {
|
|
310
307
|
...t,
|
|
311
308
|
readingDirection: "rtl"
|
|
312
|
-
},
|
|
309
|
+
}, ae = {
|
|
313
310
|
manifest: {
|
|
314
311
|
content: [$],
|
|
315
312
|
spine: [N]
|
|
@@ -317,6 +314,6 @@ var s = (e) => e === "left" ? {
|
|
|
317
314
|
resource: [H]
|
|
318
315
|
};
|
|
319
316
|
//#endregion
|
|
320
|
-
export { Z as CBZ_MIME_TYPES,
|
|
317
|
+
export { Z as CBZ_MIME_TYPES, y as PANORAMA_RESOURCE_PREFIX, b as PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE, w as buildVirtualPanoramaResourcePath, re as cbzEnhancer, B as createPanoramaSplitXhtml, v as detectPanoramaFromBasename, $ as detectReadingDirectionManifest, Q as isCbzArchive, M as isPanoramaSplitSupportedArchiveRecord, S as isPanoramaSplitSupportedImage, N as panoramaSplit, H as panoramaSplitResourceHook, I as parseVirtualPanoramaResourcePath, ae as streamerHooks };
|
|
321
318
|
|
|
322
319
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import { type Archive, isArchiveEpub } from \"@prose-reader/archive-reader\"\nimport {\n createXmlSafeId,\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { createManifestResourceHref } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/shared\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { parseContentType } from \"@prose-reader/shared\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|
package/dist/isCbzArchive.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Archive } from '@prose-reader/archive-reader';
|
|
1
2
|
import { Manifest } from '@prose-reader/shared';
|
|
2
|
-
import { Archive } from '@prose-reader/streamer';
|
|
3
3
|
export { type DetectedPanorama, detectPanoramaFromBasename, } from './detectPanoramaFromBasename';
|
|
4
4
|
export type PanoramaCropSide = "left" | "right";
|
|
5
5
|
export type VirtualPanoramaResource = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Archive
|
|
1
|
+
import { Archive } from '@prose-reader/archive-reader';
|
|
2
|
+
import { HookResource } from '@prose-reader/streamer';
|
|
2
3
|
import { PanoramaCropSide, VirtualPanoramaResource } from './panoramaSplitManifest';
|
|
3
4
|
type ImageDimensions = {
|
|
4
5
|
width: number;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prose-reader/cbz",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.321.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"/dist"
|
|
7
7
|
],
|
|
8
|
-
"main": "./dist/index.
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
9
|
"module": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"publishConfig": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
17
|
"import": "./dist/index.js",
|
|
18
|
-
"require": "./dist/index.
|
|
18
|
+
"require": "./dist/index.cjs"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
"vite": "^8"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
+
"@prose-reader/archive-reader": "^1.320.0",
|
|
34
35
|
"@prose-reader/cfi": "^1.296.0",
|
|
35
36
|
"@prose-reader/core": "^1.296.0",
|
|
36
|
-
"@prose-reader/shared": "^1.
|
|
37
|
+
"@prose-reader/shared": "^1.320.0",
|
|
37
38
|
"@prose-reader/streamer": "^1.296.0"
|
|
38
39
|
},
|
|
39
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "8bfc054a6a40ada59234bf36d4527db17395afcc"
|
|
40
41
|
}
|
package/dist/index.umd.cjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@prose-reader/cfi"),require("@prose-reader/streamer"),require("@prose-reader/shared")):typeof define==`function`&&define.amd?define([`exports`,`@prose-reader/cfi`,`@prose-reader/streamer`,`@prose-reader/shared`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`prose-reader-cbz`]={},e._prose_reader_cfi,e._prose_reader_streamer,e._prose_reader_shared))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var i=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},a=({index:e,readingDirection:t})=>{let n=e%2==0;return t===`rtl`?n?`right`:`left`:n?`left`:`right`},o=e=>e===`rtl`?`left`:`right`,s=({firstItem:e,secondItem:t})=>{if(e===void 0||t===void 0)return!1;let n=(0,r.getItemSpreadPosition)(e),i=(0,r.getItemSpreadPosition)(t);return n!==void 0&&i!==void 0&&n!==i},c=({firstItem:e,firstItemIndex:t,readingDirection:n,secondItem:i})=>(0,r.getItemSpreadPosition)(e)===a({index:t,readingDirection:n})&&(0,r.getItemSpreadPosition)(i)===a({index:t+1,readingDirection:n}),l=({readingDirection:e,spineItems:t})=>{let n=t[0];return n===void 0||(0,r.getItemSpreadPosition)(n)!==void 0||t.findIndex((n,r)=>{let i=t[r+1];return!s({firstItem:n,secondItem:i})||i===void 0?!1:!c({firstItem:n,firstItemIndex:r,readingDirection:e,secondItem:i})})<=0?t:[{...n,...i(o(e))},...t.slice(1)]},u=2e3,d=e=>{let t=Number.parseInt(e,10);if(Number.isFinite(t)&&!(t<0||t>u))return t},f=e=>/(?:^|[\s._(-]|\[)p\s*(\d{1,5})\s*[-_~]\s*(?:p\s*)?(\d{1,5})(?=$|[^\d])/i.exec(e)||/(?:^|[\s._(]|\[)(0\d{1,4})\s*[-_~]\s*(0\d{1,4})(?=$|[^\d])/i.exec(e),p=e=>{let t=f(e.replace(/\.[^.]+$/,``));if(!t)return;let[,n,r]=t;if(n===void 0||r===void 0)return;let i=d(n),a=d(r);if(!(i===void 0||a===void 0)&&a===i+1)return{firstPageLabel:n,secondPageLabel:r}},m=`__prose-reader__/panorama`,h=`application/xhtml+xml`,g=new Set([`image/jpg`,`image/jpeg`,`image/png`,`image/webp`]),_=e=>e!==void 0&&g.has(e),v=e=>encodeURIComponent(e),y=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return encodeURI(`${n}${t}`)},b=e=>e.toLowerCase().endsWith(`.opf`),x=e=>e.records.some(e=>!e.dir&&(b(e.basename)||b(e.uri))),S=({cropSide:e,originalUri:t})=>`${m}/${v(t)}/${e}.xhtml`,C=e=>e===`left`?{pageSpreadLeft:!0,pageSpreadRight:void 0}:{pageSpreadLeft:void 0,pageSpreadRight:!0},w=e=>e===`rtl`?[`right`,`left`]:[`left`,`right`],T=({baseUrl:e,cropSide:t,label:r,originalSpineItem:i,originalUri:a,progressionWeight:o})=>{let s=S({cropSide:t,originalUri:a});return{...i,id:(0,n.createXmlSafeId)(`${i.id}.${r}`),href:y({baseUrl:e,resourcePath:s}),mediaType:h,progressionWeight:o,renditionLayout:`pre-paginated`,...C(t)}},E=({href:e,id:t,mediaType:n})=>({href:e,id:t,mediaType:n}),D=({archive:e,baseUrl:t,spineItem:n})=>{let r=[n.href,O(n.href)],i=new Set(r.flatMap(e=>A(e,t)));return e.records.find(e=>!e.dir&&i.has(e.uri))},O=e=>{try{return decodeURI(e)}catch{return e}},k=e=>e.endsWith(`/`)?e:`${e}/`,A=(e,t)=>{let n=[e];if(e.startsWith(`file://`)&&n.push(e.slice(7)),t){let r=k(t);e.startsWith(r)&&n.push(e.slice(r.length))}return n},ee=e=>(0,r.parseContentType)(e?.encodingFormat??``)||(0,r.detectMimeTypeFromName)(e?.basename??``),te=e=>(0,r.detectMimeTypeFromName)(e.uri)||(0,r.detectMimeTypeFromName)(e.basename),j=e=>e===void 0||e.dir||!_(te(e))?!1:_(ee(e)),M=({archive:e,baseUrl:t})=>async n=>{if(x(e))return n;let r=[],i=n.spineItems.flatMap(i=>{let a=D({archive:e,baseUrl:t,spineItem:i});if(!j(a))return[i];let o=p(a.basename);if(o===void 0)return[i];let[s,c]=w(n.readingDirection),l=i.progressionWeight===void 0?void 0:i.progressionWeight/2,u=T({baseUrl:t,cropSide:s,label:o.firstPageLabel,originalSpineItem:i,originalUri:a.uri,progressionWeight:l}),d=T({baseUrl:t,cropSide:c,label:o.secondPageLabel,originalSpineItem:i,originalUri:a.uri,progressionWeight:l});return r.push(E(u),E(d)),[u,d]}),a=l({readingDirection:n.readingDirection,spineItems:i});return r.length===0&&a===i?n:{...n,spineItems:a.map((e,t)=>({...e,index:t})),items:[...n.items,...r]}},N=new WeakMap,P=e=>{try{return decodeURIComponent(e)}catch{return}},F=e=>{let t=e.indexOf(`${m}/`);if(t<0)return;let n=e.slice(t).split(`/`),r=n[2],i=n[3];if(n.length!==4||n[0]!==`__prose-reader__`||n[1]!==`panorama`||r===void 0||i===void 0)return;let a=i.split(`.`)[0];if(a!==`left`&&a!==`right`)return;let o=P(r);if(o!==void 0)return{originalUri:o,cropSide:a}},I=({cropSide:e,imageHeight:t,imageWidth:n})=>{let r=Math.floor(n/2),i=n-r;return e===`left`?{x:0,width:r,height:t}:{x:r,width:i,height:t}},L=e=>/^https?:\/\//.test(e)?e:`../../../${encodeURI(e)}`,R=async e=>{if(typeof createImageBitmap!=`function`)throw Error(`Panorama XHTML generation requires createImageBitmap`);let t=await createImageBitmap(e);try{return{height:t.height,width:t.width}}finally{t.close()}},z=({cropSide:e,imageDimensions:t,originalUri:n})=>{if(t.width<2)throw Error(`Panorama image is too narrow to split`);let i=I({cropSide:e,imageHeight:t.height,imageWidth:t.width});return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
|
-
<head>
|
|
4
|
-
<meta name="viewport" content="width=${i.width}, height=${i.height}" />
|
|
5
|
-
<style>
|
|
6
|
-
html,
|
|
7
|
-
body {
|
|
8
|
-
width: ${i.width}px;
|
|
9
|
-
height: ${i.height}px;
|
|
10
|
-
margin: 0;
|
|
11
|
-
overflow: hidden;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
img {
|
|
15
|
-
display: block;
|
|
16
|
-
width: ${t.width}px;
|
|
17
|
-
height: ${t.height}px;
|
|
18
|
-
max-width: none;
|
|
19
|
-
transform: translateX(-${i.x}px);
|
|
20
|
-
user-select: none;
|
|
21
|
-
-webkit-user-drag: none;
|
|
22
|
-
}
|
|
23
|
-
</style>
|
|
24
|
-
</head>
|
|
25
|
-
<body>
|
|
26
|
-
<img src="${(0,r.escapeXmlAttributeValue)(L(n))}" alt="" />
|
|
27
|
-
</body>
|
|
28
|
-
</html>`},B=async({archive:e,resourcePath:t})=>{let n=F(t);if(n===void 0)return;let r=e.records.find(e=>e.uri===n.originalUri&&!e.dir);if(r===void 0||r.dir)throw Error(`no source file found for virtual panorama resourcePath:${t}`);let i=N.get(e)??new Map;N.has(e)||N.set(e,i);let a=i.get(n.originalUri)??await R(await r.blob());return i.set(n.originalUri,a),{body:z({cropSide:n.cropSide,imageDimensions:a,originalUri:n.originalUri}),params:{contentType:h}}},V=({archive:e,resourcePath:t})=>async n=>{let r=await B({archive:e,resourcePath:t});return r===void 0?n:{...n,...r,params:{...n.params,...r.params}}},H=`vnd.prose-reader.cbz.virtual-spine-id`,U=e=>{try{return decodeURIComponent(e)}catch{return e}},W=e=>{try{return decodeURI(e)}catch{return e}},G=e=>{let t=F(e)??F(W(e));if(t)return{...t,originalUri:U(t.originalUri)}},K=e=>G(e.item.href)!==void 0,q=(e,t)=>{let n=new Set,r=0;for(let i of e.spineItemsManager.items){let e=G(i.item.href);if(!e){r++;continue}if(e.originalUri===t)return r;n.has(e.originalUri)||(n.add(e.originalUri),r++)}},J=(e,r,i)=>{let a=G(i.href);if(!a)return;let o=q(e,a.originalUri);if(o!==void 0)return(0,t.updateEpubCfiSpineItemref)(r,{extensions:{[H]:encodeURIComponent(i.id)},spineId:(0,n.createXmlSafeId)(a.originalUri),spineIndex:o})},Y=(e,n)=>{let r=(0,t.getEpubCfiSpineItemref)(n)?.extensions?.[H];if(!r)return;let i=e.spineItemsManager.get(U(r));if(!(!i||!G(i.item.href)))return(0,t.updateEpubCfiSpineItemref)(n,{extensions:{[H]:void 0},spineId:i.item.id,spineIndex:i.item.index})},X=e=>t=>{let n=e(t),r=n.hookManager.register(`cfi.afterGenerate`,({cfi:e,spineItem:t})=>J(n,e,t)),i=n.hookManager.register(`cfi.beforeResolve`,({cfi:e})=>Y(n,e)),a=()=>{r(),i(),n.destroy()};return{...n,__PROSE_READER_ENHANCER_CBZ:!0,cbz:{isPanoramaSpineItem:K},destroy:a}},Z=new Set([`application/vnd.comicbook+zip`,`application/x-cbz`]),ne=e=>e.toLowerCase().endsWith(`.cbz`),Q=e=>{let t=(0,r.parseContentType)(e.encodingFormat??``);return!!(t&&Z.has(t)||ne(e.filename??``))},$=({archive:e})=>t=>t.readingDirection!==void 0||!Q(e)?t:{...t,readingDirection:`rtl`},re={manifest:{content:[$],spine:[M]},resource:[V]};e.CBZ_MIME_TYPES=Z,e.PANORAMA_RESOURCE_PREFIX=m,e.PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE=h,e.buildVirtualPanoramaResourcePath=S,e.cbzEnhancer=X,e.createPanoramaSplitXhtml=z,e.detectPanoramaFromBasename=p,e.detectReadingDirectionManifest=$,e.isCbzArchive=Q,e.isPanoramaSplitSupportedArchiveRecord=j,e.isPanoramaSplitSupportedImage=_,e.panoramaSplit=M,e.panoramaSplitResourceHook=V,e.parseVirtualPanoramaResourcePath=F,e.streamerHooks=re});
|
|
29
|
-
//# sourceMappingURL=index.umd.cjs.map
|
package/dist/index.umd.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.cjs","names":[],"sources":["../src/alignSpineItemsForSpreadParity.ts","../src/detectPanoramaFromBasename.ts","../src/panoramaSplitManifest.ts","../src/panoramaSplitResource.ts","../src/enhancer.ts","../src/isCbzArchive.ts","../src/detectReadingDirectionManifest.ts","../src/streamer.ts"],"sourcesContent":["import { getItemSpreadPosition, type Manifest } from \"@prose-reader/shared\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype PageSpreadSide = \"left\" | \"right\"\n\nconst spreadPropertiesForSide = (\n side: PageSpreadSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst naturalSpreadSideAtIndex = ({\n index,\n readingDirection,\n}: {\n index: number\n readingDirection: Manifest[\"readingDirection\"]\n}): PageSpreadSide => {\n const isEvenSlot = index % 2 === 0\n\n return readingDirection === `rtl`\n ? isEvenSlot\n ? `right`\n : `left`\n : isEvenSlot\n ? `left`\n : `right`\n}\n\nconst openingPageSideForParityShift = (\n readingDirection: Manifest[\"readingDirection\"],\n): PageSpreadSide => (readingDirection === `rtl` ? `left` : `right`)\n\nconst isSpreadPair = ({\n firstItem,\n secondItem,\n}: {\n firstItem: SpineItem | undefined\n secondItem: SpineItem | undefined\n}) => {\n if (firstItem === undefined || secondItem === undefined) return false\n\n const firstSide = getItemSpreadPosition(firstItem)\n const secondSide = getItemSpreadPosition(secondItem)\n\n return (\n firstSide !== undefined &&\n secondSide !== undefined &&\n firstSide !== secondSide\n )\n}\n\nconst isSpreadPairNaturallyAligned = ({\n firstItem,\n firstItemIndex,\n readingDirection,\n secondItem,\n}: {\n firstItem: SpineItem\n firstItemIndex: number\n readingDirection: Manifest[\"readingDirection\"]\n secondItem: SpineItem\n}) =>\n getItemSpreadPosition(firstItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex,\n readingDirection,\n }) &&\n getItemSpreadPosition(secondItem) ===\n naturalSpreadSideAtIndex({\n index: firstItemIndex + 1,\n readingDirection,\n })\n\nexport const alignSpineItemsForSpreadParity = ({\n readingDirection,\n spineItems,\n}: {\n readingDirection: Manifest[\"readingDirection\"]\n spineItems: SpineItem[]\n}) => {\n const openingItem = spineItems[0]\n\n if (\n openingItem === undefined ||\n getItemSpreadPosition(openingItem) !== undefined\n ) {\n return spineItems\n }\n\n const misalignedPairIndex = spineItems.findIndex((firstItem, index) => {\n const secondItem = spineItems[index + 1]\n\n if (\n !isSpreadPair({\n firstItem,\n secondItem,\n })\n ) {\n return false\n }\n\n if (secondItem === undefined) return false\n\n return !isSpreadPairNaturallyAligned({\n firstItem,\n firstItemIndex: index,\n readingDirection,\n secondItem,\n })\n })\n\n if (misalignedPairIndex <= 0) return spineItems\n\n return [\n {\n ...openingItem,\n ...spreadPropertiesForSide(\n openingPageSideForParityShift(readingDirection),\n ),\n },\n ...spineItems.slice(1),\n ]\n}\n","/**\n * This detector intentionally stays filename-only and conservative.\n *\n * Future improvements should consider archive sequence context before widening\n * detection. For example, a spread such as `p002-003.jpg` is safer to split\n * when neighboring resources make the sequence plausible, like `p001.jpg` and\n * `p004.jpg`.\n */\nexport type DetectedPanorama = {\n firstPageLabel: string\n secondPageLabel: string\n}\n\nconst MAX_DETECTED_PAGE_NUMBER = 2000\n\nconst numberFromPageLabel = (label: string): number | undefined => {\n const value = Number.parseInt(label, 10)\n\n if (!Number.isFinite(value)) return undefined\n if (value < 0 || value > MAX_DETECTED_PAGE_NUMBER) return undefined\n\n return value\n}\n\nconst detectPageLabelsFromBasename = (basenameWithoutExtension: string) => {\n const explicitPageRangeMatch =\n /(?:^|[\\s._(-]|\\[)p\\s*(\\d{1,5})\\s*[-_~]\\s*(?:p\\s*)?(\\d{1,5})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n\n if (explicitPageRangeMatch) return explicitPageRangeMatch\n\n return /(?:^|[\\s._(]|\\[)(0\\d{1,4})\\s*[-_~]\\s*(0\\d{1,4})(?=$|[^\\d])/i.exec(\n basenameWithoutExtension,\n )\n}\n\nexport const detectPanoramaFromBasename = (\n basename: string,\n): DetectedPanorama | undefined => {\n const basenameWithoutExtension = basename.replace(/\\.[^.]+$/, ``)\n const match = detectPageLabelsFromBasename(basenameWithoutExtension)\n\n if (!match) return undefined\n\n const [, firstPageLabel, secondPageLabel] = match\n\n if (firstPageLabel === undefined || secondPageLabel === undefined) {\n return undefined\n }\n\n const firstPageNumber = numberFromPageLabel(firstPageLabel)\n const secondPageNumber = numberFromPageLabel(secondPageLabel)\n\n if (firstPageNumber === undefined || secondPageNumber === undefined) {\n return undefined\n }\n\n if (secondPageNumber !== firstPageNumber + 1) {\n return undefined\n }\n\n return {\n firstPageLabel,\n secondPageLabel,\n }\n}\n","import {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\nimport { type Archive, createXmlSafeId } from \"@prose-reader/streamer\"\nimport { alignSpineItemsForSpreadParity } from \"./alignSpineItemsForSpreadParity\"\nimport { detectPanoramaFromBasename } from \"./detectPanoramaFromBasename\"\n\nexport {\n type DetectedPanorama,\n detectPanoramaFromBasename,\n} from \"./detectPanoramaFromBasename\"\n\nexport type PanoramaCropSide = \"left\" | \"right\"\n\nexport type VirtualPanoramaResource = {\n originalUri: string\n cropSide: PanoramaCropSide\n}\n\nexport const PANORAMA_RESOURCE_PREFIX = `__prose-reader__/panorama`\nexport const PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE = `application/xhtml+xml`\n\nconst supportedImageMediaTypes = new Set([\n `image/jpg`,\n `image/jpeg`,\n `image/png`,\n `image/webp`,\n])\n\nexport const isPanoramaSplitSupportedImage = (mimeType: string | undefined) => {\n if (mimeType === undefined) return false\n\n return supportedImageMediaTypes.has(mimeType)\n}\n\ntype SpineItem = Manifest[\"spineItems\"][number]\ntype ManifestItem = Manifest[\"items\"][number]\ntype ArchiveRecord = Archive[\"records\"][number]\ntype ArchiveFileRecord = Extract<ArchiveRecord, { dir: false }>\n\nconst encodeOriginalUriSegment = (uri: string) => encodeURIComponent(uri)\n\nconst createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return encodeURI(`${hrefBaseUrl}${resourcePath}`)\n}\n\nconst hasOpfExtension = (path: string) => path.toLowerCase().endsWith(`.opf`)\n\nconst isArchiveEpub = (archive: Archive) =>\n archive.records.some(\n (file) =>\n !file.dir &&\n (hasOpfExtension(file.basename) || hasOpfExtension(file.uri)),\n )\n\nexport const buildVirtualPanoramaResourcePath = ({\n cropSide,\n originalUri,\n}: {\n originalUri: string\n cropSide: PanoramaCropSide\n}) => {\n return `${PANORAMA_RESOURCE_PREFIX}/${encodeOriginalUriSegment(originalUri)}/${cropSide}.xhtml`\n}\n\nconst spreadPropertiesForSide = (\n side: PanoramaCropSide,\n): Pick<SpineItem, \"pageSpreadLeft\" | \"pageSpreadRight\"> =>\n side === `left`\n ? { pageSpreadLeft: true, pageSpreadRight: undefined }\n : { pageSpreadLeft: undefined, pageSpreadRight: true }\n\nconst cropSidesInReadingOrder = (\n readingDirection: Manifest[\"readingDirection\"],\n): [PanoramaCropSide, PanoramaCropSide] =>\n readingDirection === `rtl` ? [`right`, `left`] : [`left`, `right`]\n\nconst createVirtualSpineItem = ({\n baseUrl,\n cropSide,\n label,\n originalSpineItem,\n originalUri,\n progressionWeight,\n}: {\n baseUrl: string\n originalSpineItem: SpineItem\n originalUri: string\n label: string\n cropSide: PanoramaCropSide\n progressionWeight: number | undefined\n}): SpineItem => {\n const resourcePath = buildVirtualPanoramaResourcePath({\n cropSide,\n originalUri,\n })\n\n return {\n ...originalSpineItem,\n id: createXmlSafeId(`${originalSpineItem.id}.${label}`),\n href: createManifestResourceHref({ baseUrl, resourcePath }),\n mediaType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n progressionWeight,\n renditionLayout: `pre-paginated`,\n ...spreadPropertiesForSide(cropSide),\n }\n}\n\nconst createVirtualManifestItem = ({\n href,\n id,\n mediaType,\n}: Pick<ManifestItem, \"href\" | \"id\" | \"mediaType\">): ManifestItem => ({\n href,\n id,\n mediaType,\n})\n\nexport const getArchiveRecordForManifestItem = ({\n archive,\n baseUrl,\n spineItem,\n}: {\n archive: Archive\n baseUrl: string\n spineItem: Manifest[\"spineItems\"][number]\n}): ArchiveRecord | undefined => {\n const hrefCandidates = [spineItem.href, decodeManifestHref(spineItem.href)]\n const resourcePathCandidates = new Set(\n hrefCandidates.flatMap((href) => getResourcePathCandidates(href, baseUrl)),\n )\n\n return archive.records.find(\n (item) => !item.dir && resourcePathCandidates.has(item.uri),\n )\n}\n\nconst decodeManifestHref = (href: string) => {\n try {\n return decodeURI(href)\n } catch {\n return href\n }\n}\n\nconst normalizeBaseUrl = (baseUrl: string) =>\n baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n\nconst getResourcePathCandidates = (href: string, baseUrl: string) => {\n const candidates = [href]\n\n if (href.startsWith(`file://`)) {\n candidates.push(href.slice(`file://`.length))\n }\n\n if (baseUrl) {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n if (href.startsWith(normalizedBaseUrl)) {\n candidates.push(href.slice(normalizedBaseUrl.length))\n }\n }\n\n return candidates\n}\n\nexport const mediaTypeFromArchiveRecord = (\n record:\n | {\n basename: string\n encodingFormat?: string\n }\n | undefined,\n) =>\n parseContentType(record?.encodingFormat ?? ``) ||\n detectMimeTypeFromName(record?.basename ?? ``)\n\nconst mediaTypeFromArchiveRecordResourcePath = (\n record: Pick<ArchiveRecord, \"basename\" | \"uri\">,\n) =>\n detectMimeTypeFromName(record.uri) || detectMimeTypeFromName(record.basename)\n\nexport const isPanoramaSplitSupportedArchiveRecord = (\n record: ArchiveRecord | undefined,\n): record is ArchiveFileRecord => {\n if (record === undefined || record.dir) return false\n\n const resourcePathMediaType = mediaTypeFromArchiveRecordResourcePath(record)\n\n if (!isPanoramaSplitSupportedImage(resourcePathMediaType)) return false\n\n return isPanoramaSplitSupportedImage(mediaTypeFromArchiveRecord(record))\n}\n\nexport const panoramaSplit =\n ({ archive, baseUrl }: { archive: Archive; baseUrl: string }) =>\n async (manifest: Manifest): Promise<Manifest> => {\n if (isArchiveEpub(archive)) return manifest\n\n const virtualManifestItems: ManifestItem[] = []\n const spineItems = manifest.spineItems.flatMap((spineItem) => {\n const archiveRecord = getArchiveRecordForManifestItem({\n archive,\n baseUrl,\n spineItem,\n })\n\n if (!isPanoramaSplitSupportedArchiveRecord(archiveRecord)) {\n return [spineItem]\n }\n\n const detected = detectPanoramaFromBasename(archiveRecord.basename)\n\n if (detected === undefined) return [spineItem]\n\n const [firstCropSide, secondCropSide] = cropSidesInReadingOrder(\n manifest.readingDirection,\n )\n const splitProgressionWeight =\n spineItem.progressionWeight !== undefined\n ? spineItem.progressionWeight / 2\n : undefined\n const firstSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: firstCropSide,\n label: detected.firstPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n const secondSpineItem = createVirtualSpineItem({\n baseUrl,\n cropSide: secondCropSide,\n label: detected.secondPageLabel,\n originalSpineItem: spineItem,\n originalUri: archiveRecord.uri,\n progressionWeight: splitProgressionWeight,\n })\n\n virtualManifestItems.push(\n createVirtualManifestItem(firstSpineItem),\n createVirtualManifestItem(secondSpineItem),\n )\n\n return [firstSpineItem, secondSpineItem]\n })\n\n const alignedSpineItems = alignSpineItemsForSpreadParity({\n readingDirection: manifest.readingDirection,\n spineItems,\n })\n\n if (virtualManifestItems.length === 0 && alignedSpineItems === spineItems) {\n return manifest\n }\n\n return {\n ...manifest,\n spineItems: alignedSpineItems.map((spineItem, index) => ({\n ...spineItem,\n index,\n })),\n items: [...manifest.items, ...virtualManifestItems],\n }\n }\n","import { escapeXmlAttributeValue } from \"@prose-reader/shared\"\nimport type { Archive, HookResource } from \"@prose-reader/streamer\"\nimport {\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\ntype CropRect = {\n x: number\n width: number\n height: number\n}\n\ntype ImageDimensions = {\n width: number\n height: number\n}\n\nconst imageDimensionsCache = new WeakMap<\n Archive,\n Map<string, ImageDimensions>\n>()\n\nconst decodeOriginalUriSegment = (encoded: string): string | undefined => {\n try {\n return decodeURIComponent(encoded)\n } catch {\n return undefined\n }\n}\n\nexport const parseVirtualPanoramaResourcePath = (\n resourcePath: string,\n): VirtualPanoramaResource | undefined => {\n const prefixIndex = resourcePath.indexOf(`${PANORAMA_RESOURCE_PREFIX}/`)\n\n if (prefixIndex < 0) return undefined\n\n const virtualPath = resourcePath.slice(prefixIndex)\n const parts = virtualPath.split(`/`)\n\n const encodedOriginalUri = parts[2]\n const cropFileName = parts[3]\n\n if (\n parts.length !== 4 ||\n parts[0] !== `__prose-reader__` ||\n parts[1] !== `panorama` ||\n encodedOriginalUri === undefined ||\n cropFileName === undefined\n ) {\n return undefined\n }\n\n const cropSide = cropFileName.split(`.`)[0]\n\n if (cropSide !== `left` && cropSide !== `right`) return undefined\n\n const originalUri = decodeOriginalUriSegment(encodedOriginalUri)\n\n if (originalUri === undefined) return undefined\n\n return {\n originalUri,\n cropSide,\n }\n}\n\nconst cropRectForSide = ({\n cropSide,\n imageHeight,\n imageWidth,\n}: {\n cropSide: PanoramaCropSide\n imageWidth: number\n imageHeight: number\n}): CropRect => {\n const leftWidth = Math.floor(imageWidth / 2)\n const rightWidth = imageWidth - leftWidth\n\n return cropSide === `left`\n ? { x: 0, width: leftWidth, height: imageHeight }\n : { x: leftWidth, width: rightWidth, height: imageHeight }\n}\n\n/**\n * Since we create a virtual sub path we need to use the relative path to the original image.\n * There is no \"real\" path but the streamer does not need to know that.\n */\nconst getRelativeOriginalImageSrc = (originalUri: string) => {\n if (/^https?:\\/\\//.test(originalUri)) return originalUri\n\n return `../../../${encodeURI(originalUri)}`\n}\n\nconst readImageDimensions = async (source: Blob): Promise<ImageDimensions> => {\n if (typeof createImageBitmap !== `function`) {\n throw new Error(`Panorama XHTML generation requires createImageBitmap`)\n }\n\n const bitmap = await createImageBitmap(source)\n\n try {\n return {\n height: bitmap.height,\n width: bitmap.width,\n }\n } finally {\n bitmap.close()\n }\n}\n\nexport const createPanoramaSplitXhtml = ({\n cropSide,\n imageDimensions,\n originalUri,\n}: {\n cropSide: PanoramaCropSide\n imageDimensions: ImageDimensions\n originalUri: string\n}): string => {\n if (imageDimensions.width < 2) {\n throw new Error(`Panorama image is too narrow to split`)\n }\n\n const crop = cropRectForSide({\n cropSide,\n imageHeight: imageDimensions.height,\n imageWidth: imageDimensions.width,\n })\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta name=\"viewport\" content=\"width=${crop.width}, height=${crop.height}\" />\n <style>\n html,\n body {\n width: ${crop.width}px;\n height: ${crop.height}px;\n margin: 0;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: ${imageDimensions.width}px;\n height: ${imageDimensions.height}px;\n max-width: none;\n transform: translateX(-${crop.x}px);\n user-select: none;\n -webkit-user-drag: none;\n }\n </style>\n </head>\n <body>\n <img src=\"${escapeXmlAttributeValue(getRelativeOriginalImageSrc(originalUri))}\" alt=\"\" />\n </body>\n</html>`\n}\n\nconst generatePanoramaSplitResource = async ({\n archive,\n resourcePath,\n}: {\n archive: Archive\n resourcePath: string\n}): Promise<HookResource | undefined> => {\n const virtualResource = parseVirtualPanoramaResourcePath(resourcePath)\n\n if (virtualResource === undefined) return undefined\n\n const file = archive.records.find(\n (file) => file.uri === virtualResource.originalUri && !file.dir,\n )\n\n if (file === undefined || file.dir) {\n throw new Error(\n `no source file found for virtual panorama resourcePath:${resourcePath}`,\n )\n }\n\n const archiveCache = imageDimensionsCache.get(archive) ?? new Map()\n\n if (!imageDimensionsCache.has(archive)) {\n imageDimensionsCache.set(archive, archiveCache)\n }\n\n const imageDimensions =\n archiveCache.get(virtualResource.originalUri) ??\n (await readImageDimensions(await file.blob()))\n\n archiveCache.set(virtualResource.originalUri, imageDimensions)\n\n const body = createPanoramaSplitXhtml({\n cropSide: virtualResource.cropSide,\n imageDimensions,\n originalUri: virtualResource.originalUri,\n })\n\n return {\n body,\n params: {\n contentType: PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n },\n }\n}\n\nexport const panoramaSplitResourceHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const panoramaResource = await generatePanoramaSplitResource({\n archive,\n resourcePath,\n })\n\n if (panoramaResource === undefined) return resource\n\n return {\n ...resource,\n ...panoramaResource,\n params: {\n ...resource.params,\n ...panoramaResource.params,\n },\n }\n }\n","import {\n getEpubCfiSpineItemref,\n updateEpubCfiSpineItemref,\n} from \"@prose-reader/cfi\"\nimport type { Reader } from \"@prose-reader/core\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeId } from \"@prose-reader/streamer\"\nimport { parseVirtualPanoramaResourcePath } from \"./panoramaSplitResource\"\n\nconst VIRTUAL_SPINE_ID_EXTENSION = \"vnd.prose-reader.cbz.virtual-spine-id\"\n\ntype SpineItem = Manifest[\"spineItems\"][number]\n\nexport type CbzEnhancerAPI = {\n __PROSE_READER_ENHANCER_CBZ: true\n cbz: {\n isPanoramaSpineItem: (spineItem: { item: { href: string } }) => boolean\n }\n}\n\nconst decodeURIComponentSafe = (value: string) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n}\n\nconst decodeURISafe = (value: string) => {\n try {\n return decodeURI(value)\n } catch {\n return value\n }\n}\n\nconst parseVirtualPanoramaFromHref = (href: string) => {\n const virtualResource =\n parseVirtualPanoramaResourcePath(href) ??\n parseVirtualPanoramaResourcePath(decodeURISafe(href))\n\n if (!virtualResource) return undefined\n\n return {\n ...virtualResource,\n originalUri: decodeURIComponentSafe(virtualResource.originalUri),\n }\n}\n\n/**\n * A cbz panorama is a continuous double-page drawing that the streamer split\n * into two virtual portrait spine items. A spine item is therefore a panorama\n * half iff its href is one of those virtual split resources.\n */\nconst isPanoramaSpineItem = (spineItem: { item: { href: string } }) =>\n parseVirtualPanoramaFromHref(spineItem.item.href) !== undefined\n\nconst getOriginalSpineIndex = (reader: Reader, originalUri: string) => {\n const seenVirtualOriginalUris = new Set<string>()\n let originalSpineIndex = 0\n\n for (const spineItem of reader.spineItemsManager.items) {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.item.href)\n\n if (!virtualResource) {\n originalSpineIndex++\n continue\n }\n\n if (virtualResource.originalUri === originalUri) {\n return originalSpineIndex\n }\n\n if (!seenVirtualOriginalUris.has(virtualResource.originalUri)) {\n seenVirtualOriginalUris.add(virtualResource.originalUri)\n originalSpineIndex++\n }\n }\n\n return undefined\n}\n\nconst restoreOriginalSpineReference = (\n reader: Reader,\n cfi: string,\n spineItem: SpineItem,\n) => {\n const virtualResource = parseVirtualPanoramaFromHref(spineItem.href)\n\n if (!virtualResource) return undefined\n\n const originalSpineIndex = getOriginalSpineIndex(\n reader,\n virtualResource.originalUri,\n )\n\n if (originalSpineIndex === undefined) return undefined\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: encodeURIComponent(spineItem.id),\n },\n spineId: createXmlSafeId(virtualResource.originalUri),\n spineIndex: originalSpineIndex,\n })\n}\n\nconst restoreVirtualSpineReference = (reader: Reader, cfi: string) => {\n const virtualSpineId =\n getEpubCfiSpineItemref(cfi)?.extensions?.[VIRTUAL_SPINE_ID_EXTENSION]\n\n if (!virtualSpineId) return undefined\n\n const virtualSpineItem = reader.spineItemsManager.get(\n decodeURIComponentSafe(virtualSpineId),\n )\n\n if (\n !virtualSpineItem ||\n !parseVirtualPanoramaFromHref(virtualSpineItem.item.href)\n ) {\n return undefined\n }\n\n return updateEpubCfiSpineItemref(cfi, {\n extensions: {\n [VIRTUAL_SPINE_ID_EXTENSION]: undefined,\n },\n spineId: virtualSpineItem.item.id,\n spineIndex: virtualSpineItem.item.index,\n })\n}\n\nexport const cbzEnhancer =\n <InheritOptions, InheritOutput extends Reader>(\n next: (options: InheritOptions) => InheritOutput,\n ) =>\n (options: InheritOptions): InheritOutput & CbzEnhancerAPI => {\n const reader = next(options)\n\n const unregisterGenerateHook = reader.hookManager.register(\n \"cfi.afterGenerate\",\n ({ cfi, spineItem }) =>\n restoreOriginalSpineReference(reader, cfi, spineItem),\n )\n\n const unregisterResolveHook = reader.hookManager.register(\n \"cfi.beforeResolve\",\n ({ cfi }) => restoreVirtualSpineReference(reader, cfi),\n )\n\n const destroy = () => {\n unregisterGenerateHook()\n unregisterResolveHook()\n reader.destroy()\n }\n\n return {\n ...reader,\n __PROSE_READER_ENHANCER_CBZ: true,\n cbz: {\n isPanoramaSpineItem,\n },\n destroy,\n }\n }\n","import { parseContentType } from \"@prose-reader/shared\"\nimport type { Archive } from \"@prose-reader/streamer\"\n\nexport const CBZ_MIME_TYPES: ReadonlySet<string> = new Set([\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n])\n\nconst hasCbzExtension = (name: string) => name.toLowerCase().endsWith(\".cbz\")\n\nexport const isCbzArchive = (archive: Archive) => {\n const mimeType = parseContentType(archive.encodingFormat ?? \"\")\n\n if (mimeType && CBZ_MIME_TYPES.has(mimeType)) return true\n if (hasCbzExtension(archive.filename ?? \"\")) return true\n\n return false\n}\n","import type { StreamerManifestHookFactory } from \"@prose-reader/streamer\"\nimport { isCbzArchive } from \"./isCbzArchive\"\n\nexport const detectReadingDirectionManifest: StreamerManifestHookFactory =\n ({ archive }) =>\n (manifest) => {\n if (manifest.readingDirection !== undefined) return manifest\n if (!isCbzArchive(archive)) return manifest\n\n return {\n ...manifest,\n readingDirection: \"rtl\",\n }\n }\n","import type {\n StreamerManifestHookFactory,\n StreamerResourceHookFactory,\n} from \"@prose-reader/streamer\"\nimport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nimport { panoramaSplit } from \"./panoramaSplitManifest\"\nimport { panoramaSplitResourceHook } from \"./panoramaSplitResource\"\n\nexport const streamerHooks: {\n manifest: {\n content: StreamerManifestHookFactory[]\n spine: StreamerManifestHookFactory[]\n }\n resource: StreamerResourceHookFactory[]\n} = {\n manifest: {\n content: [detectReadingDirectionManifest],\n spine: [panoramaSplit],\n },\n resource: [panoramaSplitResourceHook],\n}\n\nexport { detectReadingDirectionManifest } from \"./detectReadingDirectionManifest\"\nexport { CBZ_MIME_TYPES, isCbzArchive } from \"./isCbzArchive\"\n\nexport {\n buildVirtualPanoramaResourcePath,\n detectPanoramaFromBasename,\n isPanoramaSplitSupportedArchiveRecord,\n isPanoramaSplitSupportedImage,\n PANORAMA_RESOURCE_PREFIX,\n PANORAMA_SPLIT_DOCUMENT_MEDIA_TYPE,\n type PanoramaCropSide,\n panoramaSplit,\n type VirtualPanoramaResource,\n} from \"./panoramaSplitManifest\"\n\nexport {\n createPanoramaSplitXhtml,\n panoramaSplitResourceHook,\n parseVirtualPanoramaResourcePath,\n} from \"./panoramaSplitResource\"\n"],"mappings":"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"}
|