@papyrus-sdk/ui-react-native 0.2.35-beta.11.epub.1 → 0.2.35-beta.11.epub.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +55 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +55 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/runtime/comicRuntime.test.ts +9 -9
- package/runtime/epubReader.js +32 -7
- package/runtime/epubReader.test.ts +52 -0
- package/runtime/index.html +55 -11
- package/runtime/runtime.js +55 -11
package/package.json
CHANGED
|
@@ -188,7 +188,7 @@ describe("mobile comic runtime helpers", () => {
|
|
|
188
188
|
expect(outlineGuard).toBeLessThan(readyEvent);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
it("
|
|
191
|
+
it("ships the paginated and chapter-scroll EPUB reading policy", () => {
|
|
192
192
|
const runtime = readFileSync(
|
|
193
193
|
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
194
194
|
"utf8"
|
|
@@ -198,18 +198,18 @@ describe("mobile comic runtime helpers", () => {
|
|
|
198
198
|
"utf8"
|
|
199
199
|
);
|
|
200
200
|
|
|
201
|
-
expect(runtime).toContain("manager:
|
|
202
|
-
expect(html).toContain("manager:
|
|
203
|
-
expect(runtime).toContain("
|
|
204
|
-
expect(html).toContain("
|
|
201
|
+
expect(runtime).toContain("manager:'default'");
|
|
202
|
+
expect(html).toContain("manager:'default'");
|
|
203
|
+
expect(runtime).toContain("'scrolled-doc'");
|
|
204
|
+
expect(html).toContain("'scrolled-doc'");
|
|
205
205
|
expect(runtime).toContain("rendition.on('relocated'");
|
|
206
206
|
expect(html).toContain("rendition.on('relocated'");
|
|
207
207
|
expect(runtime).toContain("sendEvent('VIEWER_SCROLL'");
|
|
208
208
|
expect(html).toContain("sendEvent('VIEWER_SCROLL'");
|
|
209
|
-
expect(runtime).toContain("sendEvent('
|
|
210
|
-
expect(html).toContain("sendEvent('
|
|
211
|
-
expect(runtime).
|
|
212
|
-
expect(html).
|
|
209
|
+
expect(runtime).toContain("sendEvent('EPUB_CONTENT_TAP'");
|
|
210
|
+
expect(html).toContain("sendEvent('EPUB_CONTENT_TAP'");
|
|
211
|
+
expect(runtime).toContain("'paginated'");
|
|
212
|
+
expect(html).toContain("'paginated'");
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
it("observes the real EPUB manager queue without changing queue semantics", () => {
|
package/runtime/epubReader.js
CHANGED
|
@@ -9,13 +9,36 @@
|
|
|
9
9
|
if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
-
function location(raw,count,readingMode){
|
|
12
|
+
function location(raw,count,readingMode,geometry){
|
|
13
13
|
var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
|
|
14
14
|
var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
|
|
15
15
|
var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
|
|
16
16
|
var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
|
|
17
|
+
var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
|
|
18
|
+
// Chromium may round a column offset down by a fraction of a CSS pixel.
|
|
19
|
+
// Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
|
|
20
|
+
if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
|
|
21
|
+
var boundary=Math.round(geometry.offset/geometry.pageWidth);
|
|
22
|
+
if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
|
|
23
|
+
visualPage=Math.min(total,Math.max(1,boundary+1));
|
|
24
|
+
atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
|
|
25
|
+
atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
var fraction=total?Math.max(0,(visualPage-1)/total):0;
|
|
18
|
-
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:
|
|
29
|
+
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
|
|
30
|
+
}
|
|
31
|
+
function installResizeAnchor(manager,readCfi){
|
|
32
|
+
var original=manager.resize,anchor=null;
|
|
33
|
+
function resize(width,height,target){
|
|
34
|
+
if(validCfi(target))anchor=target;
|
|
35
|
+
if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
|
|
36
|
+
// epub.js accepts a logical target as its third argument. Keep it across
|
|
37
|
+
// keyboard/dialog reflows instead of adopting each new viewport start.
|
|
38
|
+
return original.call(this,width,height,anchor || target);
|
|
39
|
+
}
|
|
40
|
+
manager.resize=resize;
|
|
41
|
+
return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
|
|
19
42
|
}
|
|
20
43
|
function install(contents,config){
|
|
21
44
|
var doc=contents.document,win=contents.window,start=null,blocked=false;
|
|
@@ -23,11 +46,13 @@
|
|
|
23
46
|
function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
|
|
24
47
|
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
|
|
25
48
|
function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
|
|
26
|
-
function
|
|
27
|
-
function
|
|
49
|
+
function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
|
|
50
|
+
function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
|
|
51
|
+
function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
|
|
28
52
|
function cancel(){start=null;blocked=true;}
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
|
|
54
|
+
doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
|
|
55
|
+
return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
|
|
31
56
|
}
|
|
32
|
-
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,install:install};
|
|
57
|
+
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
|
|
33
58
|
})(typeof window==='undefined'?globalThis:window);
|
|
@@ -20,3 +20,55 @@ describe('EPUB iframe gestures', () => {
|
|
|
20
20
|
touch(d.window.document.querySelector('p')!);expect(taps).toBe(0);hit=false;touch(d.window.document.querySelector('a')!);expect(taps).toBe(0);touch(d.window.document.querySelector('p')!);expect(taps).toBe(1);cleanup();touch(d.window.document.querySelector('p')!);expect(taps).toBe(1);
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
describe('EPUB subpixel column boundaries',()=>{
|
|
25
|
+
it('normalizes only a nearby LTR boundary and updates start/end',()=>{
|
|
26
|
+
const p=load(),raw={start:{index:0,cfi:'epubcfi(/6/2!/4/12)',displayed:{page:1,total:17}},atStart:true,atEnd:false};
|
|
27
|
+
expect(p.location(raw,3,'paged',{offset:411.4285888671875,pageWidth:412,direction:'ltr'})).toMatchObject({visualPage:2,atStart:false});
|
|
28
|
+
expect(p.location({...raw,start:{...raw.start,index:1}},4,'paged',{offset:0,pageWidth:412,direction:'ltr',firstIndex:1,lastIndex:2})).toMatchObject({atStart:true});
|
|
29
|
+
expect(p.location(raw,3,'paged',{offset:400,pageWidth:412,direction:'ltr'}).visualPage).toBe(1);
|
|
30
|
+
expect(p.location(raw,3,'paged',{offset:411.5,pageWidth:412,direction:'rtl'}).visualPage).toBe(1);
|
|
31
|
+
expect(p.location({...raw,start:{...raw.start,index:2,displayed:{page:16,total:17}}},3,'paged',{offset:6591.5,pageWidth:412,direction:'ltr'})).toMatchObject({visualPage:17,atEnd:true});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('EPUB resize anchors',()=>{
|
|
36
|
+
it('preserves a logical CFI through transient resize until real interaction',()=>{
|
|
37
|
+
const p=load(),calls:any[]=[];let current='epubcfi(/6/2!/4/12/1:149)';
|
|
38
|
+
const original=function(this:any,w:any,h:any,target:any){calls.push([this,w,h,target]);return 'resized';};
|
|
39
|
+
const manager={resize:original};const anchor=p.installResizeAnchor(manager,()=>current);
|
|
40
|
+
expect(manager.resize(412,889,undefined)).toBe('resized');
|
|
41
|
+
current='epubcfi(/6/2!/4/12/1:97)';manager.resize(412,863,undefined);
|
|
42
|
+
expect(calls.map(c=>c[3])).toEqual(['epubcfi(/6/2!/4/12/1:149)','epubcfi(/6/2!/4/12/1:149)']);
|
|
43
|
+
expect(anchor.cfi()).toBe('epubcfi(/6/2!/4/12/1:149)');anchor.invalidate();
|
|
44
|
+
current='epubcfi(/6/2!/4/22/1:149)';manager.resize(863,412,undefined);
|
|
45
|
+
expect(calls.at(-1)[3]).toBe(current);expect(calls.every(c=>c[0]===manager)).toBe(true);
|
|
46
|
+
anchor.destroy();expect(manager.resize).toBe(original);
|
|
47
|
+
});
|
|
48
|
+
it('honors explicit valid targets and does not pass invalid CFIs',()=>{
|
|
49
|
+
const p=load(),calls:any[]=[];const manager={resize:(w:any,h:any,target:any)=>calls.push(target)};
|
|
50
|
+
const anchor=p.installResizeAnchor(manager,()=>null);manager.resize(1,2,undefined);expect(calls[0]).toBeUndefined();
|
|
51
|
+
manager.resize(1,2,'epubcfi(/6/4!/4/2)');expect(anchor.cfi()).toBe('epubcfi(/6/4!/4/2)');anchor.destroy();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not discard a resize anchor for tap, selection or note marker',()=>{
|
|
56
|
+
const d=new JSDOM('<p>text</p><a href="#">link</a><button data-papyrus-overlay>note</button>',{runScripts:'outside-only'});
|
|
57
|
+
d.window.eval(readFileSync('packages/ui-react-native/runtime/epubReader.js','utf8'));
|
|
58
|
+
let invalidations=0,readingMode='paged';const p=(d.window as any).PapyrusEpubReader;
|
|
59
|
+
const cleanup=p.install({document:d.window.document,window:d.window},{mode:()=>readingMode,rtl:()=>false,blocked:()=>false,current:()=>true,tap:()=>{},turn:()=>invalidations++,interact:()=>invalidations++});
|
|
60
|
+
function touch(target:Element,y=40){for(const type of ['touchstart','touchend']){const point={clientX:30,clientY:type==='touchend'?y:40};const e=new d.window.Event(type,{bubbles:true});Object.defineProperties(e,{touches:{value:type==='touchend'?[]:[point]},changedTouches:{value:[point]}});target.dispatchEvent(e);}}
|
|
61
|
+
touch(d.window.document.querySelector('p')!);touch(d.window.document.querySelector('button')!);expect(invalidations).toBe(0);
|
|
62
|
+
readingMode='chapter-scroll';touch(d.window.document.querySelector('p')!,200);expect(invalidations).toBe(1);
|
|
63
|
+
d.window.document.querySelector('a')!.dispatchEvent(new d.window.Event('click',{bubbles:true}));expect(invalidations).toBe(2);cleanup();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('invalidates chapter-scroll before relocation and ignores stale iframe callbacks',()=>{
|
|
67
|
+
const d=new JSDOM('<p>text</p><a href="#">link</a>',{runScripts:'outside-only'});
|
|
68
|
+
d.window.eval(readFileSync('packages/ui-react-native/runtime/epubReader.js','utf8'));
|
|
69
|
+
let invalidations=0,current=true;
|
|
70
|
+
const cleanup=(d.window as any).PapyrusEpubReader.install({document:d.window.document,window:d.window},{mode:()=> 'chapter-scroll',rtl:()=>false,blocked:()=>false,current:()=>current,tap:()=>{},turn:()=>{},interact:()=>invalidations++});
|
|
71
|
+
function dispatch(type:string,y:number){const point={clientX:30,clientY:y},e=new d.window.Event(type,{bubbles:true});Object.defineProperties(e,{touches:{value:type==='touchend'?[]:[point]},changedTouches:{value:[point]}});d.window.document.querySelector('p')!.dispatchEvent(e);}
|
|
72
|
+
dispatch('touchstart',40);dispatch('touchmove',120);expect(invalidations).toBe(1);
|
|
73
|
+
current=false;dispatch('touchend',180);d.window.document.querySelector('a')!.dispatchEvent(new d.window.Event('click',{bubbles:true}));expect(invalidations).toBe(1);cleanup();
|
|
74
|
+
});
|
package/runtime/index.html
CHANGED
|
@@ -20495,13 +20495,36 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20495
20495
|
if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
|
|
20496
20496
|
return null;
|
|
20497
20497
|
}
|
|
20498
|
-
function location(raw,count,readingMode){
|
|
20498
|
+
function location(raw,count,readingMode,geometry){
|
|
20499
20499
|
var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
|
|
20500
20500
|
var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
|
|
20501
20501
|
var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
|
|
20502
20502
|
var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
|
|
20503
|
+
var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
|
|
20504
|
+
// Chromium may round a column offset down by a fraction of a CSS pixel.
|
|
20505
|
+
// Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
|
|
20506
|
+
if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
|
|
20507
|
+
var boundary=Math.round(geometry.offset/geometry.pageWidth);
|
|
20508
|
+
if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
|
|
20509
|
+
visualPage=Math.min(total,Math.max(1,boundary+1));
|
|
20510
|
+
atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
|
|
20511
|
+
atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
|
|
20512
|
+
}
|
|
20513
|
+
}
|
|
20503
20514
|
var fraction=total?Math.max(0,(visualPage-1)/total):0;
|
|
20504
|
-
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:
|
|
20515
|
+
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
|
|
20516
|
+
}
|
|
20517
|
+
function installResizeAnchor(manager,readCfi){
|
|
20518
|
+
var original=manager.resize,anchor=null;
|
|
20519
|
+
function resize(width,height,target){
|
|
20520
|
+
if(validCfi(target))anchor=target;
|
|
20521
|
+
if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
|
|
20522
|
+
// epub.js accepts a logical target as its third argument. Keep it across
|
|
20523
|
+
// keyboard/dialog reflows instead of adopting each new viewport start.
|
|
20524
|
+
return original.call(this,width,height,anchor || target);
|
|
20525
|
+
}
|
|
20526
|
+
manager.resize=resize;
|
|
20527
|
+
return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
|
|
20505
20528
|
}
|
|
20506
20529
|
function install(contents,config){
|
|
20507
20530
|
var doc=contents.document,win=contents.window,start=null,blocked=false;
|
|
@@ -20509,13 +20532,15 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20509
20532
|
function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
|
|
20510
20533
|
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
|
|
20511
20534
|
function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
|
|
20512
|
-
function
|
|
20513
|
-
function
|
|
20535
|
+
function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
|
|
20536
|
+
function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
|
|
20537
|
+
function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
|
|
20514
20538
|
function cancel(){start=null;blocked=true;}
|
|
20515
|
-
|
|
20516
|
-
|
|
20539
|
+
function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
|
|
20540
|
+
doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
|
|
20541
|
+
return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
|
|
20517
20542
|
}
|
|
20518
|
-
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,install:install};
|
|
20543
|
+
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
|
|
20519
20544
|
})(typeof window==='undefined'?globalThis:window);
|
|
20520
20545
|
|
|
20521
20546
|
/* @papyrus-epub-reader:end */
|
|
@@ -20650,6 +20675,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20650
20675
|
let currentType = null;
|
|
20651
20676
|
let book = null;
|
|
20652
20677
|
let rendition = null;
|
|
20678
|
+
let epubResizeAnchor = null;
|
|
20653
20679
|
let documentSessionId = null;
|
|
20654
20680
|
let annotationDocumentId;
|
|
20655
20681
|
let annotationLabels = {};
|
|
@@ -21062,6 +21088,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21062
21088
|
};
|
|
21063
21089
|
|
|
21064
21090
|
const clearViewer = () => {
|
|
21091
|
+
if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
|
|
21065
21092
|
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
21066
21093
|
epubAnnotationRenderers.clear();
|
|
21067
21094
|
if (epubInteractionCleanup) {
|
|
@@ -21749,10 +21776,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21749
21776
|
blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
|
|
21750
21777
|
annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
|
|
21751
21778
|
current: () => session === documentSessionId && activeRendition === rendition,
|
|
21779
|
+
interact: () => epubResizeAnchor?.invalidate(),
|
|
21752
21780
|
tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
|
|
21753
21781
|
turn: direction => {
|
|
21754
21782
|
epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
|
|
21755
21783
|
if(session !== documentSessionId || activeRendition !== rendition)return;
|
|
21784
|
+
epubResizeAnchor?.invalidate();
|
|
21756
21785
|
return direction<0?activeRendition.prev():activeRendition.next();
|
|
21757
21786
|
});
|
|
21758
21787
|
epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
|
|
@@ -21870,8 +21899,18 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21870
21899
|
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
21871
21900
|
if (index < 0 || index >= spineItems.length) return;
|
|
21872
21901
|
epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
|
|
21873
|
-
|
|
21902
|
+
let pageGeometry;
|
|
21903
|
+
const manager=rendition.manager,layout=manager?.layout;
|
|
21904
|
+
if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
|
|
21905
|
+
const visible=manager.visible?.();
|
|
21906
|
+
const view=visible?.find(view=>view.index===index);
|
|
21907
|
+
if(view && manager.container){
|
|
21908
|
+
pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
|
|
21909
|
+
}
|
|
21910
|
+
}
|
|
21911
|
+
epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
|
|
21874
21912
|
if(epubLocation){
|
|
21913
|
+
epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
|
|
21875
21914
|
if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
|
|
21876
21915
|
sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
|
|
21877
21916
|
}
|
|
@@ -21891,6 +21930,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21891
21930
|
);
|
|
21892
21931
|
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
21893
21932
|
sendEpubDiagnostic('epub.display.end', diagnosticPayload);
|
|
21933
|
+
const resizeRendition=rendition;
|
|
21934
|
+
epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
|
|
21894
21935
|
if (
|
|
21895
21936
|
rendition &&
|
|
21896
21937
|
rendition.manager &&
|
|
@@ -22024,6 +22065,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22024
22065
|
const request = ++epubNavigationRequest;
|
|
22025
22066
|
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
22026
22067
|
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
22068
|
+
epubResizeAnchor?.invalidate();
|
|
22027
22069
|
await activeRendition.display(target);
|
|
22028
22070
|
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
22029
22071
|
currentPage = pageIndex + 1;
|
|
@@ -22213,6 +22255,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22213
22255
|
epubNavigationRequest += 1;
|
|
22214
22256
|
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
22215
22257
|
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
22258
|
+
epubResizeAnchor?.invalidate();
|
|
22216
22259
|
await activeRendition.display(annotation.anchor.href);
|
|
22217
22260
|
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
22218
22261
|
const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
|
|
@@ -22254,6 +22297,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22254
22297
|
const active = rendition;
|
|
22255
22298
|
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
22256
22299
|
if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
|
|
22300
|
+
epubResizeAnchor?.invalidate();
|
|
22257
22301
|
return payload.direction < 0 ? active.prev() : active.next();
|
|
22258
22302
|
});
|
|
22259
22303
|
await epubNavigationQueue;
|
|
@@ -22266,7 +22310,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22266
22310
|
{
|
|
22267
22311
|
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
22268
22312
|
if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
|
|
22269
|
-
const target=epubLocation?.cfi;
|
|
22313
|
+
const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
22270
22314
|
epubViewMode=next;
|
|
22271
22315
|
await renderEpubRendition('layout',epubLoadGeneration,target);
|
|
22272
22316
|
});
|
|
@@ -22276,7 +22320,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22276
22320
|
}
|
|
22277
22321
|
if(kind === 'epub-go-to-location'){
|
|
22278
22322
|
if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
|
|
22279
|
-
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>payload.documentSessionId
|
|
22323
|
+
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
|
|
22280
22324
|
await epubNavigationQueue;sendResponse(id,true,null);return;
|
|
22281
22325
|
}
|
|
22282
22326
|
|
|
@@ -22287,7 +22331,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22287
22331
|
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
22288
22332
|
if(session!==documentSessionId)return;
|
|
22289
22333
|
const active=rendition;
|
|
22290
|
-
const cfi=epubLocation?.cfi;
|
|
22334
|
+
const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
22291
22335
|
zoom=requestedZoom;applyEpubZoom();
|
|
22292
22336
|
// Let the content CSS update before redisplaying the logical anchor.
|
|
22293
22337
|
await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
|
package/runtime/runtime.js
CHANGED
|
@@ -121,13 +121,36 @@
|
|
|
121
121
|
if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
|
|
122
122
|
return null;
|
|
123
123
|
}
|
|
124
|
-
function location(raw,count,readingMode){
|
|
124
|
+
function location(raw,count,readingMode,geometry){
|
|
125
125
|
var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
|
|
126
126
|
var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
|
|
127
127
|
var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
|
|
128
128
|
var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
|
|
129
|
+
var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
|
|
130
|
+
// Chromium may round a column offset down by a fraction of a CSS pixel.
|
|
131
|
+
// Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
|
|
132
|
+
if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
|
|
133
|
+
var boundary=Math.round(geometry.offset/geometry.pageWidth);
|
|
134
|
+
if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
|
|
135
|
+
visualPage=Math.min(total,Math.max(1,boundary+1));
|
|
136
|
+
atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
|
|
137
|
+
atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
129
140
|
var fraction=total?Math.max(0,(visualPage-1)/total):0;
|
|
130
|
-
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:
|
|
141
|
+
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
|
|
142
|
+
}
|
|
143
|
+
function installResizeAnchor(manager,readCfi){
|
|
144
|
+
var original=manager.resize,anchor=null;
|
|
145
|
+
function resize(width,height,target){
|
|
146
|
+
if(validCfi(target))anchor=target;
|
|
147
|
+
if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
|
|
148
|
+
// epub.js accepts a logical target as its third argument. Keep it across
|
|
149
|
+
// keyboard/dialog reflows instead of adopting each new viewport start.
|
|
150
|
+
return original.call(this,width,height,anchor || target);
|
|
151
|
+
}
|
|
152
|
+
manager.resize=resize;
|
|
153
|
+
return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
|
|
131
154
|
}
|
|
132
155
|
function install(contents,config){
|
|
133
156
|
var doc=contents.document,win=contents.window,start=null,blocked=false;
|
|
@@ -135,13 +158,15 @@
|
|
|
135
158
|
function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
|
|
136
159
|
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
|
|
137
160
|
function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
|
|
138
|
-
function
|
|
139
|
-
function
|
|
161
|
+
function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
|
|
162
|
+
function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
|
|
163
|
+
function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
|
|
140
164
|
function cancel(){start=null;blocked=true;}
|
|
141
|
-
|
|
142
|
-
|
|
165
|
+
function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
|
|
166
|
+
doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
|
|
167
|
+
return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
|
|
143
168
|
}
|
|
144
|
-
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,install:install};
|
|
169
|
+
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
|
|
145
170
|
})(typeof window==='undefined'?globalThis:window);
|
|
146
171
|
|
|
147
172
|
/* @papyrus-epub-reader:end */
|
|
@@ -276,6 +301,7 @@
|
|
|
276
301
|
let currentType = null;
|
|
277
302
|
let book = null;
|
|
278
303
|
let rendition = null;
|
|
304
|
+
let epubResizeAnchor = null;
|
|
279
305
|
let documentSessionId = null;
|
|
280
306
|
let annotationDocumentId;
|
|
281
307
|
let annotationLabels = {};
|
|
@@ -688,6 +714,7 @@
|
|
|
688
714
|
};
|
|
689
715
|
|
|
690
716
|
const clearViewer = () => {
|
|
717
|
+
if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
|
|
691
718
|
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
692
719
|
epubAnnotationRenderers.clear();
|
|
693
720
|
if (epubInteractionCleanup) {
|
|
@@ -1375,10 +1402,12 @@
|
|
|
1375
1402
|
blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
|
|
1376
1403
|
annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
|
|
1377
1404
|
current: () => session === documentSessionId && activeRendition === rendition,
|
|
1405
|
+
interact: () => epubResizeAnchor?.invalidate(),
|
|
1378
1406
|
tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
|
|
1379
1407
|
turn: direction => {
|
|
1380
1408
|
epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
|
|
1381
1409
|
if(session !== documentSessionId || activeRendition !== rendition)return;
|
|
1410
|
+
epubResizeAnchor?.invalidate();
|
|
1382
1411
|
return direction<0?activeRendition.prev():activeRendition.next();
|
|
1383
1412
|
});
|
|
1384
1413
|
epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
|
|
@@ -1496,8 +1525,18 @@
|
|
|
1496
1525
|
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
1497
1526
|
if (index < 0 || index >= spineItems.length) return;
|
|
1498
1527
|
epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
|
|
1499
|
-
|
|
1528
|
+
let pageGeometry;
|
|
1529
|
+
const manager=rendition.manager,layout=manager?.layout;
|
|
1530
|
+
if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
|
|
1531
|
+
const visible=manager.visible?.();
|
|
1532
|
+
const view=visible?.find(view=>view.index===index);
|
|
1533
|
+
if(view && manager.container){
|
|
1534
|
+
pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
|
|
1500
1538
|
if(epubLocation){
|
|
1539
|
+
epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
|
|
1501
1540
|
if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
|
|
1502
1541
|
sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
|
|
1503
1542
|
}
|
|
@@ -1517,6 +1556,8 @@
|
|
|
1517
1556
|
);
|
|
1518
1557
|
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1519
1558
|
sendEpubDiagnostic('epub.display.end', diagnosticPayload);
|
|
1559
|
+
const resizeRendition=rendition;
|
|
1560
|
+
epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
|
|
1520
1561
|
if (
|
|
1521
1562
|
rendition &&
|
|
1522
1563
|
rendition.manager &&
|
|
@@ -1650,6 +1691,7 @@
|
|
|
1650
1691
|
const request = ++epubNavigationRequest;
|
|
1651
1692
|
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
1652
1693
|
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
1694
|
+
epubResizeAnchor?.invalidate();
|
|
1653
1695
|
await activeRendition.display(target);
|
|
1654
1696
|
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
1655
1697
|
currentPage = pageIndex + 1;
|
|
@@ -1839,6 +1881,7 @@
|
|
|
1839
1881
|
epubNavigationRequest += 1;
|
|
1840
1882
|
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
1841
1883
|
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
1884
|
+
epubResizeAnchor?.invalidate();
|
|
1842
1885
|
await activeRendition.display(annotation.anchor.href);
|
|
1843
1886
|
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
1844
1887
|
const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
|
|
@@ -1880,6 +1923,7 @@
|
|
|
1880
1923
|
const active = rendition;
|
|
1881
1924
|
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
1882
1925
|
if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
|
|
1926
|
+
epubResizeAnchor?.invalidate();
|
|
1883
1927
|
return payload.direction < 0 ? active.prev() : active.next();
|
|
1884
1928
|
});
|
|
1885
1929
|
await epubNavigationQueue;
|
|
@@ -1892,7 +1936,7 @@
|
|
|
1892
1936
|
{
|
|
1893
1937
|
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
1894
1938
|
if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
|
|
1895
|
-
const target=epubLocation?.cfi;
|
|
1939
|
+
const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
1896
1940
|
epubViewMode=next;
|
|
1897
1941
|
await renderEpubRendition('layout',epubLoadGeneration,target);
|
|
1898
1942
|
});
|
|
@@ -1902,7 +1946,7 @@
|
|
|
1902
1946
|
}
|
|
1903
1947
|
if(kind === 'epub-go-to-location'){
|
|
1904
1948
|
if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
|
|
1905
|
-
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>payload.documentSessionId
|
|
1949
|
+
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
|
|
1906
1950
|
await epubNavigationQueue;sendResponse(id,true,null);return;
|
|
1907
1951
|
}
|
|
1908
1952
|
|
|
@@ -1913,7 +1957,7 @@
|
|
|
1913
1957
|
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
1914
1958
|
if(session!==documentSessionId)return;
|
|
1915
1959
|
const active=rendition;
|
|
1916
|
-
const cfi=epubLocation?.cfi;
|
|
1960
|
+
const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
1917
1961
|
zoom=requestedZoom;applyEpubZoom();
|
|
1918
1962
|
// Let the content CSS update before redisplaying the logical anchor.
|
|
1919
1963
|
await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
|