@papyrus-sdk/ui-react-native 0.2.35-beta.9 → 0.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20517,6 +20517,67 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20517
20517
  <!-- @papyrus-comic-runtime:end -->
20518
20518
  <script>
20519
20519
  (function () {
20520
+ /* @papyrus-epub-reader:start */
20521
+ (function(root){
20522
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
20523
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\\([^\\n<>]+\\)$/.test(value);}
20524
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
20525
+ function gesture(start,end,state){
20526
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
20527
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
20528
+ if(state.mode==='paged' && Math.abs(dx)>=48 && Math.abs(dx)>Math.abs(dy)*1.5 && elapsed>=0 && elapsed<1500)return (dx<0)!==Boolean(state.rtl)?'next':'prev';
20529
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
20530
+ return null;
20531
+ }
20532
+ function location(raw,count,readingMode,geometry){
20533
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
20534
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
20535
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
20536
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
20537
+ var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
20538
+ // Chromium may round a column offset down by a fraction of a CSS pixel.
20539
+ // Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
20540
+ if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
20541
+ var boundary=Math.round(geometry.offset/geometry.pageWidth);
20542
+ if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
20543
+ visualPage=Math.min(total,Math.max(1,boundary+1));
20544
+ atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
20545
+ atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
20546
+ }
20547
+ }
20548
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
20549
+ 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};
20550
+ }
20551
+ function installResizeAnchor(manager,readCfi){
20552
+ var original=manager.resize,anchor=null;
20553
+ function resize(width,height,target){
20554
+ if(validCfi(target))anchor=target;
20555
+ if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
20556
+ // epub.js accepts a logical target as its third argument. Keep it across
20557
+ // keyboard/dialog reflows instead of adopting each new viewport start.
20558
+ return original.call(this,width,height,anchor || target);
20559
+ }
20560
+ manager.resize=resize;
20561
+ return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
20562
+ }
20563
+ function install(contents,config){
20564
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
20565
+ function point(e){var t=e.changedTouches&&e.changedTouches[0]||e.touches&&e.touches[0];return t?{x:t.clientX,y:t.clientY,time:Date.now()}:null;}
20566
+ function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20567
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay],[data-papyrus-ink-capture]'));}
20568
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20569
+ 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();}
20570
+ function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
20571
+ 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);}
20572
+ function cancel(){start=null;blocked=true;}
20573
+ function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
20574
+ 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);
20575
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
20576
+ }
20577
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
20578
+ })(typeof window==='undefined'?globalThis:window);
20579
+
20580
+ /* @papyrus-epub-reader:end */
20520
20581
  /* @papyrus-epub-annotations:start */
20521
20582
  (function (root, factory) {
20522
20583
  if (typeof module === 'object' && module.exports) module.exports = factory();
@@ -20528,6 +20589,84 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20528
20589
  typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
20529
20590
  typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
20530
20591
  }
20592
+ function simplifyPath(points, tolerance, maxPoints) {
20593
+ if (!Array.isArray(points) || points.length <= 2) return Array.isArray(points) ? points.slice() : [];
20594
+ tolerance = Number.isFinite(tolerance) ? Math.max(0, tolerance) : 1.5;
20595
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20596
+ var keep = new Uint8Array(points.length); keep[0] = 1; keep[points.length - 1] = 1;
20597
+ var stack = [[0, points.length - 1]], toleranceSquared = tolerance * tolerance;
20598
+ while (stack.length) {
20599
+ var interval = stack.pop(), first = interval[0], last = interval[1], a = points[first], b = points[last];
20600
+ var dx = b.x - a.x, dy = b.y - a.y, lengthSquared = dx * dx + dy * dy, maxDistance = toleranceSquared, split = -1;
20601
+ for (var i = first + 1; i < last; i++) {
20602
+ var p = points[i], ratio = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
20603
+ var px = a.x + ratio * dx, py = a.y + ratio * dy, ox = p.x - px, oy = p.y - py, distance = ox * ox + oy * oy;
20604
+ if (distance > maxDistance) { maxDistance = distance; split = i; }
20605
+ }
20606
+ if (split >= 0) { keep[split] = 1; stack.push([first, split], [split, last]); }
20607
+ }
20608
+ var result = []; for (var j = 0; j < points.length; j++) if (keep[j]) result.push(points[j]);
20609
+ if (result.length <= maxPoints) return result;
20610
+ var capped = []; for (var k = 0; k < maxPoints; k++) capped.push(result[Math.round(k * (result.length - 1) / (maxPoints - 1))]);
20611
+ return capped;
20612
+ }
20613
+ function compactGesturePoints(points, maxPoints) {
20614
+ if (!Array.isArray(points)) return [];
20615
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 1024;
20616
+ if (points.length <= maxPoints) return points.slice();
20617
+ var compacted = [];
20618
+ for (var i = 0; i < maxPoints; i++) compacted.push(points[Math.round(i * (points.length - 1) / (maxPoints - 1))]);
20619
+ return compacted;
20620
+ }
20621
+ function densifyPath(points, maxStep, maxPoints) {
20622
+ if (!Array.isArray(points) || !points.length) return [];
20623
+ if (points.length === 1) return points.slice();
20624
+ maxStep = Number.isFinite(maxStep) ? Math.max(1, maxStep) : 12;
20625
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20626
+ var cumulative = [0], total = 0;
20627
+ for (var i = 1; i < points.length; i++) {
20628
+ if (![points[i-1].x,points[i-1].y,points[i].x,points[i].y].every(Number.isFinite)) return [];
20629
+ total += Math.hypot(points[i].x-points[i-1].x, points[i].y-points[i-1].y);
20630
+ cumulative.push(total);
20631
+ }
20632
+ if (!total) return [points[0], points[points.length-1]];
20633
+ var intervals = Math.min(maxPoints-1, Math.max(1, Math.ceil(total/maxStep))), result = [], segment = 1;
20634
+ for (var sample = 0; sample <= intervals; sample++) {
20635
+ var distance = total*sample/intervals;
20636
+ while (segment < cumulative.length-1 && cumulative[segment] < distance) segment++;
20637
+ var start = points[segment-1], end = points[segment], span = cumulative[segment]-cumulative[segment-1];
20638
+ var ratio = span ? (distance-cumulative[segment-1])/span : 0;
20639
+ result.push({x:start.x+(end.x-start.x)*ratio,y:start.y+(end.y-start.y)*ratio});
20640
+ }
20641
+ return result;
20642
+ }
20643
+ function createSpatialIndex(entries, cellSize) {
20644
+ cellSize = Number.isFinite(cellSize) ? Math.max(8, cellSize) : 64;
20645
+ var cells = new Map();
20646
+ entries.forEach(function(entry) {
20647
+ var left = Math.floor(entry.left / cellSize), right = Math.floor(entry.right / cellSize);
20648
+ var top = Math.floor(entry.top / cellSize), bottom = Math.floor(entry.bottom / cellSize);
20649
+ for (var x = left; x <= right; x++) for (var y = top; y <= bottom; y++) {
20650
+ var key = x + ':' + y, bucket = cells.get(key); if (!bucket) { bucket = []; cells.set(key, bucket); } bucket.push(entry);
20651
+ }
20652
+ });
20653
+ return {cells:cells,cellSize:cellSize};
20654
+ }
20655
+ function queryNearestTextRect(index, x, y, maxDistance) {
20656
+ if (!index || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(maxDistance) || maxDistance < 0) return null;
20657
+ var size = index.cellSize, minX = Math.floor((x-maxDistance)/size), maxX = Math.floor((x+maxDistance)/size);
20658
+ var minY = Math.floor((y-maxDistance)/size), maxY = Math.floor((y+maxDistance)/size), seen = new Set(), best = null;
20659
+ for (var cx=minX; cx<=maxX; cx++) for (var cy=minY; cy<=maxY; cy++) {
20660
+ var bucket = index.cells.get(cx+':'+cy); if (!bucket) continue;
20661
+ bucket.forEach(function(entry) {
20662
+ if (seen.has(entry)) return; seen.add(entry);
20663
+ var nearestX = Math.max(entry.left,Math.min(entry.right,x)), nearestY = Math.max(entry.top,Math.min(entry.bottom,y));
20664
+ var dx=x-nearestX,dy=y-nearestY,distance=Math.sqrt(dx*dx+dy*dy);
20665
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={entry:entry,distance:distance,x:nearestX,y:nearestY};
20666
+ });
20667
+ }
20668
+ return best;
20669
+ }
20531
20670
  function textNodes(document) {
20532
20671
  var walker = document.createTreeWalker(document.body, 4);
20533
20672
  var nodes = [], text = '', node;
@@ -20577,13 +20716,196 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20577
20716
  if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
20578
20717
  doc.body.appendChild(overlay);
20579
20718
  var registry = new Map(), hitRegions = [];
20719
+ var inkRegistry = new Map(), rangeCache = new Map(), spatialIndex = null, activeGesture = null, inkConfig = {enabled:false,preset:'ink'};
20720
+ var inkSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
20721
+ inkSvg.setAttribute('data-papyrus-ink-overlay','');
20722
+ inkSvg.style.cssText = 'position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1';
20723
+ overlay.appendChild(inkSvg);
20724
+ var inkCanvas = doc.createElement('canvas');
20725
+ inkCanvas.dataset.papyrusInkCapture = '';
20726
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
20727
+ doc.body.appendChild(inkCanvas);
20728
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
20729
+ function resizeCanvas() {
20730
+ var bodyRect=doc.body.getBoundingClientRect(), ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
20731
+ var width=Math.max(1,Math.min(4096,doc.body.clientWidth||bodyRect.width||Math.min(4096,win.innerWidth||1)));
20732
+ var height=Math.max(1,Math.min(4096,doc.body.clientHeight||bodyRect.height||Math.min(4096,win.innerHeight||1)));
20733
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
20734
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
20735
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
20736
+ }
20737
+ function collectTextRects() {
20738
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
20739
+ while ((node=walker.nextNode())) {
20740
+ var parent=node.parentElement;
20741
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
20742
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
20743
+ for (var start=0; start<text.length;) {
20744
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
20745
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
20746
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
20747
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20748
+ rects.forEach(function(rect) {
20749
+ if (!(rect.width>0 && rect.height>0)) return;
20750
+ entries.push({left:rect.left+win.scrollX,top:rect.top+win.scrollY,right:rect.right+win.scrollX,bottom:rect.bottom+win.scrollY,width:rect.width,height:rect.height,
20751
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
20752
+ });
20753
+ start=end;
20754
+ }
20755
+ }
20756
+ return entries;
20757
+ }
20758
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
20759
+ function inkViewportExtent() {
20760
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
20761
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
20762
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
20763
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
20764
+ }
20765
+ function inkStrokeWidthPx(width,preset) {
20766
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
20767
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
20768
+ }
20769
+ function closestGlyph(entry,x,y,maxDistance) {
20770
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
20771
+ for (var glyph of source) {
20772
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
20773
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20774
+ rects.forEach(function(rect) {
20775
+ if (!(rect.width>0 && rect.height>0)) return;
20776
+ var nx=Math.max(rect.left+win.scrollX,Math.min(rect.right+win.scrollX,x)), ny=Math.max(rect.top+win.scrollY,Math.min(rect.bottom+win.scrollY,y));
20777
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
20778
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
20779
+ });
20780
+ }
20781
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
20782
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
20783
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\\([^\\n<>]+\\)$/.test(cfiRange)) return null;
20784
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
20785
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
20786
+ }
20787
+ function renderInkAnnotation(annotation,keep,bodyRect) {
20788
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
20789
+ var data=annotation.epubInk;
20790
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
20791
+ keep.add(annotation.id);
20792
+ var group=inkRegistry.get(annotation.id);
20793
+ if (!group) { group=doc.createElementNS('http://www.w3.org/2000/svg','g'); group.setAttribute('data-epub-ink-id',annotation.id); inkSvg.appendChild(group); inkRegistry.set(annotation.id,group); }
20794
+ group.replaceChildren();
20795
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
20796
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
20797
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
20798
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
20799
+ data.segments.forEach(function(segment) {
20800
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
20801
+ var paths=[], current=[];
20802
+ segment.points.forEach(function(point) {
20803
+ var range=rangeCache.get(point.cfiRange);
20804
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
20805
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
20806
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
20807
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
20808
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
20809
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
20810
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
20811
+ current.push({x:x,y:y,lineHeight:lineHeight});
20812
+ });
20813
+ if(current.length>1)paths.push(current);
20814
+ paths.forEach(function(points) {
20815
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
20816
+ for(var i=1;i<points.length;i++){var prev=points[i-1],point=points[i],mx=(prev.x+point.x)/2,my=(prev.y+point.y)/2;d+=' Q '+prev.x.toFixed(1)+' '+prev.y.toFixed(1)+' '+mx.toFixed(1)+' '+my.toFixed(1);}
20817
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
20818
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');path.setAttribute('d',d);path.setAttribute('fill','none');path.setAttribute('stroke',color);path.setAttribute('stroke-width',String(stroke));path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(opacity));group.appendChild(path);
20819
+ });
20820
+ });
20821
+ }
20822
+ function clearPreview() {
20823
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
20824
+ inkSvg.querySelectorAll('[data-papyrus-ink-preview]').forEach(function(path){path.remove();});
20825
+ }
20826
+ function createPreviewPath(x,y) {
20827
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
20828
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
20829
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
20830
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
20831
+ path.style.pointerEvents='none';inkSvg.appendChild(path);return path;
20832
+ }
20833
+ function updatePreviewPath(clientX,clientY) {
20834
+ var bodyRect=doc.body.getBoundingClientRect(),x=clientX-bodyRect.left,y=clientY-bodyRect.top;
20835
+ var gesture=activeGesture;
20836
+ if(!gesture.previewPath||gesture.previewPathPoints>=32){
20837
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
20838
+ gesture.previewPathPoints=1;
20839
+ }
20840
+ var pathData=gesture.previewPath.getAttribute('d')||'';
20841
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
20842
+ gesture.previewPathPoints++;
20843
+ gesture.previewLast={x:x,y:y};
20844
+ }
20845
+ function cancelGesture() {
20846
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20847
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
20848
+ }
20849
+ function commitGesture() {
20850
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20851
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
20852
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
20853
+ anchorSamples.forEach(function(point) {
20854
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
20855
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
20856
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
20857
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
20858
+ current.push(anchored);
20859
+ });
20860
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
20861
+ segments=segments.slice(0,64);
20862
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
20863
+ if(!pointCount)return;
20864
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
20865
+ var normalized=simplified.map(function(p){return {x:Math.max(0,Math.min(1,p.x/width)),y:Math.max(0,Math.min(1,p.y/height))};});
20866
+ var minX=Math.min.apply(null,normalized.map(function(p){return p.x;})),maxX=Math.max.apply(null,normalized.map(function(p){return p.x;}));
20867
+ var minY=Math.min.apply(null,normalized.map(function(p){return p.y;})),maxY=Math.max.apply(null,normalized.map(function(p){return p.y;}));
20868
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
20869
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
20870
+ rect:{x:minX,y:minY,width:Math.min(1-minX,Math.max(0.001,maxX-minX)),height:Math.min(1-minY,Math.max(0.001,maxY-minY))},path:normalized,color:inkConfig.color,opacity:inkConfig.opacity,strokeWidth:inkConfig.strokeWidth,createdAt:Date.now(),content:first.quote,
20871
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
20872
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
20873
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
20874
+ }
20875
+ function pointerDown(event) {
20876
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
20877
+ if(activeGesture){cancelGesture();return;}
20878
+ event.preventDefault();event.stopPropagation();
20879
+ activeGesture={pointerId:event.pointerId,points:[{x:event.clientX+win.scrollX,y:event.clientY+win.scrollY}],last:{x:event.clientX,y:event.clientY},previewPath:null,previewPathPoints:0,previewLast:null};
20880
+ updatePreviewPath(event.clientX,event.clientY);
20881
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
20882
+ }
20883
+ function pointerMove(event) {
20884
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
20885
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
20886
+ event.preventDefault();event.stopPropagation();
20887
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
20888
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
20889
+ if(inkContext){inkContext.beginPath();inkContext.moveTo(activeGesture.last.x,activeGesture.last.y);inkContext.lineTo(event.clientX,event.clientY);inkContext.strokeStyle=inkConfig.color;inkContext.globalAlpha=inkConfig.opacity;inkContext.lineWidth=inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset);inkContext.lineCap='round';inkContext.lineJoin='round';inkContext.stroke();}
20890
+ updatePreviewPath(event.clientX,event.clientY);
20891
+ activeGesture.last={x:event.clientX,y:event.clientY};
20892
+ }
20893
+ function pointerUp(event) {if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;event.preventDefault();event.stopPropagation();var end={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY},last=activeGesture.points[activeGesture.points.length-1];if(Math.hypot(end.x-last.x,end.y-last.y)>0.5){activeGesture.points.push(end);updatePreviewPath(event.clientX,event.clientY);}commitGesture();}
20894
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
20895
+ resizeCanvas();rebuildSpatialIndex();
20896
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
20897
+ win.addEventListener('blur',pointerCancel);win.addEventListener('resize',resizeCanvas);
20580
20898
  function render() {
20581
20899
  frame = 0; if (!alive || !options.isCurrent()) return;
20582
- var keep = new Set(), noteGroups = new Map();
20900
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20583
20901
  hitRegions = [];
20902
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
20903
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
20904
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
20584
20905
  options.annotations().forEach(function(annotation) {
20585
20906
  var anchor = annotation.anchor;
20586
20907
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
20908
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
20587
20909
  var range = resolveRange(contents,anchor); if (!range) return;
20588
20910
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20589
20911
  if (!rects.length) return;
@@ -20595,6 +20917,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20595
20917
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
20596
20918
  var style = annotation.markupStyle || annotation.type;
20597
20919
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
20920
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
20598
20921
  rects.forEach(function(rect) {
20599
20922
  var mark = doc.createElement('div');
20600
20923
  var bodyRect=doc.body.getBoundingClientRect();
@@ -20607,8 +20930,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20607
20930
  if (hasNote) {
20608
20931
  var last = rects[rects.length-1], button = doc.createElement('button');
20609
20932
  button.textContent = '\u25CF'; button.setAttribute('aria-label',options.labels().comment || '');
20610
- button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+Math.max(0,Math.min(doc.documentElement.clientWidth-44,last.right-doc.body.getBoundingClientRect().left))+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
20611
- var key = Math.floor(last.top/44);
20933
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
20934
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
20935
+ button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+((column+1)*pageWidth-44)+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
20936
+ var key = column+":"+Math.floor(last.top/44);
20612
20937
  var neighbors=noteGroups.get(key);
20613
20938
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
20614
20939
  neighbors.ids.push(annotation.id);
@@ -20619,22 +20944,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20619
20944
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20620
20945
  });
20621
20946
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
20947
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
20622
20948
  }
20623
20949
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
20950
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
20951
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
20952
+ function handleDocumentLoad(){rebuildAndSchedule();}
20624
20953
  function tapMarkup(event) {
20625
20954
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20626
20955
  var hit=hitRegions.find(function(item){return item.rects.some(function(r){return event.clientX>=r.left && event.clientX<=r.right && event.clientY>=r.top && event.clientY<=r.bottom;});});
20627
20956
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20628
20957
  }
20629
20958
  doc.addEventListener('click',tapMarkup);
20630
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20959
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20631
20960
  if (observer) observer.observe(doc.body);
20632
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
20633
- if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20961
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
20962
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
20634
20963
  schedule();
20635
- return {schedule:schedule, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
20964
+ return {schedule:schedule, setInkMode:function(config){var preset=['ink','highlight','underline'].indexOf(config?.preset)>=0?config.preset:'ink';inkConfig={enabled:!!config?.enabled,preset:preset,color:/^#[0-9a-f]{6}$/i.test(config?.color)?config.color:'#2563eb',opacity:Number.isFinite(config?.opacity)?Math.max(0,Math.min(1,config.opacity)):1,strokeWidth:Number.isFinite(config?.strokeWidth)?Math.max(0.0025,Math.min(0.02,config.strokeWidth)):0.006};inkCanvas.style.pointerEvents=inkConfig.enabled?'auto':'none';if(!inkConfig.enabled)cancelGesture();}, invalidateLayout:rebuildAndSchedule, hit:function(x,y){return hitRegions.some(function(item){return item.rects.some(function(r){return x>=r.left && x<=r.right && y>=r.top && y<=r.bottom;});});}, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;cancelGesture();if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',handleResize);win.removeEventListener('scroll',schedule);win.removeEventListener('blur',pointerCancel);doc.removeEventListener('load',handleDocumentLoad,true);doc.removeEventListener('click',tapMarkup);inkCanvas.removeEventListener('pointerdown',pointerDown);inkCanvas.removeEventListener('pointermove',pointerMove);inkCanvas.removeEventListener('pointerup',pointerUp);inkCanvas.removeEventListener('pointercancel',pointerCancel);inkCanvas.removeEventListener('lostpointercapture',pointerCancel);inkCanvas.remove();overlay.remove();doc.body.style.position=originalPosition;registry.clear();inkRegistry.clear();rangeCache.clear();}};
20636
20965
  }
20637
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20966
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20638
20967
  });
20639
20968
 
20640
20969
  /* @papyrus-epub-annotations:end */
@@ -20645,10 +20974,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20645
20974
  let currentType = null;
20646
20975
  let book = null;
20647
20976
  let rendition = null;
20977
+ let epubResizeAnchor = null;
20648
20978
  let documentSessionId = null;
20649
20979
  let annotationDocumentId;
20650
20980
  let annotationLabels = {};
20651
20981
  let epubAnnotations = [];
20982
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20652
20983
  const epubAnnotationRenderers = new Map();
20653
20984
  let spineItems = [];
20654
20985
  let textPages = [];
@@ -21057,6 +21388,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21057
21388
  };
21058
21389
 
21059
21390
  const clearViewer = () => {
21391
+ if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
21060
21392
  epubAnnotationRenderers.forEach(renderer => renderer.destroy());
21061
21393
  epubAnnotationRenderers.clear();
21062
21394
  if (epubInteractionCleanup) {
@@ -21713,29 +22045,63 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21713
22045
  pageCount,
21714
22046
  });
21715
22047
 
22048
+ if (book.locations?.generate) {
22049
+ const activeBook=book;
22050
+ activeBook.locations.generate(150).then(() => {
22051
+ if (isCurrentLoad() && activeBook===book && epubLocation) {
22052
+ epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
22053
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
22054
+ }
22055
+ }).catch(() => {});
22056
+ }
22057
+ return renderEpubRendition(loadId,generation,null);
22058
+ };
22059
+
22060
+ const renderEpubRendition = async (loadId,generation,target) => {
22061
+ const diagnosticPayload={loadId};
22062
+ const isCurrentLoad=()=>generation===epubLoadGeneration;
22063
+ if(epubInteractionCleanup)epubInteractionCleanup();
22064
+ if(rendition)rendition.destroy();
21716
22065
  clearViewer();
21717
- rendition = book.renderTo(viewer, {
21718
- manager: 'continuous',
21719
- allowScriptedContent: false,
21720
- width: '100%',
21721
- height: '100%',
21722
- flow: 'scrolled-continuous',
21723
- spread: 'none',
21724
- });
22066
+ rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
21725
22067
  sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
21726
22068
 
21727
22069
  if (rendition && rendition.hooks && rendition.hooks.content) {
21728
22070
  const session = documentSessionId;
21729
22071
  const activeRendition = rendition;
21730
22072
  rendition.hooks.content.register((contents) => {
22073
+ const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
22074
+ mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
22075
+ rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
22076
+ blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
22077
+ annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
22078
+ current: () => session === documentSessionId && activeRendition === rendition,
22079
+ interact: () => epubResizeAnchor?.invalidate(),
22080
+ tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
22081
+ turn: direction => {
22082
+ epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
22083
+ if(session !== documentSessionId || activeRendition !== rendition)return;
22084
+ epubResizeAnchor?.invalidate();
22085
+ return direction<0?activeRendition.prev():activeRendition.next();
22086
+ });
22087
+ epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
22088
+ },
22089
+ });
22090
+ const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
22091
+ epubSelectionCleanups.add(gestureCleanup);
22092
+ contents.__papyrusGestureCleanup=gestureCleanup;
21731
22093
  const href = spineItems[contents.sectionIndex]?.href;
21732
22094
  if (href && window.PapyrusEpubAnnotations) {
21733
22095
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21734
22096
  href, get documentId() { return annotationDocumentId; },
22097
+ spineIndex:contents.sectionIndex,
21735
22098
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
22099
+ pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21736
22100
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22101
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21737
22102
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21738
22103
  });
22104
+ renderer.setInkMode(epubInkMode);
21739
22105
  epubAnnotationRenderers.set(contents,renderer);
21740
22106
  }
21741
22107
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21751,8 +22117,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21751
22117
  typeof selectionWindow.getSelection === 'function'
21752
22118
  ) {
21753
22119
  let selectionTimer = null;
21754
- let lastSelectionKey = '';
21755
- let lastSelectionAt = 0;
22120
+
21756
22121
  const reportSelection = () => {
21757
22122
  selectionTimer = null;
21758
22123
  if (session !== documentSessionId || activeRendition !== rendition) return;
@@ -21767,11 +22132,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21767
22132
  try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
21768
22133
  const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
21769
22134
  if (!anchor) return;
21770
- const key = anchor.cfiRange + ':' + anchor.quote;
21771
- const now = Date.now();
21772
- if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
21773
- lastSelectionKey = key;
21774
- lastSelectionAt = now;
22135
+ if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21775
22136
  const payload = {documentSessionId: session, text: anchor.quote,
21776
22137
  pageIndex: contents.sectionIndex + 1, anchor};
21777
22138
  nativeMenuSelection = {contents, payload};
@@ -21786,6 +22147,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21786
22147
  // The content document still emits selectionchange, so resolve its
21787
22148
  // live Range to a CFI before the native menu action can fire.
21788
22149
  if (text) reportSelection();
22150
+ else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
21789
22151
  };
21790
22152
  selectionDocument.addEventListener('selectionchange', handleSelectionChange);
21791
22153
  const selectionCleanup = () => {
@@ -21808,6 +22170,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21808
22170
  typeof rendition.hooks.unloaded.register === 'function'
21809
22171
  ) {
21810
22172
  rendition.hooks.unloaded.register((view) => {
22173
+ view?.contents?.__papyrusGestureCleanup?.();
21811
22174
  const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
21812
22175
  if (typeof cleanup === 'function') cleanup();
21813
22176
  const renderer = epubAnnotationRenderers.get(view?.contents);
@@ -21823,21 +22186,37 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21823
22186
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21824
22187
  const href = spineItems[contents.sectionIndex]?.href;
21825
22188
  const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
21826
- if (!anchor) return;
22189
+ if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21827
22190
  epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
21828
22191
  const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
21829
22192
  nativeMenuSelection = {contents, payload};
21830
22193
  sendEvent('EPUB_TEXT_SELECTED', payload);
21831
22194
  });
21832
- ['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
21833
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22195
+ ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
22196
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21834
22197
  }));
21835
22198
  rendition.on('relocated', (location) => {
22199
+ if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21836
22200
  const start = location && location.start;
21837
22201
  let index = start && Number.isInteger(start.index) ? start.index : -1;
21838
22202
  if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
21839
22203
  if (index < 0 || index >= spineItems.length) return;
21840
22204
  epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
22205
+ let pageGeometry;
22206
+ const manager=rendition.manager,layout=manager?.layout;
22207
+ if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
22208
+ const visible=manager.visible?.();
22209
+ const view=visible?.find(view=>view.index===index);
22210
+ if(view && manager.container){
22211
+ pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
22212
+ }
22213
+ }
22214
+ epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
22215
+ if(epubLocation){
22216
+ epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
22217
+ if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
22218
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
22219
+ }
21841
22220
  const nextPage = index + 1;
21842
22221
  if (nextPage === currentPage) return;
21843
22222
  currentPage = nextPage;
@@ -21845,21 +22224,23 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21845
22224
  });
21846
22225
  }
21847
22226
 
22227
+ applyEpubZoom();
21848
22228
  sendEpubDiagnostic('epub.display.start', diagnosticPayload);
21849
22229
  await withEpubStageTimeout(
21850
- displayEpubPage(0),
22230
+ rendition.display(target || spineItems[0]?.href),
21851
22231
  'rendition.display',
21852
22232
  diagnosticPayload,
21853
22233
  );
21854
22234
  if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
21855
22235
  sendEpubDiagnostic('epub.display.end', diagnosticPayload);
22236
+ const resizeRendition=rendition;
22237
+ epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
21856
22238
  if (
21857
22239
  rendition &&
21858
22240
  rendition.manager &&
21859
22241
  typeof rendition.manager.on === 'function' &&
21860
22242
  typeof rendition.on === 'function'
21861
22243
  ) {
21862
- let touchStart = null;
21863
22244
  let lastScrollOffset = -1;
21864
22245
  const manager = rendition.manager;
21865
22246
  epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
@@ -21934,48 +22315,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21934
22315
  return result;
21935
22316
  };
21936
22317
  }
21937
- const getTouchPoint = (event) => {
21938
- const touch =
21939
- (event && event.changedTouches && event.changedTouches[0]) ||
21940
- (event && event.touches && event.touches[0]);
21941
- if (!touch) return null;
21942
- const x = Number(touch.screenX ?? touch.clientX);
21943
- const y = Number(touch.screenY ?? touch.clientY);
21944
- return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
21945
- };
21946
- const handleScroll = (event) => {
22318
+ const handleScroll = event => {
21947
22319
  const offsetY = Math.max(0, Number(event && event.top) || 0);
21948
22320
  epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
21949
- if (Math.abs(offsetY - lastScrollOffset) < 1) return;
21950
- lastScrollOffset = offsetY;
21951
- sendEvent('VIEWER_SCROLL', { offsetY });
21952
- };
21953
- const handleTouchStart = (event) => {
21954
- touchStart = getTouchPoint(event);
21955
- epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
21956
- };
21957
- const handleTouchMove = (event) => {
21958
- epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
22321
+ if (Math.abs(offsetY-lastScrollOffset)<1) return;
22322
+ lastScrollOffset=offsetY;
22323
+ sendEvent('VIEWER_SCROLL',{offsetY});
21959
22324
  };
21960
- const handleTouchEnd = (event) => {
21961
- const end = getTouchPoint(event);
21962
- const start = touchStart;
21963
- touchStart = null;
21964
- const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
21965
- if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
21966
- sendEvent('VIEWER_TAP', {});
21967
- }
21968
- epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
21969
- };
21970
-
21971
22325
  manager.on('scroll', handleScroll);
21972
22326
  const handleScrolled = (event) => {
21973
22327
  epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
21974
22328
  };
21975
22329
  manager.on('scrolled', handleScrolled);
21976
- rendition.on('touchstart', handleTouchStart);
21977
- rendition.on('touchmove', handleTouchMove);
21978
- rendition.on('touchend', handleTouchEnd);
22330
+
21979
22331
  epubInteractionCleanup = () => {
21980
22332
  epubSelectionCleanups.forEach((cleanup) => cleanup());
21981
22333
  epubSelectionCleanups.clear();
@@ -21984,16 +22336,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21984
22336
  if (originalEnqueue) queue.enqueue = originalEnqueue;
21985
22337
  if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
21986
22338
  if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
21987
- if (typeof rendition.off === 'function') {
21988
- rendition.off('touchstart', handleTouchStart);
21989
- rendition.off('touchmove', handleTouchMove);
21990
- rendition.off('touchend', handleTouchEnd);
21991
- }
21992
22339
  epubScrollDiagnostics = null;
21993
22340
  };
21994
22341
  }
21995
- applyEpubZoom();
21996
-
21997
22342
  const outline = await withEpubStageTimeout(
21998
22343
  buildOutline(),
21999
22344
  'outline',
@@ -22007,6 +22352,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22007
22352
  return { pageCount, outline };
22008
22353
  };
22009
22354
 
22355
+ let epubNavigationQueue = Promise.resolve();
22356
+ let epubNavigationRequest = 0;
22010
22357
  const displayEpubPage = async (pageIndex) => {
22011
22358
  if (!rendition) return;
22012
22359
  const item = spineItems[pageIndex];
@@ -22016,9 +22363,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22016
22363
  target,
22017
22364
  pageIndex,
22018
22365
  });
22019
- await rendition.display(target);
22020
- currentPage = pageIndex + 1;
22021
- sendState();
22366
+ const activeRendition = rendition;
22367
+ const session = documentSessionId;
22368
+ const request = ++epubNavigationRequest;
22369
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22370
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22371
+ epubResizeAnchor?.invalidate();
22372
+ await activeRendition.display(target);
22373
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22374
+ currentPage = pageIndex + 1;
22375
+ sendState();
22376
+ });
22377
+ epubNavigationQueue = navigation;
22378
+ await navigation;
22022
22379
  };
22023
22380
 
22024
22381
  const getTextContent = async (pageIndex) => {
@@ -22120,6 +22477,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22120
22477
  };
22121
22478
 
22122
22479
  let nativeMenuSelection = null;
22480
+ let epubViewMode = "single";
22481
+ let epubLocation = null;
22123
22482
 
22124
22483
  const handleCommand = async (message) => {
22125
22484
  const { id, kind, payload } = message;
@@ -22131,7 +22490,13 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22131
22490
  documentSessionId = payload.documentSessionId || null;
22132
22491
  epubAnnotations = [];
22133
22492
  annotationDocumentId = undefined;
22493
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22134
22494
  zoom = 1.0;
22495
+ epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22496
+ epubLocation = null;
22497
+ nativeMenuSelection = null;
22498
+ epubNavigationRequest += 1;
22499
+ epubNavigationQueue = Promise.resolve();
22135
22500
 
22136
22501
  if (currentType === 'text') {
22137
22502
  const result = await loadText(
@@ -22186,17 +22551,24 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22186
22551
  epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22187
22552
  } else {
22188
22553
  const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
22189
- if (!annotation) throw new Error('Annotation not available');
22554
+ if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
22190
22555
  // Navigation uses the CFI, never a synthetic percentage/page rectangle.
22191
22556
  const navigationSession = documentSessionId;
22192
22557
  const activeRendition = rendition;
22193
22558
  // Open the confirmed chapter first, resolve its quote, then navigate its actual range.
22194
- await activeRendition.display(annotation.anchor.href);
22195
- if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22196
- const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22197
- const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22198
- if(!range)throw new Error('EPUB anchor unresolved');
22199
- await activeRendition.display(contents.cfiFromRange(range));
22559
+ epubNavigationRequest += 1;
22560
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22561
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22562
+ epubResizeAnchor?.invalidate();
22563
+ await activeRendition.display(annotation.anchor.href);
22564
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22565
+ const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22566
+ const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22567
+ if(!range)throw new Error('EPUB anchor unresolved');
22568
+ await activeRendition.display(contents.cfiFromRange(range));
22569
+ });
22570
+ epubNavigationQueue = navigation;
22571
+ await navigation;
22200
22572
  }
22201
22573
  sendResponse(id,true,null); return;
22202
22574
  }
@@ -22223,12 +22595,61 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22223
22595
  return;
22224
22596
  }
22225
22597
 
22598
+ if (kind === 'epub-turn-page') {
22599
+ if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
22600
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
22601
+ const active = rendition;
22602
+ epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
22603
+ if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
22604
+ epubResizeAnchor?.invalidate();
22605
+ return payload.direction < 0 ? active.prev() : active.next();
22606
+ });
22607
+ await epubNavigationQueue;
22608
+ sendResponse(id,true,null); return;
22609
+ }
22610
+
22611
+ if (kind === 'set-epub-view-mode') {
22612
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
22613
+ const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
22614
+ {
22615
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22616
+ if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
22617
+ const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22618
+ epubViewMode=next;
22619
+ await renderEpubRendition('layout',epubLoadGeneration,target);
22620
+ });
22621
+ await epubNavigationQueue;
22622
+ }
22623
+ sendResponse(id,true,null);return;
22624
+ }
22625
+ if (kind === 'set-epub-ink-mode') {
22626
+ if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
22627
+ epubInkMode={enabled:payload.enabled===true,preset:['ink','highlight','underline'].indexOf(payload.preset)>=0?payload.preset:'ink',color:/^#[0-9a-f]{6}$/i.test(payload.color||'')?payload.color:'#2563eb',opacity:Number.isFinite(payload.opacity)?Math.max(0,Math.min(1,payload.opacity)):1,strokeWidth:Number.isFinite(payload.strokeWidth)?Math.max(0.0025,Math.min(0.02,payload.strokeWidth)):0.006};
22628
+ epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
22629
+ sendResponse(id,true,null);return;
22630
+ }
22631
+ if(kind === 'epub-go-to-location'){
22632
+ if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22633
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
22634
+ await epubNavigationQueue;sendResponse(id,true,null);return;
22635
+ }
22636
+
22226
22637
  if (kind === 'set-zoom') {
22227
- zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
22228
- applyZoom();
22229
- sendState();
22230
- sendResponse(id, true, { zoom });
22231
- return;
22638
+ const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
22639
+ if(currentType === 'epub') {
22640
+ const session=documentSessionId;
22641
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22642
+ if(session!==documentSessionId)return;
22643
+ const active=rendition;
22644
+ const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22645
+ zoom=requestedZoom;applyEpubZoom();
22646
+ // Let the content CSS update before redisplaying the logical anchor.
22647
+ await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
22648
+ if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
22649
+ });
22650
+ await epubNavigationQueue;
22651
+ } else {zoom=requestedZoom;applyZoom();}
22652
+ sendState();sendResponse(id,true,{zoom});return;
22232
22653
  }
22233
22654
 
22234
22655
  if (kind === 'set-rotation') {
@@ -23391,6 +23812,7 @@ var IconToolStrikeout = ({
23391
23812
  ),
23392
23813
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Rect, { x: "10.9", y: "17.1", width: "14.2", height: "2.4", rx: "1.2", fill: color })
23393
23814
  ] });
23815
+ var IconStrikeout = ({ size = 22, color = defaultColor, strokeWidth = 1.8 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Path, { d: "M17 5c-1-1-3-2-5-2-3 0-5 2-5 4 0 2 2 3 5 4M17 15c0 2-2 4-5 4-2 0-4-1-5-2M4 12h16", stroke: color, strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) });
23394
23816
  var IconCopy = ({
23395
23817
  size = 20,
23396
23818
  color = defaultColor,
@@ -23455,6 +23877,39 @@ var IconToolNote = ({
23455
23877
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Line, { x1: "15", y1: "14.8", x2: "21.6", y2: "14.8", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" }),
23456
23878
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Line, { x1: "15", y1: "18.2", x2: "19.2", y2: "18.2", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" })
23457
23879
  ] });
23880
+ var IconHighlight = ({
23881
+ size = 24,
23882
+ color = "#fbbf24"
23883
+ }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: [
23884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23885
+ import_react_native_svg.Path,
23886
+ {
23887
+ d: "M4 15h16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3Z",
23888
+ fill: color,
23889
+ opacity: "0.85"
23890
+ }
23891
+ ),
23892
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23893
+ import_react_native_svg.Path,
23894
+ {
23895
+ d: "M6 5h12v10H6V5Z",
23896
+ stroke: color,
23897
+ strokeWidth: "2",
23898
+ strokeLinecap: "round",
23899
+ strokeLinejoin: "round",
23900
+ fill: "none"
23901
+ }
23902
+ ),
23903
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23904
+ import_react_native_svg.Path,
23905
+ {
23906
+ d: "M8 8h8M8 11h5",
23907
+ stroke: color,
23908
+ strokeWidth: "2",
23909
+ strokeLinecap: "round"
23910
+ }
23911
+ )
23912
+ ] });
23458
23913
  var IconUnderline = ({
23459
23914
  size = 24,
23460
23915
  color = "#60a5fa",
@@ -25883,8 +26338,38 @@ var styles = import_react_native.StyleSheet.create({
25883
26338
  var arePageRendererPropsEqual = (previous, next) => previous.documentId === next.documentId && previous.engine === next.engine && previous.pageIndex === next.pageIndex && previous.scale === next.scale && previous.PageViewComponent === next.PageViewComponent && previous.availableWidth === next.availableWidth && previous.maxPageWidth === next.maxPageWidth && previous.maxPageHeight === next.maxPageHeight && previous.pageViewportWidth === next.pageViewportWidth && previous.horizontalPadding === next.horizontalPadding && previous.spacing === next.spacing && previous.onSelectionDragActiveChange === next.onSelectionDragActiveChange && previous.gestureScrollLockActive === next.gestureScrollLockActive && previous.lastPinchEndedAt === next.lastPinchEndedAt && previous.requestSelectionVerticalAutoscroll === next.requestSelectionVerticalAutoscroll;
25884
26339
  var PageRenderer_default = (0, import_react2.memo)(PageRenderer, arePageRendererPropsEqual);
25885
26340
 
26341
+ // components/PapyrusSafeArea.tsx
26342
+ var import_react3 = __toESM(require("react"));
26343
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
26344
+ var import_jsx_runtime4 = require("react/jsx-runtime");
26345
+ var papyrusSafeAreaContext = import_react_native_safe_area_context.SafeAreaInsetsContext;
26346
+ var ZERO_SAFE_AREA_INSETS = {
26347
+ top: 0,
26348
+ bottom: 0,
26349
+ left: 0,
26350
+ right: 0
26351
+ };
26352
+ var usePapyrusSafeAreaInsets = () => (0, import_react3.useContext)(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
26353
+ function PapyrusSafeAreaBoundary({
26354
+ children
26355
+ }) {
26356
+ const insets = (0, import_react3.useContext)(papyrusSafeAreaContext);
26357
+ if (insets) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
26358
+ return import_react3.default.createElement(import_react_native_safe_area_context.SafeAreaProvider, null, children);
26359
+ }
26360
+
26361
+ // components/epubReadingLocation.ts
26362
+ function parseEpubReadingLocation(raw) {
26363
+ if (!raw || typeof raw !== "object") return null;
26364
+ const p = raw;
26365
+ if (p.version !== 1 || typeof p.cfi !== "string" || p.cfi.length > 4096 || !/^epubcfi\([^\n<>]+\)$/.test(p.cfi) || typeof p.href !== "string" || !Number.isSafeInteger(p.chapter) || p.chapter < 1 || !Number.isSafeInteger(p.chapterCount) || p.chapterCount < p.chapter || !Number.isFinite(p.progress) || p.progress < 0 || p.progress > 1 || typeof p.atStart !== "boolean" || typeof p.atEnd !== "boolean") return null;
26366
+ if (p.mode === "chapter-scroll") return p.visualPage === null && p.visualPageCount === null ? p : null;
26367
+ if (p.mode !== "paged" || !Number.isSafeInteger(p.visualPage) || (p.visualPage ?? 0) < 1 || !Number.isSafeInteger(p.visualPageCount) || (p.visualPageCount ?? 0) < (p.visualPage ?? 0)) return null;
26368
+ return p;
26369
+ }
26370
+
25886
26371
  // components/WebViewViewer.tsx
25887
- var import_react3 = require("react");
26372
+ var import_react4 = require("react");
25888
26373
  var import_react_native2 = require("react-native");
25889
26374
  var import_clipboard3 = __toESM(require("@react-native-clipboard/clipboard"));
25890
26375
  var import_react_native_webview = __toESM(require("react-native-webview"));
@@ -25935,8 +26420,14 @@ var parseWebViewInteraction = (raw) => {
25935
26420
 
25936
26421
  // components/epubAnnotationEvents.ts
25937
26422
  var import_core3 = require("@papyrus-sdk/core");
26423
+ var EPUB_INK_PRESETS = ["ink", "highlight", "underline"];
26424
+ function resolveEpubInkMode(input) {
26425
+ if (!input.isLoaded || input.documentType !== "epub") return null;
26426
+ const preset = EPUB_INK_PRESETS.includes(input.preset) ? input.preset : "ink";
26427
+ return { enabled: input.activeTool === "ink" && EPUB_INK_PRESETS.includes(input.preset), preset, color: input.color, opacity: input.opacity, strokeWidth: input.strokeWidth };
26428
+ }
25938
26429
  function parseEpubAnnotationEvent(raw, session, documentId) {
25939
- if (!session || raw.length > 2e5) return null;
26430
+ if (!session || raw.length > 75e4) return null;
25940
26431
  let message;
25941
26432
  try {
25942
26433
  message = JSON.parse(raw);
@@ -25946,13 +26437,20 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
25946
26437
  const p = message?.payload;
25947
26438
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
25948
26439
  if (message.name === "EPUB_ANNOTATION_TAP") return typeof p.id === "string" && p.id.length <= 256 ? { kind: "tap", id: p.id, ids: Array.isArray(p.ids) && p.ids.length <= 100 && p.ids.every((id) => typeof id === "string" && id.length <= 256) ? p.ids : void 0 } : null;
26440
+ if (message.name === "EPUB_INK_CREATED") {
26441
+ const a = p.annotation, sidecar = a?.epubInk, first = sidecar?.segments?.[0], point = first?.points?.[0];
26442
+ if (!a || a.type !== "ink" || typeof a.id !== "string" || !a.id || a.id.length > 256 || !Number.isSafeInteger(a.pageIndex) || a.pageIndex < 1 || !(0, import_core3.validateAnnotationAnchor)(a.anchor) || a.anchor.kind !== "epub-cfi" || !(0, import_core3.validateEpubInkData)(sidecar) || !point || a.anchor.cfiRange !== point.cfiRange || a.anchor.quote !== point.quote || a.anchor.href !== first.href || documentId && (sidecar.documentId && sidecar.documentId !== documentId || a.anchor.documentId && a.anchor.documentId !== documentId) || sidecar.documentId && a.anchor.documentId && sidecar.documentId !== a.anchor.documentId) return null;
26443
+ const rect = a.rect, path = a.path;
26444
+ if (!rect || ![rect.x, rect.y, rect.width, rect.height].every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 1) || rect.x + rect.width > 1.000001 || rect.y + rect.height > 1.000001 || !Array.isArray(path) || path.length < 2 || path.length > 512 || path.some((p2) => !p2 || ![p2.x, p2.y].every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 1)) || typeof a.color !== "string" || !/^#[0-9a-f]{6}$/i.test(a.color) || !Number.isFinite(a.createdAt) || a.opacity !== void 0 && (!Number.isFinite(a.opacity) || a.opacity < 0 || a.opacity > 1) || a.strokeWidth !== void 0 && (!Number.isFinite(a.strokeWidth) || a.strokeWidth < 25e-4 || a.strokeWidth > 0.02)) return null;
26445
+ return { kind: "ink", session, annotation: a };
26446
+ }
25949
26447
  if (message.name !== "EPUB_TEXT_SELECTED" || !(0, import_core3.validateAnnotationAnchor)(p.anchor) || p.anchor.kind !== "epub-cfi" || p.text !== p.anchor.quote || !Number.isSafeInteger(p.pageIndex) || p.pageIndex < 1) return null;
25950
26448
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
25951
26449
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
25952
26450
  }
25953
26451
 
25954
26452
  // components/WebViewViewer.tsx
25955
- var import_jsx_runtime4 = require("react/jsx-runtime");
26453
+ var import_jsx_runtime5 = require("react/jsx-runtime");
25956
26454
  var runtimeAsset = require_runtime();
25957
26455
  var resolveRuntimeSource = (asset) => {
25958
26456
  if (typeof asset === "string") {
@@ -25972,6 +26470,7 @@ var resolveRuntimeSource = (asset) => {
25972
26470
  };
25973
26471
  var WebViewViewer = ({
25974
26472
  engine,
26473
+ documentType,
25975
26474
  documentId,
25976
26475
  onDefineSelection,
25977
26476
  defineSelectionMode,
@@ -25979,19 +26478,20 @@ var WebViewViewer = ({
25979
26478
  onScrollOffset,
25980
26479
  onTap
25981
26480
  }) => {
25982
- const webViewRef = (0, import_react3.useRef)(null);
25983
- const { pageTheme, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
25984
- const [selection, setSelection] = (0, import_react3.useState)(null);
26481
+ const webViewRef = (0, import_react4.useRef)(null);
26482
+ const insets = usePapyrusSafeAreaInsets();
26483
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26484
+ const [selection, setSelection] = (0, import_react4.useState)(null);
25985
26485
  const t = getStrings(locale);
25986
26486
  const bridgeEngine = engine;
25987
- const runtimeSource = (0, import_react3.useMemo)(
26487
+ const runtimeSource = (0, import_react4.useMemo)(
25988
26488
  () => resolveRuntimeSource(
25989
26489
  bridgeEngine.getWebViewRuntimeSource?.() ?? runtimeAsset
25990
26490
  ),
25991
26491
  [bridgeEngine]
25992
26492
  );
25993
- const [runtimeHtml, setRuntimeHtml] = (0, import_react3.useState)(null);
25994
- (0, import_react3.useEffect)(() => {
26493
+ const [runtimeHtml, setRuntimeHtml] = (0, import_react4.useState)(null);
26494
+ (0, import_react4.useEffect)(() => {
25995
26495
  let active = true;
25996
26496
  if ("html" in runtimeSource) {
25997
26497
  setRuntimeHtml(runtimeSource.html);
@@ -26012,7 +26512,7 @@ var WebViewViewer = ({
26012
26512
  active = false;
26013
26513
  };
26014
26514
  }, [runtimeSource]);
26015
- const webViewSource = (0, import_react3.useMemo)(() => {
26515
+ const webViewSource = (0, import_react4.useMemo)(() => {
26016
26516
  if (runtimeHtml !== null) {
26017
26517
  return {
26018
26518
  html: runtimeHtml,
@@ -26023,13 +26523,13 @@ var WebViewViewer = ({
26023
26523
  return runtimeSource;
26024
26524
  }, [runtimeHtml, runtimeSource]);
26025
26525
  const runtimeConfig = bridgeEngine.getWebViewRuntimeConfig?.();
26026
- const runtimeConfigScript = (0, import_react3.useMemo)(() => {
26526
+ const runtimeConfigScript = (0, import_react4.useMemo)(() => {
26027
26527
  if (!runtimeConfig) return void 0;
26028
26528
  return `window.__PAPYRUS_RUNTIME_CONFIG__=${JSON.stringify(
26029
26529
  runtimeConfig
26030
26530
  )};true;`;
26031
26531
  }, [runtimeConfig]);
26032
- (0, import_react3.useEffect)(() => {
26532
+ (0, import_react4.useEffect)(() => {
26033
26533
  bridgeEngine.attachWebView?.({
26034
26534
  postMessage: (message) => {
26035
26535
  if (__DEV__) {
@@ -26043,10 +26543,10 @@ var WebViewViewer = ({
26043
26543
  bridgeEngine.detachWebView?.();
26044
26544
  };
26045
26545
  }, [bridgeEngine]);
26046
- (0, import_react3.useEffect)(() => {
26546
+ (0, import_react4.useEffect)(() => {
26047
26547
  setSelection(null);
26048
26548
  }, [engine, documentId]);
26049
- (0, import_react3.useEffect)(() => {
26549
+ (0, import_react4.useEffect)(() => {
26050
26550
  if (!isLoaded) return;
26051
26551
  let active = true;
26052
26552
  void bridgeEngine.syncEpubAnnotations?.(annotations, documentId, { comment: t.annotationNote }).catch(() => {
@@ -26061,6 +26561,19 @@ var WebViewViewer = ({
26061
26561
  (note !== void 0 ? beginAnnotationDraft : addAnnotation)((0, import_core4.createContextualAnnotation)({ id: `annotation-${Date.now()}-${Math.random().toString(36).slice(2)}`, anchor: selected.anchor, pageIndex: selected.pageIndex, style, color: annotationColor, opacity: annotationOpacity, note }));
26062
26562
  setSelection(null);
26063
26563
  };
26564
+ (0, import_react4.useEffect)(() => {
26565
+ if (!isLoaded) return;
26566
+ webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26567
+ }, [isLoaded, epubReadingMode, bridgeEngine]);
26568
+ (0, import_react4.useEffect)(() => {
26569
+ if (!isLoaded || documentType !== "epub") return;
26570
+ const inkMode = resolveEpubInkMode({ documentType, isLoaded, activeTool, preset: activeDrawToolPreset, color: annotationColor, opacity: annotationOpacity, strokeWidth: inkStrokeWidth });
26571
+ if (!inkMode) return;
26572
+ webViewRef.current?.postMessage(JSON.stringify({ id: `epub-ink-mode-${Date.now()}`, kind: "set-epub-ink-mode", payload: {
26573
+ ...inkMode,
26574
+ documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.()
26575
+ } }));
26576
+ }, [isLoaded, documentType, activeTool, activeDrawToolPreset, annotationColor, annotationOpacity, inkStrokeWidth, bridgeEngine]);
26064
26577
  const sendSelectionCommand = (key) => {
26065
26578
  webViewRef.current?.postMessage(JSON.stringify({
26066
26579
  type: "epub-selection-action",
@@ -26084,6 +26597,11 @@ var WebViewViewer = ({
26084
26597
  let annotationRaw = raw;
26085
26598
  try {
26086
26599
  const message = JSON.parse(raw);
26600
+ if (message.type === "event" && message.name === "EPUB_LOCATION" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) {
26601
+ const location = parseEpubReadingLocation(message.payload.location);
26602
+ if (location) setDocumentState({ epubLocation: location });
26603
+ }
26604
+ if (message.type === "event" && message.name === "EPUB_CONTENT_TAP" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) onTap?.();
26087
26605
  if (message.type === "event" && message.name === "EPUB_SELECTION_ACTION") {
26088
26606
  action = message.payload?.action;
26089
26607
  annotationRaw = JSON.stringify({ ...message, name: "EPUB_TEXT_SELECTED" });
@@ -26115,6 +26633,10 @@ var WebViewViewer = ({
26115
26633
  else setSelectedAnnotation(annotationEvent.id);
26116
26634
  return;
26117
26635
  }
26636
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26637
+ addAnnotation(annotationEvent.annotation);
26638
+ setSelection(null);
26639
+ }
26118
26640
  const interaction = parseWebViewInteraction(raw);
26119
26641
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
26120
26642
  if (interaction?.kind === "tap") onTap?.();
@@ -26147,7 +26669,7 @@ var WebViewViewer = ({
26147
26669
  console.warn("[Papyrus WebView] error", event.nativeEvent);
26148
26670
  }
26149
26671
  };
26150
- const themeOverlayStyle = (0, import_react3.useMemo)(() => {
26672
+ const themeOverlayStyle = (0, import_react4.useMemo)(() => {
26151
26673
  switch (pageTheme) {
26152
26674
  case "sepia":
26153
26675
  return styles2.themeSepia;
@@ -26159,7 +26681,7 @@ var WebViewViewer = ({
26159
26681
  return styles2.themeNone;
26160
26682
  }
26161
26683
  }, [pageTheme]);
26162
- const allowingReadAccessToURL = (0, import_react3.useMemo)(() => {
26684
+ const allowingReadAccessToURL = (0, import_react4.useMemo)(() => {
26163
26685
  if (!runtimeSource || typeof runtimeSource !== "object") return void 0;
26164
26686
  if ("uri" in runtimeSource && runtimeSource.uri) {
26165
26687
  return runtimeSource.uri;
@@ -26172,8 +26694,8 @@ var WebViewViewer = ({
26172
26694
  maxWidth: resolvedMaxPageWidth,
26173
26695
  alignSelf: "center"
26174
26696
  } : void 0;
26175
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.container, children: [
26176
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
26697
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native2.View, { style: [styles2.container, documentType === "epub" && { paddingTop: insets.top, paddingBottom: insets.bottom }], children: [
26698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
26177
26699
  import_react_native_webview.default,
26178
26700
  {
26179
26701
  ref: webViewRef,
@@ -26197,7 +26719,7 @@ var WebViewViewer = ({
26197
26719
  style: [styles2.webview, cappedWebViewStyle]
26198
26720
  }
26199
26721
  ),
26200
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
26722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
26201
26723
  import_react_native2.View,
26202
26724
  {
26203
26725
  pointerEvents: "none",
@@ -26299,22 +26821,22 @@ function getNearbyPdfNotes(annotations, id) {
26299
26821
  // components/PdfNoteChooser.tsx
26300
26822
  var import_react_native3 = require("react-native");
26301
26823
  var import_core5 = require("@papyrus-sdk/core");
26302
- var import_jsx_runtime5 = require("react/jsx-runtime");
26824
+ var import_jsx_runtime6 = require("react/jsx-runtime");
26303
26825
  function PdfNoteChooser({ notes, title, cancel, onSelect, onClose }) {
26304
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: styles3.backdrop, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26305
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Text, { style: styles3.title, children: title }),
26306
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.Text, { children: [
26826
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.View, { style: styles3.backdrop, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26827
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Text, { style: styles3.title, children: title }),
26828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.Text, { children: [
26307
26829
  index + 1,
26308
26830
  ". ",
26309
26831
  (0, import_core5.getAnnotationNote)(item) || (0, import_core5.getAnnotationQuote)(item)
26310
26832
  ] }) }) }),
26311
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Text, { children: cancel }) })
26833
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Text, { children: cancel }) })
26312
26834
  ] }) }) });
26313
26835
  }
26314
26836
  var styles3 = import_react_native3.StyleSheet.create({ backdrop: { flex: 1, backgroundColor: "#0008", justifyContent: "center", padding: 24 }, sheet: { backgroundColor: "#fff", borderRadius: 16, padding: 16, maxHeight: "75%" }, title: { fontSize: 18, fontWeight: "600", marginBottom: 12 }, row: { minHeight: 44, padding: 12 } });
26315
26837
 
26316
26838
  // components/DedicatedAndroidPdfViewer.tsx
26317
- var import_react4 = require("react");
26839
+ var import_react5 = require("react");
26318
26840
  var import_react_native4 = require("react-native");
26319
26841
  var import_core6 = require("@papyrus-sdk/core");
26320
26842
  var import_engine_native2 = require("@papyrus-sdk/engine-native");
@@ -26335,7 +26857,7 @@ var resolvePageTapChromeVisibility = ({
26335
26857
  };
26336
26858
 
26337
26859
  // components/DedicatedAndroidPdfViewer.tsx
26338
- var import_jsx_runtime6 = require("react/jsx-runtime");
26860
+ var import_jsx_runtime7 = require("react/jsx-runtime");
26339
26861
  var TEXT_MARKUP_TOOLS2 = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
26340
26862
  var MOBILE_CHROME_HIDE_DELTA = 28;
26341
26863
  var MOBILE_CHROME_SHOW_DELTA = 22;
@@ -26364,7 +26886,7 @@ function DedicatedAndroidPdfViewer({
26364
26886
  const searchResults = (0, import_core6.useViewerStore)((state) => state.searchResults);
26365
26887
  const annotationNavigationRequest = (0, import_core6.useViewerStore)((state) => state.annotationNavigationRequest);
26366
26888
  const annotations = (0, import_core6.useViewerStore)((state) => state.annotations);
26367
- const [pendingNotes, setPendingNotes] = (0, import_react4.useState)([]);
26889
+ const [pendingNotes, setPendingNotes] = (0, import_react5.useState)([]);
26368
26890
  const handleAnnotationTap = (id) => {
26369
26891
  const nearby = getNearbyPdfNotes(annotations, id);
26370
26892
  if (nearby.length < 2) {
@@ -26392,30 +26914,30 @@ function DedicatedAndroidPdfViewer({
26392
26914
  maxWidth: resolvedMaxPageWidth,
26393
26915
  alignSelf: "center"
26394
26916
  } : void 0;
26395
- const [selection, setSelection] = (0, import_react4.useState)(null);
26396
- const selectionRef = (0, import_react4.useRef)(null);
26397
- const lastVisiblePagesKeyRef = (0, import_react4.useRef)("");
26398
- const lastScrollOffsetYRef = (0, import_react4.useRef)(0);
26399
- const scrollDownAccumRef = (0, import_react4.useRef)(0);
26400
- const scrollUpAccumRef = (0, import_react4.useRef)(0);
26401
- const pendingChromeShowTimeoutRef = (0, import_react4.useRef)(null);
26402
- const chromeVisibleRef = (0, import_react4.useRef)(true);
26403
- const lastZoomChangedAtRef = (0, import_react4.useRef)(null);
26404
- (0, import_react4.useEffect)(() => {
26917
+ const [selection, setSelection] = (0, import_react5.useState)(null);
26918
+ const selectionRef = (0, import_react5.useRef)(null);
26919
+ const lastVisiblePagesKeyRef = (0, import_react5.useRef)("");
26920
+ const lastScrollOffsetYRef = (0, import_react5.useRef)(0);
26921
+ const scrollDownAccumRef = (0, import_react5.useRef)(0);
26922
+ const scrollUpAccumRef = (0, import_react5.useRef)(0);
26923
+ const pendingChromeShowTimeoutRef = (0, import_react5.useRef)(null);
26924
+ const chromeVisibleRef = (0, import_react5.useRef)(true);
26925
+ const lastZoomChangedAtRef = (0, import_react5.useRef)(null);
26926
+ (0, import_react5.useEffect)(() => {
26405
26927
  chromeVisibleRef.current = mobileChromeVisible;
26406
26928
  }, [mobileChromeVisible]);
26407
- const clearPendingChromeShow = (0, import_react4.useCallback)(() => {
26929
+ const clearPendingChromeShow = (0, import_react5.useCallback)(() => {
26408
26930
  if (pendingChromeShowTimeoutRef.current) {
26409
26931
  clearTimeout(pendingChromeShowTimeoutRef.current);
26410
26932
  pendingChromeShowTimeoutRef.current = null;
26411
26933
  }
26412
26934
  }, []);
26413
- const setMobileChromeVisible = (0, import_react4.useCallback)((visible, reason) => {
26935
+ const setMobileChromeVisible = (0, import_react5.useCallback)((visible, reason) => {
26414
26936
  if (chromeVisibleRef.current === visible) return;
26415
26937
  chromeVisibleRef.current = visible;
26416
26938
  setDocumentState({ mobileChromeVisible: visible });
26417
26939
  }, [setDocumentState]);
26418
- const trackMobileChromeByOffset = (0, import_react4.useCallback)((offsetY, reasonPrefix) => {
26940
+ const trackMobileChromeByOffset = (0, import_react5.useCallback)((offsetY, reasonPrefix) => {
26419
26941
  const safeOffset = Math.max(0, offsetY);
26420
26942
  const delta = safeOffset - lastScrollOffsetYRef.current;
26421
26943
  lastScrollOffsetYRef.current = safeOffset;
@@ -26451,12 +26973,12 @@ function DedicatedAndroidPdfViewer({
26451
26973
  }
26452
26974
  }
26453
26975
  }, [clearPendingChromeShow, setMobileChromeVisible]);
26454
- (0, import_react4.useEffect)(() => {
26976
+ (0, import_react5.useEffect)(() => {
26455
26977
  return () => {
26456
26978
  clearPendingChromeShow();
26457
26979
  };
26458
26980
  }, [clearPendingChromeShow]);
26459
- const applySelection = (0, import_react4.useCallback)((type) => {
26981
+ const applySelection = (0, import_react5.useCallback)((type) => {
26460
26982
  const sel = selectionRef.current;
26461
26983
  if (!sel || !sel.rects || sel.rects.length === 0) return;
26462
26984
  const bounds = sel.rects.reduce((acc, r) => ({
@@ -26478,12 +27000,12 @@ function DedicatedAndroidPdfViewer({
26478
27000
  setSelection(null);
26479
27001
  selectionRef.current = null;
26480
27002
  }, [addAnnotation, beginAnnotationDraft, annotationColor, documentId]);
26481
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native4.View, { style: styles4.container, children: [
26482
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
27003
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.container, children: [
27004
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26483
27005
  setPendingNotes([]);
26484
27006
  setSelectedAnnotation(id);
26485
27007
  } }),
26486
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
27008
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
26487
27009
  import_engine_native2.PapyrusPdfDocumentView,
26488
27010
  {
26489
27011
  style: [styles4.viewer, cappedViewerStyle],
@@ -26597,7 +27119,7 @@ var styles4 = import_react_native4.StyleSheet.create({
26597
27119
  });
26598
27120
 
26599
27121
  // components/DedicatedIosPdfViewer.tsx
26600
- var import_react5 = require("react");
27122
+ var import_react6 = require("react");
26601
27123
  var import_react_native5 = require("react-native");
26602
27124
  var import_core7 = require("@papyrus-sdk/core");
26603
27125
  var import_engine_native3 = require("@papyrus-sdk/engine-native");
@@ -26694,7 +27216,7 @@ var resolveNativePdfVisiblePages = (pages) => (pages ?? []).flatMap((page) => {
26694
27216
  });
26695
27217
 
26696
27218
  // components/DedicatedIosPdfViewer.tsx
26697
- var import_jsx_runtime7 = require("react/jsx-runtime");
27219
+ var import_jsx_runtime8 = require("react/jsx-runtime");
26698
27220
  var MOBILE_CHROME_HIDE_DELTA2 = 28;
26699
27221
  var MOBILE_CHROME_SHOW_DELTA2 = 22;
26700
27222
  var MOBILE_CHROME_SHOW_DELAY_MS2 = 180;
@@ -26726,7 +27248,7 @@ function DedicatedIosPdfViewer({
26726
27248
  const inkStrokeWidth = (0, import_core7.useViewerStore)((state) => state.inkStrokeWidth);
26727
27249
  const annotationNavigationRequest = (0, import_core7.useViewerStore)((state) => state.annotationNavigationRequest);
26728
27250
  const annotations = (0, import_core7.useViewerStore)((state) => state.annotations);
26729
- const [pendingNotes, setPendingNotes] = (0, import_react5.useState)([]);
27251
+ const [pendingNotes, setPendingNotes] = (0, import_react6.useState)([]);
26730
27252
  const handleAnnotationTap = (id) => {
26731
27253
  const nearby = getNearbyPdfNotes(annotations, id);
26732
27254
  if (nearby.length < 2) {
@@ -26762,16 +27284,16 @@ function DedicatedIosPdfViewer({
26762
27284
  supportsNativeEditMenu
26763
27285
  );
26764
27286
  const resolvedMaxPageWidth = typeof maxPageWidth === "number" && Number.isFinite(maxPageWidth) && maxPageWidth > 0 ? maxPageWidth : void 0;
26765
- const chromeVisibleRef = (0, import_react5.useRef)(mobileChromeVisible);
26766
- const lastScrollOffsetYRef = (0, import_react5.useRef)(0);
26767
- const scrollDownAccumRef = (0, import_react5.useRef)(0);
26768
- const scrollUpAccumRef = (0, import_react5.useRef)(0);
26769
- const pendingChromeShowTimeoutRef = (0, import_react5.useRef)(null);
26770
- const lastZoomChangedAtRef = (0, import_react5.useRef)(null);
26771
- const lastVisiblePagesKeyRef = (0, import_react5.useRef)("");
26772
- const [selection, setSelection] = (0, import_react5.useState)(null);
26773
- const selectionRef = (0, import_react5.useRef)(null);
26774
- (0, import_react5.useEffect)(() => {
27287
+ const chromeVisibleRef = (0, import_react6.useRef)(mobileChromeVisible);
27288
+ const lastScrollOffsetYRef = (0, import_react6.useRef)(0);
27289
+ const scrollDownAccumRef = (0, import_react6.useRef)(0);
27290
+ const scrollUpAccumRef = (0, import_react6.useRef)(0);
27291
+ const pendingChromeShowTimeoutRef = (0, import_react6.useRef)(null);
27292
+ const lastZoomChangedAtRef = (0, import_react6.useRef)(null);
27293
+ const lastVisiblePagesKeyRef = (0, import_react6.useRef)("");
27294
+ const [selection, setSelection] = (0, import_react6.useState)(null);
27295
+ const selectionRef = (0, import_react6.useRef)(null);
27296
+ (0, import_react6.useEffect)(() => {
26775
27297
  const pickerActive = supportsNativeEditMenu && activeTool === "ink";
26776
27298
  setDocumentState({ nativeInkToolPickerActive: pickerActive });
26777
27299
  return () => {
@@ -26780,15 +27302,15 @@ function DedicatedIosPdfViewer({
26780
27302
  }
26781
27303
  };
26782
27304
  }, [activeTool, setDocumentState]);
26783
- (0, import_react5.useEffect)(() => {
27305
+ (0, import_react6.useEffect)(() => {
26784
27306
  chromeVisibleRef.current = mobileChromeVisible;
26785
27307
  }, [mobileChromeVisible]);
26786
- const clearPendingChromeShow = (0, import_react5.useCallback)(() => {
27308
+ const clearPendingChromeShow = (0, import_react6.useCallback)(() => {
26787
27309
  if (!pendingChromeShowTimeoutRef.current) return;
26788
27310
  clearTimeout(pendingChromeShowTimeoutRef.current);
26789
27311
  pendingChromeShowTimeoutRef.current = null;
26790
27312
  }, []);
26791
- const setMobileChromeVisible = (0, import_react5.useCallback)(
27313
+ const setMobileChromeVisible = (0, import_react6.useCallback)(
26792
27314
  (visible) => {
26793
27315
  if (chromeVisibleRef.current === visible) return;
26794
27316
  chromeVisibleRef.current = visible;
@@ -26796,14 +27318,14 @@ function DedicatedIosPdfViewer({
26796
27318
  },
26797
27319
  [setDocumentState]
26798
27320
  );
26799
- const updateSelection = (0, import_react5.useCallback)(
27321
+ const updateSelection = (0, import_react6.useCallback)(
26800
27322
  (nextSelection) => {
26801
27323
  selectionRef.current = nextSelection;
26802
27324
  setSelection(nextSelection);
26803
27325
  },
26804
27326
  []
26805
27327
  );
26806
- const handleTextSelectionChange = (0, import_react5.useCallback)(
27328
+ const handleTextSelectionChange = (0, import_react6.useCallback)(
26807
27329
  (event) => {
26808
27330
  const nextSelection = resolveNativePdfTextSelection(event.nativeEvent);
26809
27331
  updateSelection(nextSelection);
@@ -26816,7 +27338,7 @@ function DedicatedIosPdfViewer({
26816
27338
  },
26817
27339
  [onTextSelected, setSelectedAnnotation, updateSelection]
26818
27340
  );
26819
- const defineSelection = (0, import_react5.useCallback)(() => {
27341
+ const defineSelection = (0, import_react6.useCallback)(() => {
26820
27342
  const currentSelection = selectionRef.current;
26821
27343
  if (!currentSelection) return;
26822
27344
  onDefineSelection?.({
@@ -26825,7 +27347,7 @@ function DedicatedIosPdfViewer({
26825
27347
  });
26826
27348
  updateSelection(null);
26827
27349
  }, [onDefineSelection, updateSelection]);
26828
- const handleNativeDefineSelection = (0, import_react5.useCallback)(
27350
+ const handleNativeDefineSelection = (0, import_react6.useCallback)(
26829
27351
  (event) => {
26830
27352
  const text = event.nativeEvent?.text;
26831
27353
  const pageIndex = event.nativeEvent?.pageIndex;
@@ -26837,7 +27359,7 @@ function DedicatedIosPdfViewer({
26837
27359
  },
26838
27360
  [onDefineSelection, updateSelection]
26839
27361
  );
26840
- const handleInkToolPickerVisibilityChange = (0, import_react5.useCallback)(
27362
+ const handleInkToolPickerVisibilityChange = (0, import_react6.useCallback)(
26841
27363
  (event) => {
26842
27364
  handleNativeInkPickerVisibility(
26843
27365
  event.nativeEvent?.visible === true,
@@ -26846,7 +27368,7 @@ function DedicatedIosPdfViewer({
26846
27368
  },
26847
27369
  []
26848
27370
  );
26849
- const trackMobileChromeByOffset = (0, import_react5.useCallback)(
27371
+ const trackMobileChromeByOffset = (0, import_react6.useCallback)(
26850
27372
  (offsetY) => {
26851
27373
  if (pageCount <= 0) return;
26852
27374
  const safeOffset = Math.max(0, offsetY);
@@ -26882,11 +27404,11 @@ function DedicatedIosPdfViewer({
26882
27404
  },
26883
27405
  [clearPendingChromeShow, pageCount, setMobileChromeVisible]
26884
27406
  );
26885
- (0, import_react5.useEffect)(
27407
+ (0, import_react6.useEffect)(
26886
27408
  () => () => clearPendingChromeShow(),
26887
27409
  [clearPendingChromeShow]
26888
27410
  );
26889
- const handleVisiblePagesChange = (0, import_react5.useCallback)(
27411
+ const handleVisiblePagesChange = (0, import_react6.useCallback)(
26890
27412
  (event) => {
26891
27413
  const pages = resolveNativePdfVisiblePages(
26892
27414
  event.nativeEvent?.pages
@@ -26901,13 +27423,13 @@ function DedicatedIosPdfViewer({
26901
27423
  },
26902
27424
  [setDocumentState]
26903
27425
  );
26904
- const handleScroll = (0, import_react5.useCallback)(
27426
+ const handleScroll = (0, import_react6.useCallback)(
26905
27427
  (event) => {
26906
27428
  trackMobileChromeByOffset(event.nativeEvent?.offsetY ?? 0);
26907
27429
  },
26908
27430
  [trackMobileChromeByOffset]
26909
27431
  );
26910
- const handleTap = (0, import_react5.useCallback)(() => {
27432
+ const handleTap = (0, import_react6.useCallback)(() => {
26911
27433
  setSelectedAnnotation(null);
26912
27434
  const nextVisible = resolvePageTapChromeVisibility({
26913
27435
  chromeVisible: chromeVisibleRef.current,
@@ -26917,7 +27439,7 @@ function DedicatedIosPdfViewer({
26917
27439
  });
26918
27440
  if (nextVisible !== null) setMobileChromeVisible(nextVisible);
26919
27441
  }, [activeTool, selectionActive, setMobileChromeVisible, setSelectedAnnotation]);
26920
- const handleDeleteAnnotation = (0, import_react5.useCallback)(
27442
+ const handleDeleteAnnotation = (0, import_react6.useCallback)(
26921
27443
  (annotationId) => {
26922
27444
  deleteAnnotationAndClearSelection(
26923
27445
  annotationId,
@@ -26932,12 +27454,12 @@ function DedicatedIosPdfViewer({
26932
27454
  maxWidth: resolvedMaxPageWidth,
26933
27455
  alignSelf: "center"
26934
27456
  } : void 0;
26935
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native5.View, { style: styles5.container, children: [
26936
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
27457
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.container, children: [
27458
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26937
27459
  setPendingNotes([]);
26938
27460
  setSelectedAnnotation(id);
26939
27461
  } }),
26940
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27462
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
26941
27463
  import_engine_native3.PapyrusPdfDocumentView,
26942
27464
  {
26943
27465
  style: [styles5.viewer, cappedViewerStyle],
@@ -27006,22 +27528,22 @@ function DedicatedIosPdfViewer({
27006
27528
  onInkToolPickerVisibilityChange: handleInkToolPickerVisibilityChange
27007
27529
  }
27008
27530
  ),
27009
- selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.View, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27531
+ selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27010
27532
  import_react_native5.Pressable,
27011
27533
  {
27012
27534
  onPress: defineSelection,
27013
27535
  style: styles5.fallbackButton,
27014
27536
  accessibilityRole: "button",
27015
27537
  accessibilityLabel: t.define,
27016
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.Text, { style: styles5.fallbackButtonText, children: t.define })
27538
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.Text, { style: styles5.fallbackButtonText, children: t.define })
27017
27539
  }
27018
27540
  ) }),
27019
- fallbackDeleteAnnotation && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27541
+ fallbackDeleteAnnotation && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27020
27542
  import_react_native5.View,
27021
27543
  {
27022
27544
  style: styles5.annotationDeleteFallback,
27023
27545
  pointerEvents: "box-none",
27024
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27546
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27025
27547
  import_react_native5.Pressable,
27026
27548
  {
27027
27549
  onPress: () => handleDeleteAnnotation(fallbackDeleteAnnotation.id),
@@ -27029,7 +27551,7 @@ function DedicatedIosPdfViewer({
27029
27551
  accessibilityRole: "button",
27030
27552
  accessibilityLabel: t.deleteAnnotation,
27031
27553
  hitSlop: 8,
27032
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.Text, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
27554
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.Text, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
27033
27555
  }
27034
27556
  )
27035
27557
  }
@@ -27092,7 +27614,7 @@ var styles5 = import_react_native5.StyleSheet.create({
27092
27614
  });
27093
27615
 
27094
27616
  // components/NativePdfDocumentViewer.tsx
27095
- var import_jsx_runtime8 = require("react/jsx-runtime");
27617
+ var import_jsx_runtime9 = require("react/jsx-runtime");
27096
27618
  function NativePdfDocumentViewer({
27097
27619
  engine,
27098
27620
  documentId,
@@ -27105,7 +27627,7 @@ function NativePdfDocumentViewer({
27105
27627
  import_react_native6.Platform.OS
27106
27628
  );
27107
27629
  if (implementation === "android") {
27108
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27630
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
27109
27631
  DedicatedAndroidPdfViewer,
27110
27632
  {
27111
27633
  engine,
@@ -27118,7 +27640,7 @@ function NativePdfDocumentViewer({
27118
27640
  );
27119
27641
  }
27120
27642
  if (implementation === "ios") {
27121
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27643
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
27122
27644
  DedicatedIosPdfViewer,
27123
27645
  {
27124
27646
  engine,
@@ -27172,26 +27694,6 @@ var resolveMobileChromeOffsets = (insets) => ({
27172
27694
  right: MOBILE_CHROME_METRICS.screenPadding + Math.max(0, insets.right)
27173
27695
  });
27174
27696
 
27175
- // components/PapyrusSafeArea.tsx
27176
- var import_react6 = __toESM(require("react"));
27177
- var import_react_native_safe_area_context = require("react-native-safe-area-context");
27178
- var import_jsx_runtime9 = require("react/jsx-runtime");
27179
- var papyrusSafeAreaContext = import_react_native_safe_area_context.SafeAreaInsetsContext;
27180
- var ZERO_SAFE_AREA_INSETS = {
27181
- top: 0,
27182
- bottom: 0,
27183
- left: 0,
27184
- right: 0
27185
- };
27186
- var usePapyrusSafeAreaInsets = () => (0, import_react6.useContext)(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
27187
- function PapyrusSafeAreaBoundary({
27188
- children
27189
- }) {
27190
- const insets = (0, import_react6.useContext)(papyrusSafeAreaContext);
27191
- if (insets) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children });
27192
- return import_react6.default.createElement(import_react_native_safe_area_context.SafeAreaProvider, null, children);
27193
- }
27194
-
27195
27697
  // components/NativeTextDocumentViewer.tsx
27196
27698
  var import_jsx_runtime10 = require("react/jsx-runtime");
27197
27699
  function NativeTextDocumentViewer({
@@ -27740,6 +28242,7 @@ var scrollViewerListToOffset = (list, offset, animated) => {
27740
28242
  list.scrollToOffset?.({ offset, animated });
27741
28243
  };
27742
28244
  var Viewer = ({
28245
+ documentType,
27743
28246
  documentId,
27744
28247
  engine,
27745
28248
  maxPageWidth,
@@ -29267,14 +29770,8 @@ var Viewer = ({
29267
29770
  },
29268
29771
  [mobilePerf, perfEnabled, scrollPerfSession]
29269
29772
  );
29270
- const handleWebViewScroll = (0, import_react9.useCallback)(
29271
- (_offsetY) => {
29272
- if (!chromeVisibleRef.current) {
29273
- setMobileChromeVisible(true, "scroll.webview.recover");
29274
- }
29275
- },
29276
- [setMobileChromeVisible]
29277
- );
29773
+ const handleWebViewScroll = (0, import_react9.useCallback)((_offsetY) => {
29774
+ }, []);
29278
29775
  const handlePageTap = (0, import_react9.useCallback)(() => {
29279
29776
  const nextVisible = resolvePageTapChromeVisibility({
29280
29777
  chromeVisible: chromeVisibleRef.current,
@@ -29496,6 +29993,7 @@ var Viewer = ({
29496
29993
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native9.View, { style: [styles8.container, isDark && styles8.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
29497
29994
  WebViewViewer_default,
29498
29995
  {
29996
+ documentType,
29499
29997
  engine,
29500
29998
  documentId,
29501
29999
  onDefineSelection,
@@ -30042,6 +30540,7 @@ var styles9 = import_react_native11.StyleSheet.create({
30042
30540
  // components/PageJumpModal.tsx
30043
30541
  var import_jsx_runtime14 = require("react/jsx-runtime");
30044
30542
  function PageJumpModal({
30543
+ documentType,
30045
30544
  visible,
30046
30545
  currentPage,
30047
30546
  pageCount,
@@ -30068,8 +30567,8 @@ function PageJumpModal({
30068
30567
  isDark,
30069
30568
  maxHeight: 300,
30070
30569
  showHeader: true,
30071
- title: "Ir para p\xE1gina",
30072
- closeAccessibilityLabel: "Close page jump",
30570
+ title: documentType === "epub" ? "Ir para cap\xEDtulo" : "Ir para p\xE1gina",
30571
+ closeAccessibilityLabel: "Fechar navega\xE7\xE3o",
30073
30572
  sheetStyle: {
30074
30573
  paddingHorizontal: 18,
30075
30574
  paddingBottom: 18,
@@ -30097,7 +30596,7 @@ function PageJumpModal({
30097
30596
  }
30098
30597
  ],
30099
30598
  onSubmitEditing: confirmJump,
30100
- accessibilityLabel: "Page jump input",
30599
+ accessibilityLabel: documentType === "epub" ? "N\xFAmero do cap\xEDtulo" : "N\xFAmero da p\xE1gina",
30101
30600
  testID: "papyrus-page-jump-input"
30102
30601
  }
30103
30602
  ),
@@ -30152,6 +30651,7 @@ var styles10 = import_react_native12.StyleSheet.create({
30152
30651
  var import_jsx_runtime15 = require("react/jsx-runtime");
30153
30652
  var Topbar = ({
30154
30653
  engine,
30654
+ documentType,
30155
30655
  onOpenSettings,
30156
30656
  onOpenOverflow,
30157
30657
  title,
@@ -30172,6 +30672,12 @@ var Topbar = ({
30172
30672
  locale
30173
30673
  } = (0, import_core12.useViewerStore)();
30174
30674
  const inkSession = useNativeInkSession();
30675
+ const chromeVisible = mobileChromeVisible || inkSession.active;
30676
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
30677
+ const chromeMotion = (0, import_react_native_reanimated2.useAnimatedStyle)(() => {
30678
+ "worklet";
30679
+ return { opacity: chromeProgress.value };
30680
+ });
30175
30681
  const t = getStrings(locale);
30176
30682
  const progress = useNativeInkMotion(inkSession.active, inkSession.supported);
30177
30683
  const settingsMotion = (0, import_react_native_reanimated2.useAnimatedStyle)(() => {
@@ -30227,26 +30733,30 @@ var Topbar = ({
30227
30733
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconSettings, { size: MOBILE_CHROME_METRICS.iconSize, color: isDark ? "#e5e7eb" : "#111827" })
30228
30734
  }
30229
30735
  );
30230
- if (!mobileChromeVisible && !inkSession.active) return null;
30736
+ if (!chromeVisible && documentType !== "epub") return null;
30231
30737
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
30232
30738
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
30233
- import_react_native13.View,
30739
+ import_react_native_reanimated2.default.View,
30234
30740
  {
30235
30741
  style: [
30236
30742
  styles11.chromeFrame,
30743
+ chromeMotion,
30237
30744
  {
30238
30745
  paddingTop: insets.top,
30239
30746
  paddingLeft: MOBILE_CHROME_METRICS.screenPadding + insets.left,
30240
30747
  paddingRight: MOBILE_CHROME_METRICS.screenPadding + insets.right
30241
30748
  }
30242
30749
  ],
30243
- pointerEvents: "box-none",
30750
+ pointerEvents: chromeVisible ? "box-none" : "none",
30751
+ accessibilityElementsHidden: !chromeVisible,
30752
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
30244
30753
  testID: "papyrus-floating-top-controls",
30245
30754
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
30246
30755
  import_react_native13.View,
30247
30756
  {
30248
30757
  style: [
30249
30758
  styles11.container,
30759
+ import_react_native13.Platform.OS === "ios" && styles11.containerIos,
30250
30760
  isDark && styles11.containerDark,
30251
30761
  isLandscape && styles11.containerLandscape
30252
30762
  ],
@@ -30386,6 +30896,7 @@ var Topbar = ({
30386
30896
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
30387
30897
  PageJumpModal,
30388
30898
  {
30899
+ documentType,
30389
30900
  visible: jumpModalOpen,
30390
30901
  currentPage,
30391
30902
  pageCount,
@@ -30403,7 +30914,8 @@ var styles11 = import_react_native13.StyleSheet.create({
30403
30914
  maxWidth: MOBILE_CHROME_METRICS.maxFloatingWidth,
30404
30915
  marginTop: 10,
30405
30916
  paddingHorizontal: 14,
30406
- paddingVertical: 10,
30917
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30918
+ paddingVertical: 0,
30407
30919
  backgroundColor: "rgba(255,255,255,0.88)",
30408
30920
  borderWidth: 1,
30409
30921
  borderColor: "rgba(255,255,255,0.72)",
@@ -30420,6 +30932,10 @@ var styles11 = import_react_native13.StyleSheet.create({
30420
30932
  backgroundColor: "rgba(15,17,21,0.88)",
30421
30933
  borderColor: "rgba(71,85,105,0.48)"
30422
30934
  },
30935
+ containerIos: {
30936
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30937
+ paddingVertical: 0
30938
+ },
30423
30939
  containerLandscape: {
30424
30940
  maxWidth: void 0,
30425
30941
  marginTop: 0
@@ -32556,10 +33072,10 @@ var import_react_native16 = require("react-native");
32556
33072
  var import_core15 = require("@papyrus-sdk/core");
32557
33073
  var import_jsx_runtime18 = require("react/jsx-runtime");
32558
33074
  var COLORS = ["#fbbf24", "#fb7185", "#60a5fa", "#34d399", "#c084fc"];
32559
- var AnnotationEditor = () => {
33075
+ var AnnotationEditor = ({ documentId }) => {
32560
33076
  const { annotations, annotationDraft, clearAnnotationDraft, addAnnotation, selectedAnnotationId, updateAnnotation, removeAnnotation, setSelectedAnnotation, uiTheme, locale, accentColor } = (0, import_core15.useViewerStore)();
32561
33077
  const annotation = annotationDraft?.id === selectedAnnotationId ? annotationDraft : annotations.find((a) => a.id === selectedAnnotationId);
32562
- const supported = annotation && annotation.type !== "ink";
33078
+ const supported = annotation && annotation.type !== "ink" && (!documentId || !annotation.anchor?.documentId || annotation.anchor.documentId === documentId);
32563
33079
  const [editing, setEditing] = (0, import_react16.useState)(false);
32564
33080
  const [draft, setDraft] = (0, import_react16.useState)("");
32565
33081
  const [color, setColor] = (0, import_react16.useState)(COLORS[0]);
@@ -32624,14 +33140,34 @@ var AnnotationEditor = () => {
32624
33140
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.Animated.View, { accessibilityViewIsModal: true, style: [styles14.card, { backgroundColor: dark ? "#17191f" : "#fff", marginBottom: Math.max(16, insets.bottom), opacity: progress, transform: [{ translateY: progress.interpolate({ inputRange: [0, 1], outputRange: [12, 0] }) }] }], children: [
32625
33141
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.View, { style: styles14.header, children: [
32626
33142
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.title, textStyle], children: editing ? t.editNote : t.annotationNote }),
32627
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: styles14.button, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: textStyle, children: "\xD7" }) })
33143
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: [styles14.button, { borderRadius: 22, backgroundColor: dark ? "#242936" : "#f1f5f9" }], children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconClose, { size: 20, color: textStyle.color }) })
32628
33144
  ] }),
32629
33145
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.ScrollView, { style: styles14.content, keyboardShouldPersistTaps: "handled", children: [
32630
33146
  quote ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.quote, textStyle], selectable: true, children: quote }) : null,
32631
33147
  editing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
32632
33148
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.TextInput, { accessibilityLabel: t.editNote, value: draft, onChangeText: setDraft, multiline: true, autoFocus: true, placeholder: t.notePlaceholder, placeholderTextColor: dark ? "#9ca3af" : "#6b7280", style: [styles14.input, textStyle, { borderColor: dark ? "#374151" : "#d1d5db" }] }),
32633
33149
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.options, children: COLORS.map((value) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { accessibilityRole: "button", accessibilityLabel: `${t.annotationColor} ${value}`, accessibilityState: { selected: color === value }, onPress: () => setColor(value), style: [styles14.color, { backgroundColor: value, borderColor: color === value ? accentColor : "transparent" }] }, value)) }),
32634
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.options, children: [["highlight", t.annotationHighlight], ["underline", t.annotationUnderline], ["strikeout", t.annotationStrikeout], ["none", t.annotationIndicatorOnly]].map(([style, label]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: () => setMarkup(style), accessibilityRole: "button", accessibilityState: { selected: markup === style }, style: [styles14.button, markup === style && { borderColor: accentColor, borderWidth: 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: textStyle, children: label }) }, style)) })
33150
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.markupOptions, children: [
33151
+ ["highlight", t.annotationHighlight, IconHighlight],
33152
+ ["underline", t.annotationUnderline, IconUnderline],
33153
+ ["strikeout", t.annotationStrikeout, IconStrikeout],
33154
+ ["none", t.annotationIndicatorOnly, IconComment]
33155
+ ].map(([style, label, Icon]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
33156
+ import_react_native16.Pressable,
33157
+ {
33158
+ onPress: () => setMarkup(style),
33159
+ accessibilityRole: "button",
33160
+ accessibilityLabel: label,
33161
+ accessibilityState: { selected: markup === style },
33162
+ testID: `annotation-style-${style}`,
33163
+ style: [styles14.markupButton, { backgroundColor: dark ? "#242936" : "#f1f5f9", borderColor: markup === style ? accentColor : "transparent" }],
33164
+ children: [
33165
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { accessible: false, importantForAccessibility: "no-hide-descendants", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Icon, { size: 22, color: markup === style ? accentColor : textStyle.color }) }),
33166
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.markupLabel, textStyle], children: style === "none" ? locale === "pt-BR" ? "Indicador" : "Indicator" : label })
33167
+ ]
33168
+ },
33169
+ style
33170
+ )) })
32635
33171
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.note, textStyle], selectable: true, children: (0, import_core15.getAnnotationNote)(annotation) || t.notePlaceholder })
32636
33172
  ] }),
32637
33173
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.View, { style: styles14.actions, children: [
@@ -32654,7 +33190,7 @@ var AnnotationEditor = () => {
32654
33190
  ] })
32655
33191
  ] }) });
32656
33192
  };
32657
- var styles14 = import_react_native16.StyleSheet.create({ host: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,.18)", paddingHorizontal: 12 }, card: { borderRadius: 20, padding: 16, maxHeight: "72%", elevation: 12 }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, title: { fontSize: 17, fontWeight: "700" }, content: { flexGrow: 0 }, quote: { fontSize: 14, lineHeight: 21, borderLeftWidth: 3, borderLeftColor: "#9ca3af", paddingLeft: 10, marginVertical: 12 }, note: { fontSize: 16, lineHeight: 24, paddingVertical: 12 }, input: { minHeight: 112, maxHeight: 240, borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16, textAlignVertical: "top" }, button: { minWidth: 44, minHeight: 44, paddingHorizontal: 10, borderRadius: 10, alignItems: "center", justifyContent: "center" }, actions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", marginTop: 12 }, options: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, color: { width: 44, height: 44, borderRadius: 22, borderWidth: 3 } });
33193
+ var styles14 = import_react_native16.StyleSheet.create({ host: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,.18)", paddingHorizontal: 12 }, card: { borderRadius: 20, padding: 16, maxHeight: "72%", elevation: 12 }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, title: { fontSize: 17, fontWeight: "700" }, content: { flexGrow: 0 }, quote: { fontSize: 14, lineHeight: 21, borderLeftWidth: 3, borderLeftColor: "#9ca3af", paddingLeft: 10, marginVertical: 12 }, note: { fontSize: 16, lineHeight: 24, paddingVertical: 12 }, input: { minHeight: 112, maxHeight: 240, borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16, textAlignVertical: "top" }, button: { minWidth: 44, minHeight: 44, paddingHorizontal: 10, borderRadius: 10, alignItems: "center", justifyContent: "center" }, actions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", marginTop: 12 }, markupOptions: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, markupButton: { flex: 1, minWidth: 64, minHeight: 64, borderWidth: 1, borderRadius: 12, paddingHorizontal: 4, paddingVertical: 8, alignItems: "center", justifyContent: "center", gap: 6 }, markupLabel: { fontSize: 11, lineHeight: 15, textAlign: "center" }, options: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, color: { width: 44, height: 44, borderRadius: 22, borderWidth: 3 } });
32658
33194
  var AnnotationEditor_default = AnnotationEditor;
32659
33195
 
32660
33196
  // components/BottomBar.tsx
@@ -32669,7 +33205,7 @@ function buildBottomBarLayout({
32669
33205
  toolDockOpen
32670
33206
  }) {
32671
33207
  const leftSlots = [];
32672
- if (documentType === "pdf") {
33208
+ if (documentType === "pdf" || documentType === "epub") {
32673
33209
  leftSlots.push({
32674
33210
  key: "annotate",
32675
33211
  active: toolDockOpen || activeMobileDestination === "annotate"
@@ -32717,10 +33253,19 @@ var BottomBar = ({
32717
33253
  documentType,
32718
33254
  onOpenInfo,
32719
33255
  onOpenSettings,
32720
- onOpenDestination
33256
+ onOpenDestination,
33257
+ onTurnPage,
33258
+ onNavigateToPage,
33259
+ onOpenPages
32721
33260
  }) => {
32722
33261
  const {
32723
33262
  activeMobileDestination,
33263
+ viewMode,
33264
+ epubReadingMode,
33265
+ epubLocation,
33266
+ comicLayoutMode,
33267
+ currentPage,
33268
+ pageCount,
32724
33269
  mobileDockVisible,
32725
33270
  setDocumentState,
32726
33271
  uiTheme,
@@ -32735,7 +33280,7 @@ var BottomBar = ({
32735
33280
  const supportsInkChrome = documentType === "pdf" && inkSession.supported;
32736
33281
  const drawing = supportsInkChrome && inkSession.active;
32737
33282
  const dockVisible = mobileChromeVisible && mobileDockVisible && !drawing;
32738
- const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome);
33283
+ const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome || documentType === "epub");
32739
33284
  const dockMotion = (0, import_react_native_reanimated3.useAnimatedStyle)(() => {
32740
33285
  "worklet";
32741
33286
  return {
@@ -32757,6 +33302,15 @@ var BottomBar = ({
32757
33302
  activeMobileDestination,
32758
33303
  toolDockOpen
32759
33304
  });
33305
+ const paginated = documentType !== "text" && (documentType === "comic" ? comicLayoutMode === "single" : documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single");
33306
+ if (paginated) {
33307
+ layout.leftSlots.push(...layout.rightSlots.filter((slot) => slot.key === "search"));
33308
+ layout.rightSlots = [];
33309
+ }
33310
+ const turnPage = (direction) => {
33311
+ if (onTurnPage) onTurnPage(direction);
33312
+ else onNavigateToPage?.(currentPage + direction);
33313
+ };
32760
33314
  const slotMeta = {
32761
33315
  annotate: {
32762
33316
  label: t.tools,
@@ -32817,7 +33371,7 @@ var BottomBar = ({
32817
33371
  onPress: onOpenSettings
32818
33372
  }
32819
33373
  };
32820
- if (!dockVisible && !supportsInkChrome) return null;
33374
+ if (!dockVisible && !supportsInkChrome && documentType !== "epub") return null;
32821
33375
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
32822
33376
  import_react_native_reanimated3.default.View,
32823
33377
  {
@@ -32882,7 +33436,11 @@ var BottomBar = ({
32882
33436
  isDark && styles15.islandDark
32883
33437
  ],
32884
33438
  testID: "papyrus-floating-bottom-dock",
32885
- children: layout.rightSlots.map((slot) => {
33439
+ children: paginated ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
33440
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "P\xE1gina anterior" : "Previous page", disabled: !dockVisible || (documentType === "epub" ? epubLocation?.atStart : !onTurnPage && currentPage <= 1), onPress: () => turnPage(-1), style: styles15.iconOnlyItem, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconChevronLeft, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) }),
33441
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "Ir para p\xE1gina" : "Go to page", onPress: onOpenPages, style: [styles15.iconOnlyItem, { minWidth: 56 }], children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Text, { style: { color: iconColor(false), fontWeight: "700", fontSize: 13, lineHeight: 18, includeFontPadding: false }, children: documentType === "epub" ? epubLocation?.visualPage ? `Cap. ${epubLocation.chapter} \xB7 ${epubLocation.visualPage}/${epubLocation.visualPageCount}` : `Cap. ${currentPage}` : `${currentPage}/${pageCount}` }) }),
33442
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "Pr\xF3xima p\xE1gina" : "Next page", disabled: !dockVisible || (documentType === "epub" ? epubLocation?.atEnd : !onTurnPage && currentPage >= pageCount), onPress: () => turnPage(1), style: styles15.iconOnlyItem, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconChevronRight, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) })
33443
+ ] }) : layout.rightSlots.map((slot) => {
32886
33444
  const meta = slotMeta[slot.key];
32887
33445
  const Icon = meta.icon;
32888
33446
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -32945,8 +33503,9 @@ var styles15 = import_react_native17.StyleSheet.create({
32945
33503
  island: {
32946
33504
  flexDirection: "row",
32947
33505
  alignItems: "center",
32948
- paddingVertical: 8,
32949
- paddingHorizontal: 10,
33506
+ height: MOBILE_CHROME_METRICS.bottomBarVisualHeight,
33507
+ paddingVertical: 0,
33508
+ paddingHorizontal: 6,
32950
33509
  backgroundColor: "rgba(255,255,255,0.9)",
32951
33510
  borderWidth: 1,
32952
33511
  borderColor: "rgba(255,255,255,0.72)",
@@ -32972,6 +33531,8 @@ var styles15 = import_react_native17.StyleSheet.create({
32972
33531
  borderColor: "rgba(71,85,105,0.48)"
32973
33532
  },
32974
33533
  iconOnlyItem: {
33534
+ minWidth: 44,
33535
+ height: 44,
32975
33536
  alignItems: "center",
32976
33537
  justifyContent: "center",
32977
33538
  paddingHorizontal: MOBILE_CHROME_METRICS.bottomBarItemPaddingHorizontal,
@@ -33048,10 +33609,12 @@ var SettingsSheet = ({
33048
33609
  engine,
33049
33610
  visible,
33050
33611
  onClose,
33051
- documentType = "pdf"
33612
+ documentType = "pdf",
33613
+ onOpenInfo
33052
33614
  }) => {
33053
33615
  const {
33054
33616
  viewMode,
33617
+ epubReadingMode,
33055
33618
  uiTheme,
33056
33619
  zoom,
33057
33620
  setDocumentState,
@@ -33063,12 +33626,16 @@ var SettingsSheet = ({
33063
33626
  comicReadingDirection
33064
33627
  } = (0, import_core17.useViewerStore)();
33065
33628
  const isDark = uiTheme === "dark";
33066
- const isPaged = viewMode === "single";
33629
+ const isPaged = documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single";
33067
33630
  const isDouble = viewMode === "double";
33068
33631
  const t = getStrings(locale);
33069
33632
  const { height: windowHeight } = (0, import_react_native18.useWindowDimensions)();
33070
33633
  const sheetMaxHeight = getSettingsSheetMaxHeight(windowHeight);
33071
33634
  const handleTransition = (mode) => {
33635
+ if (documentType === "epub") {
33636
+ setDocumentState({ epubReadingMode: mode === "paged" ? "paged" : "chapter-scroll" });
33637
+ return;
33638
+ }
33072
33639
  if (mode === "paged") {
33073
33640
  setDocumentState({ viewMode: "single" });
33074
33641
  return;
@@ -33113,6 +33680,7 @@ var SettingsSheet = ({
33113
33680
  contentContainerStyle: styles16.sheetContent,
33114
33681
  showsVerticalScrollIndicator: false,
33115
33682
  children: [
33683
+ onOpenInfo ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Pressable, { accessibilityRole: "button", onPress: onOpenInfo, style: { minHeight: 44, justifyContent: "center", marginBottom: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Text, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.info }) }) : null,
33116
33684
  (documentType === "text" || documentType === "epub") && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33117
33685
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Text, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: locale === "pt-BR" ? "Tamanho da fonte" : "Font size" }),
33118
33686
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
@@ -33291,7 +33859,7 @@ var SettingsSheet = ({
33291
33859
  isDark && styles16.optionTextDark,
33292
33860
  (!isPaged || isDouble) && styles16.optionTextActive
33293
33861
  ],
33294
- children: t.continuous
33862
+ children: documentType === "epub" && locale === "pt-BR" ? "Rolagem por cap\xEDtulo" : t.continuous
33295
33863
  }
33296
33864
  )
33297
33865
  }
@@ -33321,7 +33889,7 @@ var SettingsSheet = ({
33321
33889
  )
33322
33890
  ] })
33323
33891
  ] }),
33324
- documentType !== "comic" && documentType !== "text" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33892
+ documentType === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33325
33893
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
33326
33894
  import_react_native18.Text,
33327
33895
  {
@@ -34199,6 +34767,7 @@ var styles20 = import_react_native22.StyleSheet.create({
34199
34767
  // components/ProgressPill.tsx
34200
34768
  var import_react18 = require("react");
34201
34769
  var import_react_native23 = require("react-native");
34770
+ var import_react_native_reanimated4 = __toESM(require("react-native-reanimated"));
34202
34771
  var import_core21 = require("@papyrus-sdk/core");
34203
34772
 
34204
34773
  // components/progressPillMode.ts
@@ -34218,9 +34787,11 @@ var import_jsx_runtime25 = require("react/jsx-runtime");
34218
34787
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
34219
34788
  function ProgressPill({
34220
34789
  documentType,
34790
+ navigationInDock = false,
34221
34791
  onPress,
34222
34792
  onOpenPageJump,
34223
34793
  onNavigateToPage,
34794
+ onTurnPage,
34224
34795
  onScrubbingChange
34225
34796
  }) {
34226
34797
  const {
@@ -34230,12 +34801,20 @@ function ProgressPill({
34230
34801
  textLength,
34231
34802
  comicLayoutMode,
34232
34803
  viewMode,
34804
+ epubReadingMode,
34805
+ epubLocation,
34233
34806
  locale,
34234
34807
  uiTheme,
34235
34808
  accentColor,
34236
34809
  mobileChromeVisible,
34237
34810
  mobileProgressPillVisible
34238
34811
  } = (0, import_core21.useViewerStore)();
34812
+ const chromeVisible = mobileChromeVisible && mobileProgressPillVisible;
34813
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
34814
+ const chromeMotion = (0, import_react_native_reanimated4.useAnimatedStyle)(() => {
34815
+ "worklet";
34816
+ return { opacity: chromeProgress.value };
34817
+ });
34239
34818
  const t = getStrings(locale);
34240
34819
  const isDark = uiTheme === "dark";
34241
34820
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
@@ -34280,10 +34859,10 @@ function ProgressPill({
34280
34859
  return `${displayedPage}/${pageCount || 0}`;
34281
34860
  }
34282
34861
  if (documentType === "epub") {
34283
- return `Cap. ${displayedPage} \xB7 ${percent}%`;
34862
+ return `Cap. ${displayedPage} \xB7 ${scrubPreviewPage === null && epubLocation ? clampPercent(epubLocation.progress * 100) : percent}%`;
34284
34863
  }
34285
34864
  return `${percent}%`;
34286
- }, [displayedPage, documentType, pageCount]);
34865
+ }, [displayedPage, documentType, pageCount, epubLocation, scrubPreviewPage]);
34287
34866
  const thumbTop = resolvePageScrubberThumbTop({
34288
34867
  currentPage,
34289
34868
  trackHeight,
@@ -34325,7 +34904,7 @@ function ProgressPill({
34325
34904
  },
34326
34905
  []
34327
34906
  );
34328
- if (!shouldRenderPageScrubber({
34907
+ if (!(documentType === "epub" && mobileProgressPillVisible) && !shouldRenderPageScrubber({
34329
34908
  mobileChromeVisible,
34330
34909
  mobileProgressPillVisible,
34331
34910
  isScrubbing: isScrubbingRef.current
@@ -34356,16 +34935,17 @@ function ProgressPill({
34356
34935
  }
34357
34936
  );
34358
34937
  }
34359
- const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : viewMode;
34938
+ const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : documentType === "epub" ? epubReadingMode === "paged" ? "single" : "continuous" : viewMode;
34360
34939
  const pillMode = resolveProgressPillMode(progressViewMode, currentPage, pageCount);
34940
+ if (pillMode.kind === "navigation" && navigationInDock) return null;
34361
34941
  if (pillMode.kind === "navigation") {
34362
- const previousDisabled = pillMode.previousPage === null || !onNavigateToPage;
34363
- const nextDisabled = pillMode.nextPage === null || !onNavigateToPage;
34942
+ const previousDisabled = !onTurnPage && (pillMode.previousPage === null || !onNavigateToPage);
34943
+ const nextDisabled = !onTurnPage && (pillMode.nextPage === null || !onNavigateToPage);
34364
34944
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34365
34945
  import_react_native23.View,
34366
34946
  {
34367
34947
  pointerEvents: "box-none",
34368
- style: [styles21.frame, styles21.singleFrame, { top: offsets.progress, right: offsets.right }],
34948
+ style: [{ position: "absolute", zIndex: 18, bottom: offsets.bottom + MOBILE_CHROME_METRICS.bottomBarVisualHeight + 12, left: offsets.left, right: offsets.right, alignItems: "center" }],
34369
34949
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react_native23.View, { style: [styles21.singlePill, isDark && styles21.pillDark], children: [
34370
34950
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34371
34951
  import_react_native23.Pressable,
@@ -34374,7 +34954,7 @@ function ProgressPill({
34374
34954
  accessibilityLabel: t.previousPage,
34375
34955
  accessibilityState: { disabled: previousDisabled },
34376
34956
  disabled: previousDisabled,
34377
- onPress: () => pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34957
+ onPress: () => onTurnPage ? onTurnPage(-1) : pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34378
34958
  style: styles21.pageArrowHit,
34379
34959
  testID: "papyrus-progress-page-previous",
34380
34960
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native23.Text, { style: [styles21.pageArrow, isDark && styles21.labelDark, previousDisabled && styles21.pageArrowDisabled], children: "\u2039" })
@@ -34402,7 +34982,7 @@ function ProgressPill({
34402
34982
  accessibilityLabel: t.nextPage,
34403
34983
  accessibilityState: { disabled: nextDisabled },
34404
34984
  disabled: nextDisabled,
34405
- onPress: () => pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34985
+ onPress: () => onTurnPage ? onTurnPage(1) : pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34406
34986
  style: styles21.pageArrowHit,
34407
34987
  testID: "papyrus-progress-page-next",
34408
34988
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native23.Text, { style: [styles21.pageArrow, isDark && styles21.labelDark, nextDisabled && styles21.pageArrowDisabled], children: "\u203A" })
@@ -34546,11 +35126,14 @@ function ProgressPill({
34546
35126
  }
34547
35127
  const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
34548
35128
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34549
- import_react_native23.View,
35129
+ import_react_native_reanimated4.default.View,
34550
35130
  {
34551
- pointerEvents: "box-none",
35131
+ pointerEvents: chromeVisible ? "box-none" : "none",
35132
+ accessibilityElementsHidden: !chromeVisible,
35133
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
34552
35134
  style: [
34553
35135
  styles21.frame,
35136
+ chromeMotion,
34554
35137
  resolvePageScrubberOverlayStyle(),
34555
35138
  {
34556
35139
  top: offsets.progress,
@@ -35279,15 +35862,10 @@ function ReadingShell({
35279
35862
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35280
35863
  Topbar_default,
35281
35864
  {
35865
+ documentType,
35282
35866
  engine,
35283
35867
  title,
35284
- onOpenOverflow: () => {
35285
- closeMobileDestination();
35286
- setInfoOpen(false);
35287
- setActionsOpen(false);
35288
- setSettingsOpen(false);
35289
- setOverflowOpen(true);
35290
- },
35868
+ onOpenSettings: () => openDestination("display"),
35291
35869
  onOpenPageJump: () => setPageJumpOpen(true),
35292
35870
  showPageNavigationControls: false
35293
35871
  }
@@ -35295,6 +35873,7 @@ function ReadingShell({
35295
35873
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native26.View, { style: styles24.viewerStage, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35296
35874
  Viewer_default,
35297
35875
  {
35876
+ documentType,
35298
35877
  engine,
35299
35878
  ...viewerProps,
35300
35879
  pageScrubActive
@@ -35303,7 +35882,13 @@ function ReadingShell({
35303
35882
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35304
35883
  ProgressPill,
35305
35884
  {
35885
+ navigationInDock: true,
35306
35886
  documentType,
35887
+ onTurnPage: documentType === "epub" ? (direction) => {
35888
+ const paginatedEngine = engine;
35889
+ void paginatedEngine.turnEpubPage?.(direction).catch(() => {
35890
+ });
35891
+ } : void 0,
35307
35892
  onNavigateToPage: navigateToPage,
35308
35893
  onPress: () => openDestination(
35309
35894
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
@@ -35315,6 +35900,12 @@ function ReadingShell({
35315
35900
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35316
35901
  BottomBar_default,
35317
35902
  {
35903
+ onNavigateToPage: navigateToPage,
35904
+ onOpenPages: () => setPageJumpOpen(true),
35905
+ onTurnPage: documentType === "epub" ? (direction) => {
35906
+ const paginatedEngine = engine;
35907
+ void paginatedEngine.turnEpubPage?.(direction).catch((error) => console.warn("[Papyrus] page navigation failed", error));
35908
+ } : void 0,
35318
35909
  documentType,
35319
35910
  onOpenDestination: openDestination,
35320
35911
  onOpenInfo: () => openDestination("info"),
@@ -35394,6 +35985,7 @@ function ReadingShell({
35394
35985
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35395
35986
  SettingsSheet_default,
35396
35987
  {
35988
+ onOpenInfo: () => openDestination("info"),
35397
35989
  engine,
35398
35990
  documentType,
35399
35991
  visible: settingsOpen,
@@ -35406,6 +35998,7 @@ function ReadingShell({
35406
35998
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35407
35999
  PageJumpModal,
35408
36000
  {
36001
+ documentType,
35409
36002
  visible: pageJumpOpen,
35410
36003
  currentPage,
35411
36004
  pageCount,