@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.mjs CHANGED
@@ -20512,6 +20512,67 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20512
20512
  <!-- @papyrus-comic-runtime:end -->
20513
20513
  <script>
20514
20514
  (function () {
20515
+ /* @papyrus-epub-reader:start */
20516
+ (function(root){
20517
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
20518
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\\([^\\n<>]+\\)$/.test(value);}
20519
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
20520
+ function gesture(start,end,state){
20521
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
20522
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
20523
+ 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';
20524
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
20525
+ return null;
20526
+ }
20527
+ function location(raw,count,readingMode,geometry){
20528
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
20529
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
20530
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
20531
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
20532
+ var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
20533
+ // Chromium may round a column offset down by a fraction of a CSS pixel.
20534
+ // Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
20535
+ if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
20536
+ var boundary=Math.round(geometry.offset/geometry.pageWidth);
20537
+ if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
20538
+ visualPage=Math.min(total,Math.max(1,boundary+1));
20539
+ atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
20540
+ atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
20541
+ }
20542
+ }
20543
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
20544
+ 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};
20545
+ }
20546
+ function installResizeAnchor(manager,readCfi){
20547
+ var original=manager.resize,anchor=null;
20548
+ function resize(width,height,target){
20549
+ if(validCfi(target))anchor=target;
20550
+ if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
20551
+ // epub.js accepts a logical target as its third argument. Keep it across
20552
+ // keyboard/dialog reflows instead of adopting each new viewport start.
20553
+ return original.call(this,width,height,anchor || target);
20554
+ }
20555
+ manager.resize=resize;
20556
+ return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
20557
+ }
20558
+ function install(contents,config){
20559
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
20560
+ 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;}
20561
+ function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20562
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay],[data-papyrus-ink-capture]'));}
20563
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20564
+ 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();}
20565
+ function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
20566
+ 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);}
20567
+ function cancel(){start=null;blocked=true;}
20568
+ function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
20569
+ 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);
20570
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
20571
+ }
20572
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
20573
+ })(typeof window==='undefined'?globalThis:window);
20574
+
20575
+ /* @papyrus-epub-reader:end */
20515
20576
  /* @papyrus-epub-annotations:start */
20516
20577
  (function (root, factory) {
20517
20578
  if (typeof module === 'object' && module.exports) module.exports = factory();
@@ -20523,6 +20584,84 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20523
20584
  typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
20524
20585
  typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
20525
20586
  }
20587
+ function simplifyPath(points, tolerance, maxPoints) {
20588
+ if (!Array.isArray(points) || points.length <= 2) return Array.isArray(points) ? points.slice() : [];
20589
+ tolerance = Number.isFinite(tolerance) ? Math.max(0, tolerance) : 1.5;
20590
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20591
+ var keep = new Uint8Array(points.length); keep[0] = 1; keep[points.length - 1] = 1;
20592
+ var stack = [[0, points.length - 1]], toleranceSquared = tolerance * tolerance;
20593
+ while (stack.length) {
20594
+ var interval = stack.pop(), first = interval[0], last = interval[1], a = points[first], b = points[last];
20595
+ var dx = b.x - a.x, dy = b.y - a.y, lengthSquared = dx * dx + dy * dy, maxDistance = toleranceSquared, split = -1;
20596
+ for (var i = first + 1; i < last; i++) {
20597
+ var p = points[i], ratio = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
20598
+ var px = a.x + ratio * dx, py = a.y + ratio * dy, ox = p.x - px, oy = p.y - py, distance = ox * ox + oy * oy;
20599
+ if (distance > maxDistance) { maxDistance = distance; split = i; }
20600
+ }
20601
+ if (split >= 0) { keep[split] = 1; stack.push([first, split], [split, last]); }
20602
+ }
20603
+ var result = []; for (var j = 0; j < points.length; j++) if (keep[j]) result.push(points[j]);
20604
+ if (result.length <= maxPoints) return result;
20605
+ var capped = []; for (var k = 0; k < maxPoints; k++) capped.push(result[Math.round(k * (result.length - 1) / (maxPoints - 1))]);
20606
+ return capped;
20607
+ }
20608
+ function compactGesturePoints(points, maxPoints) {
20609
+ if (!Array.isArray(points)) return [];
20610
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 1024;
20611
+ if (points.length <= maxPoints) return points.slice();
20612
+ var compacted = [];
20613
+ for (var i = 0; i < maxPoints; i++) compacted.push(points[Math.round(i * (points.length - 1) / (maxPoints - 1))]);
20614
+ return compacted;
20615
+ }
20616
+ function densifyPath(points, maxStep, maxPoints) {
20617
+ if (!Array.isArray(points) || !points.length) return [];
20618
+ if (points.length === 1) return points.slice();
20619
+ maxStep = Number.isFinite(maxStep) ? Math.max(1, maxStep) : 12;
20620
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20621
+ var cumulative = [0], total = 0;
20622
+ for (var i = 1; i < points.length; i++) {
20623
+ if (![points[i-1].x,points[i-1].y,points[i].x,points[i].y].every(Number.isFinite)) return [];
20624
+ total += Math.hypot(points[i].x-points[i-1].x, points[i].y-points[i-1].y);
20625
+ cumulative.push(total);
20626
+ }
20627
+ if (!total) return [points[0], points[points.length-1]];
20628
+ var intervals = Math.min(maxPoints-1, Math.max(1, Math.ceil(total/maxStep))), result = [], segment = 1;
20629
+ for (var sample = 0; sample <= intervals; sample++) {
20630
+ var distance = total*sample/intervals;
20631
+ while (segment < cumulative.length-1 && cumulative[segment] < distance) segment++;
20632
+ var start = points[segment-1], end = points[segment], span = cumulative[segment]-cumulative[segment-1];
20633
+ var ratio = span ? (distance-cumulative[segment-1])/span : 0;
20634
+ result.push({x:start.x+(end.x-start.x)*ratio,y:start.y+(end.y-start.y)*ratio});
20635
+ }
20636
+ return result;
20637
+ }
20638
+ function createSpatialIndex(entries, cellSize) {
20639
+ cellSize = Number.isFinite(cellSize) ? Math.max(8, cellSize) : 64;
20640
+ var cells = new Map();
20641
+ entries.forEach(function(entry) {
20642
+ var left = Math.floor(entry.left / cellSize), right = Math.floor(entry.right / cellSize);
20643
+ var top = Math.floor(entry.top / cellSize), bottom = Math.floor(entry.bottom / cellSize);
20644
+ for (var x = left; x <= right; x++) for (var y = top; y <= bottom; y++) {
20645
+ var key = x + ':' + y, bucket = cells.get(key); if (!bucket) { bucket = []; cells.set(key, bucket); } bucket.push(entry);
20646
+ }
20647
+ });
20648
+ return {cells:cells,cellSize:cellSize};
20649
+ }
20650
+ function queryNearestTextRect(index, x, y, maxDistance) {
20651
+ if (!index || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(maxDistance) || maxDistance < 0) return null;
20652
+ var size = index.cellSize, minX = Math.floor((x-maxDistance)/size), maxX = Math.floor((x+maxDistance)/size);
20653
+ var minY = Math.floor((y-maxDistance)/size), maxY = Math.floor((y+maxDistance)/size), seen = new Set(), best = null;
20654
+ for (var cx=minX; cx<=maxX; cx++) for (var cy=minY; cy<=maxY; cy++) {
20655
+ var bucket = index.cells.get(cx+':'+cy); if (!bucket) continue;
20656
+ bucket.forEach(function(entry) {
20657
+ if (seen.has(entry)) return; seen.add(entry);
20658
+ var nearestX = Math.max(entry.left,Math.min(entry.right,x)), nearestY = Math.max(entry.top,Math.min(entry.bottom,y));
20659
+ var dx=x-nearestX,dy=y-nearestY,distance=Math.sqrt(dx*dx+dy*dy);
20660
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={entry:entry,distance:distance,x:nearestX,y:nearestY};
20661
+ });
20662
+ }
20663
+ return best;
20664
+ }
20526
20665
  function textNodes(document) {
20527
20666
  var walker = document.createTreeWalker(document.body, 4);
20528
20667
  var nodes = [], text = '', node;
@@ -20572,13 +20711,196 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20572
20711
  if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
20573
20712
  doc.body.appendChild(overlay);
20574
20713
  var registry = new Map(), hitRegions = [];
20714
+ var inkRegistry = new Map(), rangeCache = new Map(), spatialIndex = null, activeGesture = null, inkConfig = {enabled:false,preset:'ink'};
20715
+ var inkSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
20716
+ inkSvg.setAttribute('data-papyrus-ink-overlay','');
20717
+ inkSvg.style.cssText = 'position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1';
20718
+ overlay.appendChild(inkSvg);
20719
+ var inkCanvas = doc.createElement('canvas');
20720
+ inkCanvas.dataset.papyrusInkCapture = '';
20721
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
20722
+ doc.body.appendChild(inkCanvas);
20723
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
20724
+ function resizeCanvas() {
20725
+ var bodyRect=doc.body.getBoundingClientRect(), ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
20726
+ var width=Math.max(1,Math.min(4096,doc.body.clientWidth||bodyRect.width||Math.min(4096,win.innerWidth||1)));
20727
+ var height=Math.max(1,Math.min(4096,doc.body.clientHeight||bodyRect.height||Math.min(4096,win.innerHeight||1)));
20728
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
20729
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
20730
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
20731
+ }
20732
+ function collectTextRects() {
20733
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
20734
+ while ((node=walker.nextNode())) {
20735
+ var parent=node.parentElement;
20736
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
20737
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
20738
+ for (var start=0; start<text.length;) {
20739
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
20740
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
20741
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
20742
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20743
+ rects.forEach(function(rect) {
20744
+ if (!(rect.width>0 && rect.height>0)) return;
20745
+ 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,
20746
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
20747
+ });
20748
+ start=end;
20749
+ }
20750
+ }
20751
+ return entries;
20752
+ }
20753
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
20754
+ function inkViewportExtent() {
20755
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
20756
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
20757
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
20758
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
20759
+ }
20760
+ function inkStrokeWidthPx(width,preset) {
20761
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
20762
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
20763
+ }
20764
+ function closestGlyph(entry,x,y,maxDistance) {
20765
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
20766
+ for (var glyph of source) {
20767
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
20768
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20769
+ rects.forEach(function(rect) {
20770
+ if (!(rect.width>0 && rect.height>0)) return;
20771
+ 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));
20772
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
20773
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
20774
+ });
20775
+ }
20776
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
20777
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
20778
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\\([^\\n<>]+\\)$/.test(cfiRange)) return null;
20779
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
20780
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
20781
+ }
20782
+ function renderInkAnnotation(annotation,keep,bodyRect) {
20783
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
20784
+ var data=annotation.epubInk;
20785
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
20786
+ keep.add(annotation.id);
20787
+ var group=inkRegistry.get(annotation.id);
20788
+ 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); }
20789
+ group.replaceChildren();
20790
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
20791
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
20792
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
20793
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
20794
+ data.segments.forEach(function(segment) {
20795
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
20796
+ var paths=[], current=[];
20797
+ segment.points.forEach(function(point) {
20798
+ var range=rangeCache.get(point.cfiRange);
20799
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
20800
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
20801
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
20802
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
20803
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
20804
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
20805
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
20806
+ current.push({x:x,y:y,lineHeight:lineHeight});
20807
+ });
20808
+ if(current.length>1)paths.push(current);
20809
+ paths.forEach(function(points) {
20810
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
20811
+ 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);}
20812
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
20813
+ 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);
20814
+ });
20815
+ });
20816
+ }
20817
+ function clearPreview() {
20818
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
20819
+ inkSvg.querySelectorAll('[data-papyrus-ink-preview]').forEach(function(path){path.remove();});
20820
+ }
20821
+ function createPreviewPath(x,y) {
20822
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
20823
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
20824
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
20825
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
20826
+ path.style.pointerEvents='none';inkSvg.appendChild(path);return path;
20827
+ }
20828
+ function updatePreviewPath(clientX,clientY) {
20829
+ var bodyRect=doc.body.getBoundingClientRect(),x=clientX-bodyRect.left,y=clientY-bodyRect.top;
20830
+ var gesture=activeGesture;
20831
+ if(!gesture.previewPath||gesture.previewPathPoints>=32){
20832
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
20833
+ gesture.previewPathPoints=1;
20834
+ }
20835
+ var pathData=gesture.previewPath.getAttribute('d')||'';
20836
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
20837
+ gesture.previewPathPoints++;
20838
+ gesture.previewLast={x:x,y:y};
20839
+ }
20840
+ function cancelGesture() {
20841
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20842
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
20843
+ }
20844
+ function commitGesture() {
20845
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20846
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
20847
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
20848
+ anchorSamples.forEach(function(point) {
20849
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
20850
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
20851
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
20852
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
20853
+ current.push(anchored);
20854
+ });
20855
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
20856
+ segments=segments.slice(0,64);
20857
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
20858
+ if(!pointCount)return;
20859
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
20860
+ 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))};});
20861
+ 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;}));
20862
+ 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;}));
20863
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
20864
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
20865
+ 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,
20866
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
20867
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
20868
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
20869
+ }
20870
+ function pointerDown(event) {
20871
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
20872
+ if(activeGesture){cancelGesture();return;}
20873
+ event.preventDefault();event.stopPropagation();
20874
+ 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};
20875
+ updatePreviewPath(event.clientX,event.clientY);
20876
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
20877
+ }
20878
+ function pointerMove(event) {
20879
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
20880
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
20881
+ event.preventDefault();event.stopPropagation();
20882
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
20883
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
20884
+ 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();}
20885
+ updatePreviewPath(event.clientX,event.clientY);
20886
+ activeGesture.last={x:event.clientX,y:event.clientY};
20887
+ }
20888
+ 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();}
20889
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
20890
+ resizeCanvas();rebuildSpatialIndex();
20891
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
20892
+ win.addEventListener('blur',pointerCancel);win.addEventListener('resize',resizeCanvas);
20575
20893
  function render() {
20576
20894
  frame = 0; if (!alive || !options.isCurrent()) return;
20577
- var keep = new Set(), noteGroups = new Map();
20895
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20578
20896
  hitRegions = [];
20897
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
20898
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
20899
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
20579
20900
  options.annotations().forEach(function(annotation) {
20580
20901
  var anchor = annotation.anchor;
20581
20902
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
20903
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
20582
20904
  var range = resolveRange(contents,anchor); if (!range) return;
20583
20905
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20584
20906
  if (!rects.length) return;
@@ -20590,6 +20912,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20590
20912
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
20591
20913
  var style = annotation.markupStyle || annotation.type;
20592
20914
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
20915
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
20593
20916
  rects.forEach(function(rect) {
20594
20917
  var mark = doc.createElement('div');
20595
20918
  var bodyRect=doc.body.getBoundingClientRect();
@@ -20602,8 +20925,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20602
20925
  if (hasNote) {
20603
20926
  var last = rects[rects.length-1], button = doc.createElement('button');
20604
20927
  button.textContent = '\u25CF'; button.setAttribute('aria-label',options.labels().comment || '');
20605
- 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';
20606
- var key = Math.floor(last.top/44);
20928
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
20929
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
20930
+ 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';
20931
+ var key = column+":"+Math.floor(last.top/44);
20607
20932
  var neighbors=noteGroups.get(key);
20608
20933
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
20609
20934
  neighbors.ids.push(annotation.id);
@@ -20614,22 +20939,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20614
20939
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20615
20940
  });
20616
20941
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
20942
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
20617
20943
  }
20618
20944
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
20945
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
20946
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
20947
+ function handleDocumentLoad(){rebuildAndSchedule();}
20619
20948
  function tapMarkup(event) {
20620
20949
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20621
20950
  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;});});
20622
20951
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20623
20952
  }
20624
20953
  doc.addEventListener('click',tapMarkup);
20625
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20954
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20626
20955
  if (observer) observer.observe(doc.body);
20627
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
20628
- if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20956
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
20957
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
20629
20958
  schedule();
20630
- 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();}};
20959
+ 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();}};
20631
20960
  }
20632
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20961
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20633
20962
  });
20634
20963
 
20635
20964
  /* @papyrus-epub-annotations:end */
@@ -20640,10 +20969,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20640
20969
  let currentType = null;
20641
20970
  let book = null;
20642
20971
  let rendition = null;
20972
+ let epubResizeAnchor = null;
20643
20973
  let documentSessionId = null;
20644
20974
  let annotationDocumentId;
20645
20975
  let annotationLabels = {};
20646
20976
  let epubAnnotations = [];
20977
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20647
20978
  const epubAnnotationRenderers = new Map();
20648
20979
  let spineItems = [];
20649
20980
  let textPages = [];
@@ -21052,6 +21383,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21052
21383
  };
21053
21384
 
21054
21385
  const clearViewer = () => {
21386
+ if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
21055
21387
  epubAnnotationRenderers.forEach(renderer => renderer.destroy());
21056
21388
  epubAnnotationRenderers.clear();
21057
21389
  if (epubInteractionCleanup) {
@@ -21708,29 +22040,63 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21708
22040
  pageCount,
21709
22041
  });
21710
22042
 
22043
+ if (book.locations?.generate) {
22044
+ const activeBook=book;
22045
+ activeBook.locations.generate(150).then(() => {
22046
+ if (isCurrentLoad() && activeBook===book && epubLocation) {
22047
+ epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
22048
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
22049
+ }
22050
+ }).catch(() => {});
22051
+ }
22052
+ return renderEpubRendition(loadId,generation,null);
22053
+ };
22054
+
22055
+ const renderEpubRendition = async (loadId,generation,target) => {
22056
+ const diagnosticPayload={loadId};
22057
+ const isCurrentLoad=()=>generation===epubLoadGeneration;
22058
+ if(epubInteractionCleanup)epubInteractionCleanup();
22059
+ if(rendition)rendition.destroy();
21711
22060
  clearViewer();
21712
- rendition = book.renderTo(viewer, {
21713
- manager: 'continuous',
21714
- allowScriptedContent: false,
21715
- width: '100%',
21716
- height: '100%',
21717
- flow: 'scrolled-continuous',
21718
- spread: 'none',
21719
- });
22061
+ rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
21720
22062
  sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
21721
22063
 
21722
22064
  if (rendition && rendition.hooks && rendition.hooks.content) {
21723
22065
  const session = documentSessionId;
21724
22066
  const activeRendition = rendition;
21725
22067
  rendition.hooks.content.register((contents) => {
22068
+ const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
22069
+ mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
22070
+ rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
22071
+ blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
22072
+ annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
22073
+ current: () => session === documentSessionId && activeRendition === rendition,
22074
+ interact: () => epubResizeAnchor?.invalidate(),
22075
+ tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
22076
+ turn: direction => {
22077
+ epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
22078
+ if(session !== documentSessionId || activeRendition !== rendition)return;
22079
+ epubResizeAnchor?.invalidate();
22080
+ return direction<0?activeRendition.prev():activeRendition.next();
22081
+ });
22082
+ epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
22083
+ },
22084
+ });
22085
+ const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
22086
+ epubSelectionCleanups.add(gestureCleanup);
22087
+ contents.__papyrusGestureCleanup=gestureCleanup;
21726
22088
  const href = spineItems[contents.sectionIndex]?.href;
21727
22089
  if (href && window.PapyrusEpubAnnotations) {
21728
22090
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21729
22091
  href, get documentId() { return annotationDocumentId; },
22092
+ spineIndex:contents.sectionIndex,
21730
22093
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
22094
+ pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21731
22095
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22096
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21732
22097
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21733
22098
  });
22099
+ renderer.setInkMode(epubInkMode);
21734
22100
  epubAnnotationRenderers.set(contents,renderer);
21735
22101
  }
21736
22102
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21746,8 +22112,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21746
22112
  typeof selectionWindow.getSelection === 'function'
21747
22113
  ) {
21748
22114
  let selectionTimer = null;
21749
- let lastSelectionKey = '';
21750
- let lastSelectionAt = 0;
22115
+
21751
22116
  const reportSelection = () => {
21752
22117
  selectionTimer = null;
21753
22118
  if (session !== documentSessionId || activeRendition !== rendition) return;
@@ -21762,11 +22127,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21762
22127
  try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
21763
22128
  const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
21764
22129
  if (!anchor) return;
21765
- const key = anchor.cfiRange + ':' + anchor.quote;
21766
- const now = Date.now();
21767
- if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
21768
- lastSelectionKey = key;
21769
- lastSelectionAt = now;
22130
+ if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21770
22131
  const payload = {documentSessionId: session, text: anchor.quote,
21771
22132
  pageIndex: contents.sectionIndex + 1, anchor};
21772
22133
  nativeMenuSelection = {contents, payload};
@@ -21781,6 +22142,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21781
22142
  // The content document still emits selectionchange, so resolve its
21782
22143
  // live Range to a CFI before the native menu action can fire.
21783
22144
  if (text) reportSelection();
22145
+ else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
21784
22146
  };
21785
22147
  selectionDocument.addEventListener('selectionchange', handleSelectionChange);
21786
22148
  const selectionCleanup = () => {
@@ -21803,6 +22165,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21803
22165
  typeof rendition.hooks.unloaded.register === 'function'
21804
22166
  ) {
21805
22167
  rendition.hooks.unloaded.register((view) => {
22168
+ view?.contents?.__papyrusGestureCleanup?.();
21806
22169
  const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
21807
22170
  if (typeof cleanup === 'function') cleanup();
21808
22171
  const renderer = epubAnnotationRenderers.get(view?.contents);
@@ -21818,21 +22181,37 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21818
22181
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21819
22182
  const href = spineItems[contents.sectionIndex]?.href;
21820
22183
  const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
21821
- if (!anchor) return;
22184
+ if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21822
22185
  epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
21823
22186
  const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
21824
22187
  nativeMenuSelection = {contents, payload};
21825
22188
  sendEvent('EPUB_TEXT_SELECTED', payload);
21826
22189
  });
21827
- ['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
21828
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22190
+ ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
22191
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21829
22192
  }));
21830
22193
  rendition.on('relocated', (location) => {
22194
+ if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21831
22195
  const start = location && location.start;
21832
22196
  let index = start && Number.isInteger(start.index) ? start.index : -1;
21833
22197
  if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
21834
22198
  if (index < 0 || index >= spineItems.length) return;
21835
22199
  epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
22200
+ let pageGeometry;
22201
+ const manager=rendition.manager,layout=manager?.layout;
22202
+ if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
22203
+ const visible=manager.visible?.();
22204
+ const view=visible?.find(view=>view.index===index);
22205
+ if(view && manager.container){
22206
+ pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
22207
+ }
22208
+ }
22209
+ epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
22210
+ if(epubLocation){
22211
+ epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
22212
+ if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
22213
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
22214
+ }
21836
22215
  const nextPage = index + 1;
21837
22216
  if (nextPage === currentPage) return;
21838
22217
  currentPage = nextPage;
@@ -21840,21 +22219,23 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21840
22219
  });
21841
22220
  }
21842
22221
 
22222
+ applyEpubZoom();
21843
22223
  sendEpubDiagnostic('epub.display.start', diagnosticPayload);
21844
22224
  await withEpubStageTimeout(
21845
- displayEpubPage(0),
22225
+ rendition.display(target || spineItems[0]?.href),
21846
22226
  'rendition.display',
21847
22227
  diagnosticPayload,
21848
22228
  );
21849
22229
  if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
21850
22230
  sendEpubDiagnostic('epub.display.end', diagnosticPayload);
22231
+ const resizeRendition=rendition;
22232
+ epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
21851
22233
  if (
21852
22234
  rendition &&
21853
22235
  rendition.manager &&
21854
22236
  typeof rendition.manager.on === 'function' &&
21855
22237
  typeof rendition.on === 'function'
21856
22238
  ) {
21857
- let touchStart = null;
21858
22239
  let lastScrollOffset = -1;
21859
22240
  const manager = rendition.manager;
21860
22241
  epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
@@ -21929,48 +22310,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21929
22310
  return result;
21930
22311
  };
21931
22312
  }
21932
- const getTouchPoint = (event) => {
21933
- const touch =
21934
- (event && event.changedTouches && event.changedTouches[0]) ||
21935
- (event && event.touches && event.touches[0]);
21936
- if (!touch) return null;
21937
- const x = Number(touch.screenX ?? touch.clientX);
21938
- const y = Number(touch.screenY ?? touch.clientY);
21939
- return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
21940
- };
21941
- const handleScroll = (event) => {
22313
+ const handleScroll = event => {
21942
22314
  const offsetY = Math.max(0, Number(event && event.top) || 0);
21943
22315
  epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
21944
- if (Math.abs(offsetY - lastScrollOffset) < 1) return;
21945
- lastScrollOffset = offsetY;
21946
- sendEvent('VIEWER_SCROLL', { offsetY });
22316
+ if (Math.abs(offsetY-lastScrollOffset)<1) return;
22317
+ lastScrollOffset=offsetY;
22318
+ sendEvent('VIEWER_SCROLL',{offsetY});
21947
22319
  };
21948
- const handleTouchStart = (event) => {
21949
- touchStart = getTouchPoint(event);
21950
- epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
21951
- };
21952
- const handleTouchMove = (event) => {
21953
- epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
21954
- };
21955
- const handleTouchEnd = (event) => {
21956
- const end = getTouchPoint(event);
21957
- const start = touchStart;
21958
- touchStart = null;
21959
- const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
21960
- if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
21961
- sendEvent('VIEWER_TAP', {});
21962
- }
21963
- epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
21964
- };
21965
-
21966
22320
  manager.on('scroll', handleScroll);
21967
22321
  const handleScrolled = (event) => {
21968
22322
  epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
21969
22323
  };
21970
22324
  manager.on('scrolled', handleScrolled);
21971
- rendition.on('touchstart', handleTouchStart);
21972
- rendition.on('touchmove', handleTouchMove);
21973
- rendition.on('touchend', handleTouchEnd);
22325
+
21974
22326
  epubInteractionCleanup = () => {
21975
22327
  epubSelectionCleanups.forEach((cleanup) => cleanup());
21976
22328
  epubSelectionCleanups.clear();
@@ -21979,16 +22331,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21979
22331
  if (originalEnqueue) queue.enqueue = originalEnqueue;
21980
22332
  if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
21981
22333
  if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
21982
- if (typeof rendition.off === 'function') {
21983
- rendition.off('touchstart', handleTouchStart);
21984
- rendition.off('touchmove', handleTouchMove);
21985
- rendition.off('touchend', handleTouchEnd);
21986
- }
21987
22334
  epubScrollDiagnostics = null;
21988
22335
  };
21989
22336
  }
21990
- applyEpubZoom();
21991
-
21992
22337
  const outline = await withEpubStageTimeout(
21993
22338
  buildOutline(),
21994
22339
  'outline',
@@ -22002,6 +22347,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22002
22347
  return { pageCount, outline };
22003
22348
  };
22004
22349
 
22350
+ let epubNavigationQueue = Promise.resolve();
22351
+ let epubNavigationRequest = 0;
22005
22352
  const displayEpubPage = async (pageIndex) => {
22006
22353
  if (!rendition) return;
22007
22354
  const item = spineItems[pageIndex];
@@ -22011,9 +22358,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22011
22358
  target,
22012
22359
  pageIndex,
22013
22360
  });
22014
- await rendition.display(target);
22015
- currentPage = pageIndex + 1;
22016
- sendState();
22361
+ const activeRendition = rendition;
22362
+ const session = documentSessionId;
22363
+ const request = ++epubNavigationRequest;
22364
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22365
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22366
+ epubResizeAnchor?.invalidate();
22367
+ await activeRendition.display(target);
22368
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22369
+ currentPage = pageIndex + 1;
22370
+ sendState();
22371
+ });
22372
+ epubNavigationQueue = navigation;
22373
+ await navigation;
22017
22374
  };
22018
22375
 
22019
22376
  const getTextContent = async (pageIndex) => {
@@ -22115,6 +22472,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22115
22472
  };
22116
22473
 
22117
22474
  let nativeMenuSelection = null;
22475
+ let epubViewMode = "single";
22476
+ let epubLocation = null;
22118
22477
 
22119
22478
  const handleCommand = async (message) => {
22120
22479
  const { id, kind, payload } = message;
@@ -22126,7 +22485,13 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22126
22485
  documentSessionId = payload.documentSessionId || null;
22127
22486
  epubAnnotations = [];
22128
22487
  annotationDocumentId = undefined;
22488
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22129
22489
  zoom = 1.0;
22490
+ epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22491
+ epubLocation = null;
22492
+ nativeMenuSelection = null;
22493
+ epubNavigationRequest += 1;
22494
+ epubNavigationQueue = Promise.resolve();
22130
22495
 
22131
22496
  if (currentType === 'text') {
22132
22497
  const result = await loadText(
@@ -22181,17 +22546,24 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22181
22546
  epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22182
22547
  } else {
22183
22548
  const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
22184
- if (!annotation) throw new Error('Annotation not available');
22549
+ if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
22185
22550
  // Navigation uses the CFI, never a synthetic percentage/page rectangle.
22186
22551
  const navigationSession = documentSessionId;
22187
22552
  const activeRendition = rendition;
22188
22553
  // Open the confirmed chapter first, resolve its quote, then navigate its actual range.
22189
- await activeRendition.display(annotation.anchor.href);
22190
- if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22191
- const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22192
- const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22193
- if(!range)throw new Error('EPUB anchor unresolved');
22194
- await activeRendition.display(contents.cfiFromRange(range));
22554
+ epubNavigationRequest += 1;
22555
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22556
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22557
+ epubResizeAnchor?.invalidate();
22558
+ await activeRendition.display(annotation.anchor.href);
22559
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22560
+ const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22561
+ const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22562
+ if(!range)throw new Error('EPUB anchor unresolved');
22563
+ await activeRendition.display(contents.cfiFromRange(range));
22564
+ });
22565
+ epubNavigationQueue = navigation;
22566
+ await navigation;
22195
22567
  }
22196
22568
  sendResponse(id,true,null); return;
22197
22569
  }
@@ -22218,12 +22590,61 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22218
22590
  return;
22219
22591
  }
22220
22592
 
22593
+ if (kind === 'epub-turn-page') {
22594
+ if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
22595
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
22596
+ const active = rendition;
22597
+ epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
22598
+ if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
22599
+ epubResizeAnchor?.invalidate();
22600
+ return payload.direction < 0 ? active.prev() : active.next();
22601
+ });
22602
+ await epubNavigationQueue;
22603
+ sendResponse(id,true,null); return;
22604
+ }
22605
+
22606
+ if (kind === 'set-epub-view-mode') {
22607
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
22608
+ const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
22609
+ {
22610
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22611
+ if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
22612
+ const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22613
+ epubViewMode=next;
22614
+ await renderEpubRendition('layout',epubLoadGeneration,target);
22615
+ });
22616
+ await epubNavigationQueue;
22617
+ }
22618
+ sendResponse(id,true,null);return;
22619
+ }
22620
+ if (kind === 'set-epub-ink-mode') {
22621
+ if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
22622
+ 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};
22623
+ epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
22624
+ sendResponse(id,true,null);return;
22625
+ }
22626
+ if(kind === 'epub-go-to-location'){
22627
+ if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22628
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
22629
+ await epubNavigationQueue;sendResponse(id,true,null);return;
22630
+ }
22631
+
22221
22632
  if (kind === 'set-zoom') {
22222
- zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
22223
- applyZoom();
22224
- sendState();
22225
- sendResponse(id, true, { zoom });
22226
- return;
22633
+ const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
22634
+ if(currentType === 'epub') {
22635
+ const session=documentSessionId;
22636
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22637
+ if(session!==documentSessionId)return;
22638
+ const active=rendition;
22639
+ const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22640
+ zoom=requestedZoom;applyEpubZoom();
22641
+ // Let the content CSS update before redisplaying the logical anchor.
22642
+ await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
22643
+ if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
22644
+ });
22645
+ await epubNavigationQueue;
22646
+ } else {zoom=requestedZoom;applyZoom();}
22647
+ sendState();sendResponse(id,true,{zoom});return;
22227
22648
  }
22228
22649
 
22229
22650
  if (kind === 'set-rotation') {
@@ -23399,6 +23820,7 @@ var IconToolStrikeout = ({
23399
23820
  ),
23400
23821
  /* @__PURE__ */ jsx2(Rect, { x: "10.9", y: "17.1", width: "14.2", height: "2.4", rx: "1.2", fill: color })
23401
23822
  ] });
23823
+ var IconStrikeout = ({ size = 22, color = defaultColor, strokeWidth = 1.8 }) => /* @__PURE__ */ jsx2(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx2(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" }) });
23402
23824
  var IconCopy = ({
23403
23825
  size = 20,
23404
23826
  color = defaultColor,
@@ -23463,6 +23885,39 @@ var IconToolNote = ({
23463
23885
  /* @__PURE__ */ jsx2(Line, { x1: "15", y1: "14.8", x2: "21.6", y2: "14.8", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" }),
23464
23886
  /* @__PURE__ */ jsx2(Line, { x1: "15", y1: "18.2", x2: "19.2", y2: "18.2", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" })
23465
23887
  ] });
23888
+ var IconHighlight = ({
23889
+ size = 24,
23890
+ color = "#fbbf24"
23891
+ }) => /* @__PURE__ */ jsxs(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: [
23892
+ /* @__PURE__ */ jsx2(
23893
+ Path,
23894
+ {
23895
+ d: "M4 15h16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3Z",
23896
+ fill: color,
23897
+ opacity: "0.85"
23898
+ }
23899
+ ),
23900
+ /* @__PURE__ */ jsx2(
23901
+ Path,
23902
+ {
23903
+ d: "M6 5h12v10H6V5Z",
23904
+ stroke: color,
23905
+ strokeWidth: "2",
23906
+ strokeLinecap: "round",
23907
+ strokeLinejoin: "round",
23908
+ fill: "none"
23909
+ }
23910
+ ),
23911
+ /* @__PURE__ */ jsx2(
23912
+ Path,
23913
+ {
23914
+ d: "M8 8h8M8 11h5",
23915
+ stroke: color,
23916
+ strokeWidth: "2",
23917
+ strokeLinecap: "round"
23918
+ }
23919
+ )
23920
+ ] });
23466
23921
  var IconUnderline = ({
23467
23922
  size = 24,
23468
23923
  color = "#60a5fa",
@@ -25673,6 +26128,39 @@ var styles = StyleSheet.create({
25673
26128
  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;
25674
26129
  var PageRenderer_default = memo(PageRenderer, arePageRendererPropsEqual);
25675
26130
 
26131
+ // components/PapyrusSafeArea.tsx
26132
+ import React3, { useContext as useContext2 } from "react";
26133
+ import {
26134
+ SafeAreaInsetsContext,
26135
+ SafeAreaProvider
26136
+ } from "react-native-safe-area-context";
26137
+ import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
26138
+ var papyrusSafeAreaContext = SafeAreaInsetsContext;
26139
+ var ZERO_SAFE_AREA_INSETS = {
26140
+ top: 0,
26141
+ bottom: 0,
26142
+ left: 0,
26143
+ right: 0
26144
+ };
26145
+ var usePapyrusSafeAreaInsets = () => useContext2(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
26146
+ function PapyrusSafeAreaBoundary({
26147
+ children
26148
+ }) {
26149
+ const insets = useContext2(papyrusSafeAreaContext);
26150
+ if (insets) return /* @__PURE__ */ jsx4(Fragment, { children });
26151
+ return React3.createElement(SafeAreaProvider, null, children);
26152
+ }
26153
+
26154
+ // components/epubReadingLocation.ts
26155
+ function parseEpubReadingLocation(raw) {
26156
+ if (!raw || typeof raw !== "object") return null;
26157
+ const p = raw;
26158
+ 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;
26159
+ if (p.mode === "chapter-scroll") return p.visualPage === null && p.visualPageCount === null ? p : null;
26160
+ if (p.mode !== "paged" || !Number.isSafeInteger(p.visualPage) || (p.visualPage ?? 0) < 1 || !Number.isSafeInteger(p.visualPageCount) || (p.visualPageCount ?? 0) < (p.visualPage ?? 0)) return null;
26161
+ return p;
26162
+ }
26163
+
25676
26164
  // components/WebViewViewer.tsx
25677
26165
  import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
25678
26166
  import { Image, StyleSheet as StyleSheet2, View as View2, Alert } from "react-native";
@@ -25724,9 +26212,15 @@ var parseWebViewInteraction = (raw) => {
25724
26212
  };
25725
26213
 
25726
26214
  // components/epubAnnotationEvents.ts
25727
- import { validateAnnotationAnchor } from "@papyrus-sdk/core";
26215
+ import { validateAnnotationAnchor, validateEpubInkData } from "@papyrus-sdk/core";
26216
+ var EPUB_INK_PRESETS = ["ink", "highlight", "underline"];
26217
+ function resolveEpubInkMode(input) {
26218
+ if (!input.isLoaded || input.documentType !== "epub") return null;
26219
+ const preset = EPUB_INK_PRESETS.includes(input.preset) ? input.preset : "ink";
26220
+ return { enabled: input.activeTool === "ink" && EPUB_INK_PRESETS.includes(input.preset), preset, color: input.color, opacity: input.opacity, strokeWidth: input.strokeWidth };
26221
+ }
25728
26222
  function parseEpubAnnotationEvent(raw, session, documentId) {
25729
- if (!session || raw.length > 2e5) return null;
26223
+ if (!session || raw.length > 75e4) return null;
25730
26224
  let message;
25731
26225
  try {
25732
26226
  message = JSON.parse(raw);
@@ -25736,13 +26230,20 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
25736
26230
  const p = message?.payload;
25737
26231
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
25738
26232
  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;
26233
+ if (message.name === "EPUB_INK_CREATED") {
26234
+ const a = p.annotation, sidecar = a?.epubInk, first = sidecar?.segments?.[0], point = first?.points?.[0];
26235
+ if (!a || a.type !== "ink" || typeof a.id !== "string" || !a.id || a.id.length > 256 || !Number.isSafeInteger(a.pageIndex) || a.pageIndex < 1 || !validateAnnotationAnchor(a.anchor) || a.anchor.kind !== "epub-cfi" || !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;
26236
+ const rect = a.rect, path = a.path;
26237
+ 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;
26238
+ return { kind: "ink", session, annotation: a };
26239
+ }
25739
26240
  if (message.name !== "EPUB_TEXT_SELECTED" || !validateAnnotationAnchor(p.anchor) || p.anchor.kind !== "epub-cfi" || p.text !== p.anchor.quote || !Number.isSafeInteger(p.pageIndex) || p.pageIndex < 1) return null;
25740
26241
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
25741
26242
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
25742
26243
  }
25743
26244
 
25744
26245
  // components/WebViewViewer.tsx
25745
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
26246
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
25746
26247
  var runtimeAsset = require_runtime();
25747
26248
  var resolveRuntimeSource = (asset) => {
25748
26249
  if (typeof asset === "string") {
@@ -25762,6 +26263,7 @@ var resolveRuntimeSource = (asset) => {
25762
26263
  };
25763
26264
  var WebViewViewer = ({
25764
26265
  engine,
26266
+ documentType,
25765
26267
  documentId,
25766
26268
  onDefineSelection,
25767
26269
  defineSelectionMode,
@@ -25770,7 +26272,8 @@ var WebViewViewer = ({
25770
26272
  onTap
25771
26273
  }) => {
25772
26274
  const webViewRef = useRef2(null);
25773
- const { pageTheme, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
26275
+ const insets = usePapyrusSafeAreaInsets();
26276
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
25774
26277
  const [selection, setSelection] = useState2(null);
25775
26278
  const t = getStrings(locale);
25776
26279
  const bridgeEngine = engine;
@@ -25851,6 +26354,19 @@ var WebViewViewer = ({
25851
26354
  (note !== void 0 ? beginAnnotationDraft : addAnnotation)(createContextualAnnotation({ id: `annotation-${Date.now()}-${Math.random().toString(36).slice(2)}`, anchor: selected.anchor, pageIndex: selected.pageIndex, style, color: annotationColor, opacity: annotationOpacity, note }));
25852
26355
  setSelection(null);
25853
26356
  };
26357
+ useEffect2(() => {
26358
+ if (!isLoaded) return;
26359
+ webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26360
+ }, [isLoaded, epubReadingMode, bridgeEngine]);
26361
+ useEffect2(() => {
26362
+ if (!isLoaded || documentType !== "epub") return;
26363
+ const inkMode = resolveEpubInkMode({ documentType, isLoaded, activeTool, preset: activeDrawToolPreset, color: annotationColor, opacity: annotationOpacity, strokeWidth: inkStrokeWidth });
26364
+ if (!inkMode) return;
26365
+ webViewRef.current?.postMessage(JSON.stringify({ id: `epub-ink-mode-${Date.now()}`, kind: "set-epub-ink-mode", payload: {
26366
+ ...inkMode,
26367
+ documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.()
26368
+ } }));
26369
+ }, [isLoaded, documentType, activeTool, activeDrawToolPreset, annotationColor, annotationOpacity, inkStrokeWidth, bridgeEngine]);
25854
26370
  const sendSelectionCommand = (key) => {
25855
26371
  webViewRef.current?.postMessage(JSON.stringify({
25856
26372
  type: "epub-selection-action",
@@ -25874,6 +26390,11 @@ var WebViewViewer = ({
25874
26390
  let annotationRaw = raw;
25875
26391
  try {
25876
26392
  const message = JSON.parse(raw);
26393
+ if (message.type === "event" && message.name === "EPUB_LOCATION" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) {
26394
+ const location = parseEpubReadingLocation(message.payload.location);
26395
+ if (location) setDocumentState({ epubLocation: location });
26396
+ }
26397
+ if (message.type === "event" && message.name === "EPUB_CONTENT_TAP" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) onTap?.();
25877
26398
  if (message.type === "event" && message.name === "EPUB_SELECTION_ACTION") {
25878
26399
  action = message.payload?.action;
25879
26400
  annotationRaw = JSON.stringify({ ...message, name: "EPUB_TEXT_SELECTED" });
@@ -25905,6 +26426,10 @@ var WebViewViewer = ({
25905
26426
  else setSelectedAnnotation(annotationEvent.id);
25906
26427
  return;
25907
26428
  }
26429
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26430
+ addAnnotation(annotationEvent.annotation);
26431
+ setSelection(null);
26432
+ }
25908
26433
  const interaction = parseWebViewInteraction(raw);
25909
26434
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
25910
26435
  if (interaction?.kind === "tap") onTap?.();
@@ -25962,8 +26487,8 @@ var WebViewViewer = ({
25962
26487
  maxWidth: resolvedMaxPageWidth,
25963
26488
  alignSelf: "center"
25964
26489
  } : void 0;
25965
- return /* @__PURE__ */ jsxs3(View2, { style: styles2.container, children: [
25966
- /* @__PURE__ */ jsx4(
26490
+ return /* @__PURE__ */ jsxs3(View2, { style: [styles2.container, documentType === "epub" && { paddingTop: insets.top, paddingBottom: insets.bottom }], children: [
26491
+ /* @__PURE__ */ jsx5(
25967
26492
  WebView,
25968
26493
  {
25969
26494
  ref: webViewRef,
@@ -25987,7 +26512,7 @@ var WebViewViewer = ({
25987
26512
  style: [styles2.webview, cappedWebViewStyle]
25988
26513
  }
25989
26514
  ),
25990
- /* @__PURE__ */ jsx4(
26515
+ /* @__PURE__ */ jsx5(
25991
26516
  View2,
25992
26517
  {
25993
26518
  pointerEvents: "none",
@@ -26089,16 +26614,16 @@ function getNearbyPdfNotes(annotations, id) {
26089
26614
  // components/PdfNoteChooser.tsx
26090
26615
  import { FlatList, Modal, Pressable as Pressable2, StyleSheet as StyleSheet3, Text, View as View3 } from "react-native";
26091
26616
  import { getAnnotationNote, getAnnotationQuote } from "@papyrus-sdk/core";
26092
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
26617
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
26093
26618
  function PdfNoteChooser({ notes, title, cancel, onSelect, onClose }) {
26094
- return /* @__PURE__ */ jsx5(Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ jsx5(View3, { style: styles3.backdrop, children: /* @__PURE__ */ jsxs4(View3, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26095
- /* @__PURE__ */ jsx5(Text, { style: styles3.title, children: title }),
26096
- /* @__PURE__ */ jsx5(FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ jsx5(Pressable2, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ jsxs4(Text, { children: [
26619
+ return /* @__PURE__ */ jsx6(Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ jsx6(View3, { style: styles3.backdrop, children: /* @__PURE__ */ jsxs4(View3, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26620
+ /* @__PURE__ */ jsx6(Text, { style: styles3.title, children: title }),
26621
+ /* @__PURE__ */ jsx6(FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ jsx6(Pressable2, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ jsxs4(Text, { children: [
26097
26622
  index + 1,
26098
26623
  ". ",
26099
26624
  getAnnotationNote(item) || getAnnotationQuote(item)
26100
26625
  ] }) }) }),
26101
- /* @__PURE__ */ jsx5(Pressable2, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ jsx5(Text, { children: cancel }) })
26626
+ /* @__PURE__ */ jsx6(Pressable2, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ jsx6(Text, { children: cancel }) })
26102
26627
  ] }) }) });
26103
26628
  }
26104
26629
  var styles3 = StyleSheet3.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 } });
@@ -26125,7 +26650,7 @@ var resolvePageTapChromeVisibility = ({
26125
26650
  };
26126
26651
 
26127
26652
  // components/DedicatedAndroidPdfViewer.tsx
26128
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
26653
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
26129
26654
  var TEXT_MARKUP_TOOLS2 = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
26130
26655
  var MOBILE_CHROME_HIDE_DELTA = 28;
26131
26656
  var MOBILE_CHROME_SHOW_DELTA = 22;
@@ -26269,11 +26794,11 @@ function DedicatedAndroidPdfViewer({
26269
26794
  selectionRef.current = null;
26270
26795
  }, [addAnnotation, beginAnnotationDraft, annotationColor, documentId]);
26271
26796
  return /* @__PURE__ */ jsxs5(View4, { style: styles4.container, children: [
26272
- /* @__PURE__ */ jsx6(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26797
+ /* @__PURE__ */ jsx7(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26273
26798
  setPendingNotes([]);
26274
26799
  setSelectedAnnotation(id);
26275
26800
  } }),
26276
- /* @__PURE__ */ jsx6(
26801
+ /* @__PURE__ */ jsx7(
26277
26802
  PapyrusPdfDocumentView,
26278
26803
  {
26279
26804
  style: [styles4.viewer, cappedViewerStyle],
@@ -26484,7 +27009,7 @@ var resolveNativePdfVisiblePages = (pages) => (pages ?? []).flatMap((page) => {
26484
27009
  });
26485
27010
 
26486
27011
  // components/DedicatedIosPdfViewer.tsx
26487
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
27012
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
26488
27013
  var MOBILE_CHROME_HIDE_DELTA2 = 28;
26489
27014
  var MOBILE_CHROME_SHOW_DELTA2 = 22;
26490
27015
  var MOBILE_CHROME_SHOW_DELAY_MS2 = 180;
@@ -26723,11 +27248,11 @@ function DedicatedIosPdfViewer({
26723
27248
  alignSelf: "center"
26724
27249
  } : void 0;
26725
27250
  return /* @__PURE__ */ jsxs6(View5, { style: styles5.container, children: [
26726
- /* @__PURE__ */ jsx7(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
27251
+ /* @__PURE__ */ jsx8(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26727
27252
  setPendingNotes([]);
26728
27253
  setSelectedAnnotation(id);
26729
27254
  } }),
26730
- /* @__PURE__ */ jsx7(
27255
+ /* @__PURE__ */ jsx8(
26731
27256
  PapyrusPdfDocumentView2,
26732
27257
  {
26733
27258
  style: [styles5.viewer, cappedViewerStyle],
@@ -26796,22 +27321,22 @@ function DedicatedIosPdfViewer({
26796
27321
  onInkToolPickerVisibilityChange: handleInkToolPickerVisibilityChange
26797
27322
  }
26798
27323
  ),
26799
- selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ jsx7(View5, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ jsx7(
27324
+ selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ jsx8(View5, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ jsx8(
26800
27325
  Pressable3,
26801
27326
  {
26802
27327
  onPress: defineSelection,
26803
27328
  style: styles5.fallbackButton,
26804
27329
  accessibilityRole: "button",
26805
27330
  accessibilityLabel: t.define,
26806
- children: /* @__PURE__ */ jsx7(Text2, { style: styles5.fallbackButtonText, children: t.define })
27331
+ children: /* @__PURE__ */ jsx8(Text2, { style: styles5.fallbackButtonText, children: t.define })
26807
27332
  }
26808
27333
  ) }),
26809
- fallbackDeleteAnnotation && /* @__PURE__ */ jsx7(
27334
+ fallbackDeleteAnnotation && /* @__PURE__ */ jsx8(
26810
27335
  View5,
26811
27336
  {
26812
27337
  style: styles5.annotationDeleteFallback,
26813
27338
  pointerEvents: "box-none",
26814
- children: /* @__PURE__ */ jsx7(
27339
+ children: /* @__PURE__ */ jsx8(
26815
27340
  Pressable3,
26816
27341
  {
26817
27342
  onPress: () => handleDeleteAnnotation(fallbackDeleteAnnotation.id),
@@ -26819,7 +27344,7 @@ function DedicatedIosPdfViewer({
26819
27344
  accessibilityRole: "button",
26820
27345
  accessibilityLabel: t.deleteAnnotation,
26821
27346
  hitSlop: 8,
26822
- children: /* @__PURE__ */ jsx7(Text2, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
27347
+ children: /* @__PURE__ */ jsx8(Text2, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
26823
27348
  }
26824
27349
  )
26825
27350
  }
@@ -26882,7 +27407,7 @@ var styles5 = StyleSheet5.create({
26882
27407
  });
26883
27408
 
26884
27409
  // components/NativePdfDocumentViewer.tsx
26885
- import { jsx as jsx8 } from "react/jsx-runtime";
27410
+ import { jsx as jsx9 } from "react/jsx-runtime";
26886
27411
  function NativePdfDocumentViewer({
26887
27412
  engine,
26888
27413
  documentId,
@@ -26895,7 +27420,7 @@ function NativePdfDocumentViewer({
26895
27420
  Platform3.OS
26896
27421
  );
26897
27422
  if (implementation === "android") {
26898
- return /* @__PURE__ */ jsx8(
27423
+ return /* @__PURE__ */ jsx9(
26899
27424
  DedicatedAndroidPdfViewer,
26900
27425
  {
26901
27426
  engine,
@@ -26908,7 +27433,7 @@ function NativePdfDocumentViewer({
26908
27433
  );
26909
27434
  }
26910
27435
  if (implementation === "ios") {
26911
- return /* @__PURE__ */ jsx8(
27436
+ return /* @__PURE__ */ jsx9(
26912
27437
  DedicatedIosPdfViewer,
26913
27438
  {
26914
27439
  engine,
@@ -26964,29 +27489,6 @@ var resolveMobileChromeOffsets = (insets) => ({
26964
27489
  right: MOBILE_CHROME_METRICS.screenPadding + Math.max(0, insets.right)
26965
27490
  });
26966
27491
 
26967
- // components/PapyrusSafeArea.tsx
26968
- import React6, { useContext as useContext2 } from "react";
26969
- import {
26970
- SafeAreaInsetsContext,
26971
- SafeAreaProvider
26972
- } from "react-native-safe-area-context";
26973
- import { Fragment, jsx as jsx9 } from "react/jsx-runtime";
26974
- var papyrusSafeAreaContext = SafeAreaInsetsContext;
26975
- var ZERO_SAFE_AREA_INSETS = {
26976
- top: 0,
26977
- bottom: 0,
26978
- left: 0,
26979
- right: 0
26980
- };
26981
- var usePapyrusSafeAreaInsets = () => useContext2(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
26982
- function PapyrusSafeAreaBoundary({
26983
- children
26984
- }) {
26985
- const insets = useContext2(papyrusSafeAreaContext);
26986
- if (insets) return /* @__PURE__ */ jsx9(Fragment, { children });
26987
- return React6.createElement(SafeAreaProvider, null, children);
26988
- }
26989
-
26990
27492
  // components/NativeTextDocumentViewer.tsx
26991
27493
  import { jsx as jsx10 } from "react/jsx-runtime";
26992
27494
  function NativeTextDocumentViewer({
@@ -27537,6 +28039,7 @@ var scrollViewerListToOffset = (list, offset, animated) => {
27537
28039
  list.scrollToOffset?.({ offset, animated });
27538
28040
  };
27539
28041
  var Viewer = ({
28042
+ documentType,
27540
28043
  documentId,
27541
28044
  engine,
27542
28045
  maxPageWidth,
@@ -29064,14 +29567,8 @@ var Viewer = ({
29064
29567
  },
29065
29568
  [mobilePerf, perfEnabled, scrollPerfSession]
29066
29569
  );
29067
- const handleWebViewScroll = useCallback4(
29068
- (_offsetY) => {
29069
- if (!chromeVisibleRef.current) {
29070
- setMobileChromeVisible(true, "scroll.webview.recover");
29071
- }
29072
- },
29073
- [setMobileChromeVisible]
29074
- );
29570
+ const handleWebViewScroll = useCallback4((_offsetY) => {
29571
+ }, []);
29075
29572
  const handlePageTap = useCallback4(() => {
29076
29573
  const nextVisible = resolvePageTapChromeVisibility({
29077
29574
  chromeVisible: chromeVisibleRef.current,
@@ -29293,6 +29790,7 @@ var Viewer = ({
29293
29790
  return /* @__PURE__ */ jsx12(View8, { style: [styles8.container, isDark && styles8.containerDark], children: /* @__PURE__ */ jsx12(
29294
29791
  WebViewViewer_default,
29295
29792
  {
29793
+ documentType,
29296
29794
  engine,
29297
29795
  documentId,
29298
29796
  onDefineSelection,
@@ -29499,7 +29997,8 @@ import {
29499
29997
  Text as Text5,
29500
29998
  Pressable as Pressable6,
29501
29999
  StyleSheet as StyleSheet11,
29502
- useWindowDimensions as useWindowDimensions4
30000
+ useWindowDimensions as useWindowDimensions4,
30001
+ Platform as Platform6
29503
30002
  } from "react-native";
29504
30003
  import { useViewerStore as useViewerStore9 } from "@papyrus-sdk/core";
29505
30004
  import Animated3, { useAnimatedStyle } from "react-native-reanimated";
@@ -29862,6 +30361,7 @@ var styles9 = StyleSheet9.create({
29862
30361
  // components/PageJumpModal.tsx
29863
30362
  import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
29864
30363
  function PageJumpModal({
30364
+ documentType,
29865
30365
  visible,
29866
30366
  currentPage,
29867
30367
  pageCount,
@@ -29888,8 +30388,8 @@ function PageJumpModal({
29888
30388
  isDark,
29889
30389
  maxHeight: 300,
29890
30390
  showHeader: true,
29891
- title: "Ir para p\xE1gina",
29892
- closeAccessibilityLabel: "Close page jump",
30391
+ title: documentType === "epub" ? "Ir para cap\xEDtulo" : "Ir para p\xE1gina",
30392
+ closeAccessibilityLabel: "Fechar navega\xE7\xE3o",
29893
30393
  sheetStyle: {
29894
30394
  paddingHorizontal: 18,
29895
30395
  paddingBottom: 18,
@@ -29917,7 +30417,7 @@ function PageJumpModal({
29917
30417
  }
29918
30418
  ],
29919
30419
  onSubmitEditing: confirmJump,
29920
- accessibilityLabel: "Page jump input",
30420
+ accessibilityLabel: documentType === "epub" ? "N\xFAmero do cap\xEDtulo" : "N\xFAmero da p\xE1gina",
29921
30421
  testID: "papyrus-page-jump-input"
29922
30422
  }
29923
30423
  ),
@@ -29972,6 +30472,7 @@ var styles10 = StyleSheet10.create({
29972
30472
  import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
29973
30473
  var Topbar = ({
29974
30474
  engine,
30475
+ documentType,
29975
30476
  onOpenSettings,
29976
30477
  onOpenOverflow,
29977
30478
  title,
@@ -29992,6 +30493,12 @@ var Topbar = ({
29992
30493
  locale
29993
30494
  } = useViewerStore9();
29994
30495
  const inkSession = useNativeInkSession();
30496
+ const chromeVisible = mobileChromeVisible || inkSession.active;
30497
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
30498
+ const chromeMotion = useAnimatedStyle(() => {
30499
+ "worklet";
30500
+ return { opacity: chromeProgress.value };
30501
+ });
29995
30502
  const t = getStrings(locale);
29996
30503
  const progress = useNativeInkMotion(inkSession.active, inkSession.supported);
29997
30504
  const settingsMotion = useAnimatedStyle(() => {
@@ -30047,26 +30554,30 @@ var Topbar = ({
30047
30554
  children: /* @__PURE__ */ jsx15(IconSettings, { size: MOBILE_CHROME_METRICS.iconSize, color: isDark ? "#e5e7eb" : "#111827" })
30048
30555
  }
30049
30556
  );
30050
- if (!mobileChromeVisible && !inkSession.active) return null;
30557
+ if (!chromeVisible && documentType !== "epub") return null;
30051
30558
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
30052
30559
  /* @__PURE__ */ jsx15(
30053
- View11,
30560
+ Animated3.View,
30054
30561
  {
30055
30562
  style: [
30056
30563
  styles11.chromeFrame,
30564
+ chromeMotion,
30057
30565
  {
30058
30566
  paddingTop: insets.top,
30059
30567
  paddingLeft: MOBILE_CHROME_METRICS.screenPadding + insets.left,
30060
30568
  paddingRight: MOBILE_CHROME_METRICS.screenPadding + insets.right
30061
30569
  }
30062
30570
  ],
30063
- pointerEvents: "box-none",
30571
+ pointerEvents: chromeVisible ? "box-none" : "none",
30572
+ accessibilityElementsHidden: !chromeVisible,
30573
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
30064
30574
  testID: "papyrus-floating-top-controls",
30065
30575
  children: /* @__PURE__ */ jsxs10(
30066
30576
  View11,
30067
30577
  {
30068
30578
  style: [
30069
30579
  styles11.container,
30580
+ Platform6.OS === "ios" && styles11.containerIos,
30070
30581
  isDark && styles11.containerDark,
30071
30582
  isLandscape && styles11.containerLandscape
30072
30583
  ],
@@ -30206,6 +30717,7 @@ var Topbar = ({
30206
30717
  /* @__PURE__ */ jsx15(
30207
30718
  PageJumpModal,
30208
30719
  {
30720
+ documentType,
30209
30721
  visible: jumpModalOpen,
30210
30722
  currentPage,
30211
30723
  pageCount,
@@ -30223,7 +30735,8 @@ var styles11 = StyleSheet11.create({
30223
30735
  maxWidth: MOBILE_CHROME_METRICS.maxFloatingWidth,
30224
30736
  marginTop: 10,
30225
30737
  paddingHorizontal: 14,
30226
- paddingVertical: 10,
30738
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30739
+ paddingVertical: 0,
30227
30740
  backgroundColor: "rgba(255,255,255,0.88)",
30228
30741
  borderWidth: 1,
30229
30742
  borderColor: "rgba(255,255,255,0.72)",
@@ -30240,6 +30753,10 @@ var styles11 = StyleSheet11.create({
30240
30753
  backgroundColor: "rgba(15,17,21,0.88)",
30241
30754
  borderColor: "rgba(71,85,105,0.48)"
30242
30755
  },
30756
+ containerIos: {
30757
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30758
+ paddingVertical: 0
30759
+ },
30243
30760
  containerLandscape: {
30244
30761
  maxWidth: void 0,
30245
30762
  marginTop: 0
@@ -31389,7 +31906,7 @@ import {
31389
31906
  } from "react";
31390
31907
  import {
31391
31908
  Image as Image2,
31392
- Platform as Platform6,
31909
+ Platform as Platform7,
31393
31910
  Pressable as Pressable8,
31394
31911
  StyleSheet as StyleSheet13,
31395
31912
  Text as Text7,
@@ -31470,7 +31987,7 @@ var PageThumbnail = ({
31470
31987
  if (!layoutReady || !useNativePreview || !shouldRenderPreview) return;
31471
31988
  const viewTag = findNodeHandle2(viewRef.current);
31472
31989
  if (!viewTag) return;
31473
- const isNative = Platform6.OS === "android" || Platform6.OS === "ios";
31990
+ const isNative = Platform7.OS === "android" || Platform7.OS === "ios";
31474
31991
  const renderScale = isNative ? 2 / Math.max(zoom, 0.5) : 2;
31475
31992
  void engine.renderPage(pageIndex, viewTag, renderScale);
31476
31993
  }, [
@@ -32395,14 +32912,14 @@ var RightSheet_default = RightSheet;
32395
32912
 
32396
32913
  // components/AnnotationEditor.tsx
32397
32914
  import { useEffect as useEffect13, useRef as useRef9, useState as useState12 } from "react";
32398
- import { AccessibilityInfo as AccessibilityInfo2, Alert as Alert3, Animated as Animated4, KeyboardAvoidingView, Modal as Modal3, Platform as Platform7, Pressable as Pressable9, ScrollView as ScrollView4, StyleSheet as StyleSheet14, Text as Text8, TextInput as TextInput2, View as View14 } from "react-native";
32915
+ import { AccessibilityInfo as AccessibilityInfo2, Alert as Alert3, Animated as Animated4, KeyboardAvoidingView, Modal as Modal3, Platform as Platform8, Pressable as Pressable9, ScrollView as ScrollView4, StyleSheet as StyleSheet14, Text as Text8, TextInput as TextInput2, View as View14 } from "react-native";
32399
32916
  import { getAnnotationEditPatch, getAnnotationMarkup as getAnnotationMarkup2, getAnnotationNote as getAnnotationNote2, getAnnotationQuote as getAnnotationQuote2, useViewerStore as useViewerStore12 } from "@papyrus-sdk/core";
32400
32917
  import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
32401
32918
  var COLORS = ["#fbbf24", "#fb7185", "#60a5fa", "#34d399", "#c084fc"];
32402
- var AnnotationEditor = () => {
32919
+ var AnnotationEditor = ({ documentId }) => {
32403
32920
  const { annotations, annotationDraft, clearAnnotationDraft, addAnnotation, selectedAnnotationId, updateAnnotation, removeAnnotation, setSelectedAnnotation, uiTheme, locale, accentColor } = useViewerStore12();
32404
32921
  const annotation = annotationDraft?.id === selectedAnnotationId ? annotationDraft : annotations.find((a) => a.id === selectedAnnotationId);
32405
- const supported = annotation && annotation.type !== "ink";
32922
+ const supported = annotation && annotation.type !== "ink" && (!documentId || !annotation.anchor?.documentId || annotation.anchor.documentId === documentId);
32406
32923
  const [editing, setEditing] = useState12(false);
32407
32924
  const [draft, setDraft] = useState12("");
32408
32925
  const [color, setColor] = useState12(COLORS[0]);
@@ -32462,19 +32979,39 @@ var AnnotationEditor = () => {
32462
32979
  const remove = () => Alert3.alert(t.deleteAnnotation, t.deleteAnnotationConfirmation, [{ text: t.cancel, style: "cancel" }, { text: t.deleteAnnotation, style: "destructive", onPress: () => deleteAnnotationAndClearSelection(annotation.id, removeAnnotation, setSelectedAnnotation) }]);
32463
32980
  const textStyle = { color: dark ? "#f9fafb" : "#111827" };
32464
32981
  const quote = getAnnotationQuote2(annotation);
32465
- return /* @__PURE__ */ jsx18(Modal3, { visible: true, transparent: true, animationType: "none", onRequestClose: close, children: /* @__PURE__ */ jsxs13(KeyboardAvoidingView, { behavior: Platform7.OS === "ios" ? "padding" : void 0, style: styles14.host, children: [
32982
+ return /* @__PURE__ */ jsx18(Modal3, { visible: true, transparent: true, animationType: "none", onRequestClose: close, children: /* @__PURE__ */ jsxs13(KeyboardAvoidingView, { behavior: Platform8.OS === "ios" ? "padding" : void 0, style: styles14.host, children: [
32466
32983
  /* @__PURE__ */ jsx18(Pressable9, { style: StyleSheet14.absoluteFill, onPress: close, accessibilityLabel: t.close }),
32467
32984
  /* @__PURE__ */ jsxs13(Animated4.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: [
32468
32985
  /* @__PURE__ */ jsxs13(View14, { style: styles14.header, children: [
32469
32986
  /* @__PURE__ */ jsx18(Text8, { style: [styles14.title, textStyle], children: editing ? t.editNote : t.annotationNote }),
32470
- /* @__PURE__ */ jsx18(Pressable9, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: styles14.button, children: /* @__PURE__ */ jsx18(Text8, { style: textStyle, children: "\xD7" }) })
32987
+ /* @__PURE__ */ jsx18(Pressable9, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: [styles14.button, { borderRadius: 22, backgroundColor: dark ? "#242936" : "#f1f5f9" }], children: /* @__PURE__ */ jsx18(IconClose, { size: 20, color: textStyle.color }) })
32471
32988
  ] }),
32472
32989
  /* @__PURE__ */ jsxs13(ScrollView4, { style: styles14.content, keyboardShouldPersistTaps: "handled", children: [
32473
32990
  quote ? /* @__PURE__ */ jsx18(Text8, { style: [styles14.quote, textStyle], selectable: true, children: quote }) : null,
32474
32991
  editing ? /* @__PURE__ */ jsxs13(Fragment3, { children: [
32475
32992
  /* @__PURE__ */ jsx18(TextInput2, { 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" }] }),
32476
32993
  /* @__PURE__ */ jsx18(View14, { style: styles14.options, children: COLORS.map((value) => /* @__PURE__ */ jsx18(Pressable9, { accessibilityRole: "button", accessibilityLabel: `${t.annotationColor} ${value}`, accessibilityState: { selected: color === value }, onPress: () => setColor(value), style: [styles14.color, { backgroundColor: value, borderColor: color === value ? accentColor : "transparent" }] }, value)) }),
32477
- /* @__PURE__ */ jsx18(View14, { style: styles14.options, children: [["highlight", t.annotationHighlight], ["underline", t.annotationUnderline], ["strikeout", t.annotationStrikeout], ["none", t.annotationIndicatorOnly]].map(([style, label]) => /* @__PURE__ */ jsx18(Pressable9, { onPress: () => setMarkup(style), accessibilityRole: "button", accessibilityState: { selected: markup === style }, style: [styles14.button, markup === style && { borderColor: accentColor, borderWidth: 1 }], children: /* @__PURE__ */ jsx18(Text8, { style: textStyle, children: label }) }, style)) })
32994
+ /* @__PURE__ */ jsx18(View14, { style: styles14.markupOptions, children: [
32995
+ ["highlight", t.annotationHighlight, IconHighlight],
32996
+ ["underline", t.annotationUnderline, IconUnderline],
32997
+ ["strikeout", t.annotationStrikeout, IconStrikeout],
32998
+ ["none", t.annotationIndicatorOnly, IconComment]
32999
+ ].map(([style, label, Icon]) => /* @__PURE__ */ jsxs13(
33000
+ Pressable9,
33001
+ {
33002
+ onPress: () => setMarkup(style),
33003
+ accessibilityRole: "button",
33004
+ accessibilityLabel: label,
33005
+ accessibilityState: { selected: markup === style },
33006
+ testID: `annotation-style-${style}`,
33007
+ style: [styles14.markupButton, { backgroundColor: dark ? "#242936" : "#f1f5f9", borderColor: markup === style ? accentColor : "transparent" }],
33008
+ children: [
33009
+ /* @__PURE__ */ jsx18(View14, { accessible: false, importantForAccessibility: "no-hide-descendants", children: /* @__PURE__ */ jsx18(Icon, { size: 22, color: markup === style ? accentColor : textStyle.color }) }),
33010
+ /* @__PURE__ */ jsx18(Text8, { style: [styles14.markupLabel, textStyle], children: style === "none" ? locale === "pt-BR" ? "Indicador" : "Indicator" : label })
33011
+ ]
33012
+ },
33013
+ style
33014
+ )) })
32478
33015
  ] }) : /* @__PURE__ */ jsx18(Text8, { style: [styles14.note, textStyle], selectable: true, children: getAnnotationNote2(annotation) || t.notePlaceholder })
32479
33016
  ] }),
32480
33017
  /* @__PURE__ */ jsxs13(View14, { style: styles14.actions, children: [
@@ -32497,11 +33034,11 @@ var AnnotationEditor = () => {
32497
33034
  ] })
32498
33035
  ] }) });
32499
33036
  };
32500
- var styles14 = StyleSheet14.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 } });
33037
+ var styles14 = StyleSheet14.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 } });
32501
33038
  var AnnotationEditor_default = AnnotationEditor;
32502
33039
 
32503
33040
  // components/BottomBar.tsx
32504
- import { View as View15, Pressable as Pressable10, StyleSheet as StyleSheet15, useWindowDimensions as useWindowDimensions7 } from "react-native";
33041
+ import { View as View15, Text as Text9, Pressable as Pressable10, StyleSheet as StyleSheet15, useWindowDimensions as useWindowDimensions7 } from "react-native";
32505
33042
  import { useViewerStore as useViewerStore13 } from "@papyrus-sdk/core";
32506
33043
  import Animated5, { useAnimatedStyle as useAnimatedStyle2 } from "react-native-reanimated";
32507
33044
 
@@ -32512,7 +33049,7 @@ function buildBottomBarLayout({
32512
33049
  toolDockOpen
32513
33050
  }) {
32514
33051
  const leftSlots = [];
32515
- if (documentType === "pdf") {
33052
+ if (documentType === "pdf" || documentType === "epub") {
32516
33053
  leftSlots.push({
32517
33054
  key: "annotate",
32518
33055
  active: toolDockOpen || activeMobileDestination === "annotate"
@@ -32555,15 +33092,24 @@ function resolveAnnotateButtonAction(input) {
32555
33092
  }
32556
33093
 
32557
33094
  // components/BottomBar.tsx
32558
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
33095
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
32559
33096
  var BottomBar = ({
32560
33097
  documentType,
32561
33098
  onOpenInfo,
32562
33099
  onOpenSettings,
32563
- onOpenDestination
33100
+ onOpenDestination,
33101
+ onTurnPage,
33102
+ onNavigateToPage,
33103
+ onOpenPages
32564
33104
  }) => {
32565
33105
  const {
32566
33106
  activeMobileDestination,
33107
+ viewMode,
33108
+ epubReadingMode,
33109
+ epubLocation,
33110
+ comicLayoutMode,
33111
+ currentPage,
33112
+ pageCount,
32567
33113
  mobileDockVisible,
32568
33114
  setDocumentState,
32569
33115
  uiTheme,
@@ -32578,7 +33124,7 @@ var BottomBar = ({
32578
33124
  const supportsInkChrome = documentType === "pdf" && inkSession.supported;
32579
33125
  const drawing = supportsInkChrome && inkSession.active;
32580
33126
  const dockVisible = mobileChromeVisible && mobileDockVisible && !drawing;
32581
- const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome);
33127
+ const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome || documentType === "epub");
32582
33128
  const dockMotion = useAnimatedStyle2(() => {
32583
33129
  "worklet";
32584
33130
  return {
@@ -32600,6 +33146,15 @@ var BottomBar = ({
32600
33146
  activeMobileDestination,
32601
33147
  toolDockOpen
32602
33148
  });
33149
+ const paginated = documentType !== "text" && (documentType === "comic" ? comicLayoutMode === "single" : documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single");
33150
+ if (paginated) {
33151
+ layout.leftSlots.push(...layout.rightSlots.filter((slot) => slot.key === "search"));
33152
+ layout.rightSlots = [];
33153
+ }
33154
+ const turnPage = (direction) => {
33155
+ if (onTurnPage) onTurnPage(direction);
33156
+ else onNavigateToPage?.(currentPage + direction);
33157
+ };
32603
33158
  const slotMeta = {
32604
33159
  annotate: {
32605
33160
  label: t.tools,
@@ -32660,7 +33215,7 @@ var BottomBar = ({
32660
33215
  onPress: onOpenSettings
32661
33216
  }
32662
33217
  };
32663
- if (!dockVisible && !supportsInkChrome) return null;
33218
+ if (!dockVisible && !supportsInkChrome && documentType !== "epub") return null;
32664
33219
  return /* @__PURE__ */ jsx19(
32665
33220
  Animated5.View,
32666
33221
  {
@@ -32725,7 +33280,11 @@ var BottomBar = ({
32725
33280
  isDark && styles15.islandDark
32726
33281
  ],
32727
33282
  testID: "papyrus-floating-bottom-dock",
32728
- children: layout.rightSlots.map((slot) => {
33283
+ children: paginated ? /* @__PURE__ */ jsxs14(Fragment4, { children: [
33284
+ /* @__PURE__ */ jsx19(Pressable10, { 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__ */ jsx19(IconChevronLeft, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) }),
33285
+ /* @__PURE__ */ jsx19(Pressable10, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "Ir para p\xE1gina" : "Go to page", onPress: onOpenPages, style: [styles15.iconOnlyItem, { minWidth: 56 }], children: /* @__PURE__ */ jsx19(Text9, { 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}` }) }),
33286
+ /* @__PURE__ */ jsx19(Pressable10, { 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__ */ jsx19(IconChevronRight, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) })
33287
+ ] }) : layout.rightSlots.map((slot) => {
32729
33288
  const meta = slotMeta[slot.key];
32730
33289
  const Icon = meta.icon;
32731
33290
  return /* @__PURE__ */ jsx19(
@@ -32788,8 +33347,9 @@ var styles15 = StyleSheet15.create({
32788
33347
  island: {
32789
33348
  flexDirection: "row",
32790
33349
  alignItems: "center",
32791
- paddingVertical: 8,
32792
- paddingHorizontal: 10,
33350
+ height: MOBILE_CHROME_METRICS.bottomBarVisualHeight,
33351
+ paddingVertical: 0,
33352
+ paddingHorizontal: 6,
32793
33353
  backgroundColor: "rgba(255,255,255,0.9)",
32794
33354
  borderWidth: 1,
32795
33355
  borderColor: "rgba(255,255,255,0.72)",
@@ -32815,6 +33375,8 @@ var styles15 = StyleSheet15.create({
32815
33375
  borderColor: "rgba(71,85,105,0.48)"
32816
33376
  },
32817
33377
  iconOnlyItem: {
33378
+ minWidth: 44,
33379
+ height: 44,
32818
33380
  alignItems: "center",
32819
33381
  justifyContent: "center",
32820
33382
  paddingHorizontal: MOBILE_CHROME_METRICS.bottomBarItemPaddingHorizontal,
@@ -32845,9 +33407,9 @@ var BottomBar_default = BottomBar;
32845
33407
  // components/SettingsSheet.tsx
32846
33408
  import {
32847
33409
  View as View16,
32848
- Text as Text9,
33410
+ Text as Text10,
32849
33411
  Pressable as Pressable11,
32850
- Platform as Platform8,
33412
+ Platform as Platform9,
32851
33413
  StyleSheet as StyleSheet16,
32852
33414
  useWindowDimensions as useWindowDimensions8
32853
33415
  } from "react-native";
@@ -32898,10 +33460,12 @@ var SettingsSheet = ({
32898
33460
  engine,
32899
33461
  visible,
32900
33462
  onClose,
32901
- documentType = "pdf"
33463
+ documentType = "pdf",
33464
+ onOpenInfo
32902
33465
  }) => {
32903
33466
  const {
32904
33467
  viewMode,
33468
+ epubReadingMode,
32905
33469
  uiTheme,
32906
33470
  zoom,
32907
33471
  setDocumentState,
@@ -32913,12 +33477,16 @@ var SettingsSheet = ({
32913
33477
  comicReadingDirection
32914
33478
  } = useViewerStore14();
32915
33479
  const isDark = uiTheme === "dark";
32916
- const isPaged = viewMode === "single";
33480
+ const isPaged = documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single";
32917
33481
  const isDouble = viewMode === "double";
32918
33482
  const t = getStrings(locale);
32919
33483
  const { height: windowHeight } = useWindowDimensions8();
32920
33484
  const sheetMaxHeight = getSettingsSheetMaxHeight(windowHeight);
32921
33485
  const handleTransition = (mode) => {
33486
+ if (documentType === "epub") {
33487
+ setDocumentState({ epubReadingMode: mode === "paged" ? "paged" : "chapter-scroll" });
33488
+ return;
33489
+ }
32922
33490
  if (mode === "paged") {
32923
33491
  setDocumentState({ viewMode: "single" });
32924
33492
  return;
@@ -32963,22 +33531,23 @@ var SettingsSheet = ({
32963
33531
  contentContainerStyle: styles16.sheetContent,
32964
33532
  showsVerticalScrollIndicator: false,
32965
33533
  children: [
33534
+ onOpenInfo ? /* @__PURE__ */ jsx20(Pressable11, { accessibilityRole: "button", onPress: onOpenInfo, style: { minHeight: 44, justifyContent: "center", marginBottom: 16 }, children: /* @__PURE__ */ jsx20(Text10, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.info }) }) : null,
32966
33535
  (documentType === "text" || documentType === "epub") && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
32967
- /* @__PURE__ */ jsx20(Text9, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: locale === "pt-BR" ? "Tamanho da fonte" : "Font size" }),
33536
+ /* @__PURE__ */ jsx20(Text10, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: locale === "pt-BR" ? "Tamanho da fonte" : "Font size" }),
32968
33537
  /* @__PURE__ */ jsxs15(View16, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
32969
33538
  [-0.1, 0.1].map((delta) => /* @__PURE__ */ jsx20(Pressable11, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? delta < 0 ? "Diminuir fonte" : "Aumentar fonte" : delta < 0 ? "Decrease font" : "Increase font", onPress: () => {
32970
33539
  const next = Math.max(0.7, Math.min(2, zoom + delta));
32971
33540
  if (documentType === "epub") engine.setZoom(next);
32972
33541
  setDocumentState({ zoom: next });
32973
- }, style: { minWidth: 44, minHeight: 44, justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ jsx20(Text9, { style: { fontSize: 20, color: isDark ? "#fff" : "#111" }, children: delta < 0 ? "A\u2212" : "A+" }) }, delta)),
32974
- /* @__PURE__ */ jsxs15(Text9, { style: { color: isDark ? "#fff" : "#111" }, children: [
33542
+ }, style: { minWidth: 44, minHeight: 44, justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ jsx20(Text10, { style: { fontSize: 20, color: isDark ? "#fff" : "#111" }, children: delta < 0 ? "A\u2212" : "A+" }) }, delta)),
33543
+ /* @__PURE__ */ jsxs15(Text10, { style: { color: isDark ? "#fff" : "#111" }, children: [
32975
33544
  Math.round(zoom * 100),
32976
33545
  "%"
32977
33546
  ] })
32978
33547
  ] })
32979
33548
  ] }),
32980
33549
  documentType === "comic" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
32981
- /* @__PURE__ */ jsx20(Text9, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicMode }),
33550
+ /* @__PURE__ */ jsx20(Text10, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicMode }),
32982
33551
  /* @__PURE__ */ jsx20(View16, { style: styles16.optionRow, children: ["single", "continuous"].map((mode) => {
32983
33552
  const active = comicLayoutMode === mode;
32984
33553
  return /* @__PURE__ */ jsx20(
@@ -32986,12 +33555,12 @@ var SettingsSheet = ({
32986
33555
  {
32987
33556
  onPress: () => setDocumentState({ comicLayoutMode: mode }),
32988
33557
  style: [styles16.optionButton, isDark && styles16.optionButtonDark, active && styles16.optionButtonActive, active && { backgroundColor: accentColor }],
32989
- children: /* @__PURE__ */ jsx20(Text9, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: mode === "single" ? t.comicSingle : t.comicContinuous })
33558
+ children: /* @__PURE__ */ jsx20(Text10, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: mode === "single" ? t.comicSingle : t.comicContinuous })
32990
33559
  },
32991
33560
  mode
32992
33561
  );
32993
33562
  }) }),
32994
- /* @__PURE__ */ jsx20(Text9, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicFit }),
33563
+ /* @__PURE__ */ jsx20(Text10, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicFit }),
32995
33564
  /* @__PURE__ */ jsx20(View16, { style: styles16.optionRow, children: ["width", "page"].map((mode) => {
32996
33565
  const active = comicFitMode === mode;
32997
33566
  return /* @__PURE__ */ jsx20(
@@ -32999,12 +33568,12 @@ var SettingsSheet = ({
32999
33568
  {
33000
33569
  onPress: () => setDocumentState({ comicFitMode: mode }),
33001
33570
  style: [styles16.optionButton, isDark && styles16.optionButtonDark, active && styles16.optionButtonActive, active && { backgroundColor: accentColor }],
33002
- children: /* @__PURE__ */ jsx20(Text9, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: mode === "width" ? t.comicFitWidth : t.comicFitPage })
33571
+ children: /* @__PURE__ */ jsx20(Text10, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: mode === "width" ? t.comicFitWidth : t.comicFitPage })
33003
33572
  },
33004
33573
  mode
33005
33574
  );
33006
33575
  }) }),
33007
- /* @__PURE__ */ jsx20(Text9, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicDirection }),
33576
+ /* @__PURE__ */ jsx20(Text10, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.comicDirection }),
33008
33577
  /* @__PURE__ */ jsx20(View16, { style: styles16.optionRow, children: ["ltr", "rtl"].map((direction) => {
33009
33578
  const active = comicReadingDirection === direction;
33010
33579
  return /* @__PURE__ */ jsx20(
@@ -33012,7 +33581,7 @@ var SettingsSheet = ({
33012
33581
  {
33013
33582
  onPress: () => setDocumentState({ comicReadingDirection: direction }),
33014
33583
  style: [styles16.optionButton, isDark && styles16.optionButtonDark, active && styles16.optionButtonActive, active && { backgroundColor: accentColor }],
33015
- children: /* @__PURE__ */ jsx20(Text9, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: direction === "ltr" ? t.comicLtr : t.comicRtl })
33584
+ children: /* @__PURE__ */ jsx20(Text10, { style: [styles16.optionText, isDark && styles16.optionTextDark, active && styles16.optionTextActive], children: direction === "ltr" ? t.comicLtr : t.comicRtl })
33016
33585
  },
33017
33586
  direction
33018
33587
  );
@@ -33020,7 +33589,7 @@ var SettingsSheet = ({
33020
33589
  ] }),
33021
33590
  /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33022
33591
  /* @__PURE__ */ jsx20(
33023
- Text9,
33592
+ Text10,
33024
33593
  {
33025
33594
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33026
33595
  children: t.appearance
@@ -33038,7 +33607,7 @@ var SettingsSheet = ({
33038
33607
  uiTheme === "light" && { backgroundColor: accentColor }
33039
33608
  ],
33040
33609
  children: /* @__PURE__ */ jsx20(
33041
- Text9,
33610
+ Text10,
33042
33611
  {
33043
33612
  style: [
33044
33613
  styles16.optionText,
@@ -33061,7 +33630,7 @@ var SettingsSheet = ({
33061
33630
  uiTheme === "dark" && { backgroundColor: accentColor }
33062
33631
  ],
33063
33632
  children: /* @__PURE__ */ jsx20(
33064
- Text9,
33633
+ Text10,
33065
33634
  {
33066
33635
  style: [
33067
33636
  styles16.optionText,
@@ -33077,7 +33646,7 @@ var SettingsSheet = ({
33077
33646
  ] }),
33078
33647
  /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33079
33648
  /* @__PURE__ */ jsx20(
33080
- Text9,
33649
+ Text10,
33081
33650
  {
33082
33651
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33083
33652
  children: t.pageTheme
@@ -33098,7 +33667,7 @@ var SettingsSheet = ({
33098
33667
  children: [
33099
33668
  /* @__PURE__ */ jsx20(ThemeSwatch, { value: option.value }),
33100
33669
  /* @__PURE__ */ jsx20(
33101
- Text9,
33670
+ Text10,
33102
33671
  {
33103
33672
  style: [
33104
33673
  styles16.themeOptionLabel,
@@ -33116,7 +33685,7 @@ var SettingsSheet = ({
33116
33685
  ] }),
33117
33686
  documentType !== "comic" && documentType !== "text" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33118
33687
  /* @__PURE__ */ jsx20(
33119
- Text9,
33688
+ Text10,
33120
33689
  {
33121
33690
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33122
33691
  children: t.pageTransition
@@ -33134,14 +33703,14 @@ var SettingsSheet = ({
33134
33703
  (!isPaged || isDouble) && { backgroundColor: accentColor }
33135
33704
  ],
33136
33705
  children: /* @__PURE__ */ jsx20(
33137
- Text9,
33706
+ Text10,
33138
33707
  {
33139
33708
  style: [
33140
33709
  styles16.optionText,
33141
33710
  isDark && styles16.optionTextDark,
33142
33711
  (!isPaged || isDouble) && styles16.optionTextActive
33143
33712
  ],
33144
- children: t.continuous
33713
+ children: documentType === "epub" && locale === "pt-BR" ? "Rolagem por cap\xEDtulo" : t.continuous
33145
33714
  }
33146
33715
  )
33147
33716
  }
@@ -33157,7 +33726,7 @@ var SettingsSheet = ({
33157
33726
  isPaged && { backgroundColor: accentColor }
33158
33727
  ],
33159
33728
  children: /* @__PURE__ */ jsx20(
33160
- Text9,
33729
+ Text10,
33161
33730
  {
33162
33731
  style: [
33163
33732
  styles16.optionText,
@@ -33171,9 +33740,9 @@ var SettingsSheet = ({
33171
33740
  )
33172
33741
  ] })
33173
33742
  ] }),
33174
- documentType !== "comic" && documentType !== "text" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33743
+ documentType === "pdf" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33175
33744
  /* @__PURE__ */ jsx20(
33176
- Text9,
33745
+ Text10,
33177
33746
  {
33178
33747
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33179
33748
  children: t.layout
@@ -33191,7 +33760,7 @@ var SettingsSheet = ({
33191
33760
  !isDouble && { backgroundColor: accentColor }
33192
33761
  ],
33193
33762
  children: /* @__PURE__ */ jsx20(
33194
- Text9,
33763
+ Text10,
33195
33764
  {
33196
33765
  style: [
33197
33766
  styles16.optionText,
@@ -33214,7 +33783,7 @@ var SettingsSheet = ({
33214
33783
  isDouble && { backgroundColor: accentColor }
33215
33784
  ],
33216
33785
  children: /* @__PURE__ */ jsx20(
33217
- Text9,
33786
+ Text10,
33218
33787
  {
33219
33788
  style: [
33220
33789
  styles16.optionText,
@@ -33230,7 +33799,7 @@ var SettingsSheet = ({
33230
33799
  ] }),
33231
33800
  documentType !== "comic" && documentType !== "text" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33232
33801
  /* @__PURE__ */ jsx20(
33233
- Text9,
33802
+ Text10,
33234
33803
  {
33235
33804
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33236
33805
  children: t.rotate
@@ -33243,7 +33812,7 @@ var SettingsSheet = ({
33243
33812
  onPress: () => handleRotate("counterclockwise"),
33244
33813
  style: [styles16.optionButton, isDark && styles16.optionButtonDark],
33245
33814
  children: /* @__PURE__ */ jsx20(
33246
- Text9,
33815
+ Text10,
33247
33816
  {
33248
33817
  style: [styles16.optionText, isDark && styles16.optionTextDark],
33249
33818
  children: t.counterclockwise
@@ -33251,13 +33820,13 @@ var SettingsSheet = ({
33251
33820
  )
33252
33821
  }
33253
33822
  ),
33254
- Platform8.OS === "ios" && /* @__PURE__ */ jsx20(
33823
+ Platform9.OS === "ios" && /* @__PURE__ */ jsx20(
33255
33824
  Pressable11,
33256
33825
  {
33257
33826
  onPress: handleResetRotation,
33258
33827
  style: [styles16.optionButton, isDark && styles16.optionButtonDark],
33259
33828
  children: /* @__PURE__ */ jsx20(
33260
- Text9,
33829
+ Text10,
33261
33830
  {
33262
33831
  style: [styles16.optionText, isDark && styles16.optionTextDark],
33263
33832
  children: t.rotationOriginal
@@ -33271,7 +33840,7 @@ var SettingsSheet = ({
33271
33840
  onPress: () => handleRotate("clockwise"),
33272
33841
  style: [styles16.optionButton, isDark && styles16.optionButtonDark],
33273
33842
  children: /* @__PURE__ */ jsx20(
33274
- Text9,
33843
+ Text10,
33275
33844
  {
33276
33845
  style: [styles16.optionText, isDark && styles16.optionTextDark],
33277
33846
  children: t.clockwise
@@ -33283,7 +33852,7 @@ var SettingsSheet = ({
33283
33852
  ] }),
33284
33853
  documentType !== "text" && /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33285
33854
  /* @__PURE__ */ jsx20(
33286
- Text9,
33855
+ Text10,
33287
33856
  {
33288
33857
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33289
33858
  children: t.zoom
@@ -33298,7 +33867,7 @@ var SettingsSheet = ({
33298
33867
  children: /* @__PURE__ */ jsx20(IconZoomOut, { size: 16, color: isDark ? "#e5e7eb" : "#111827" })
33299
33868
  }
33300
33869
  ),
33301
- /* @__PURE__ */ jsx20(View16, { style: styles16.zoomValue, children: /* @__PURE__ */ jsxs15(Text9, { style: [styles16.zoomText, isDark && styles16.zoomTextDark], children: [
33870
+ /* @__PURE__ */ jsx20(View16, { style: styles16.zoomValue, children: /* @__PURE__ */ jsxs15(Text10, { style: [styles16.zoomText, isDark && styles16.zoomTextDark], children: [
33302
33871
  Math.round(zoom * 100),
33303
33872
  "%"
33304
33873
  ] }) }),
@@ -33314,7 +33883,7 @@ var SettingsSheet = ({
33314
33883
  ] }),
33315
33884
  /* @__PURE__ */ jsxs15(View16, { style: styles16.section, children: [
33316
33885
  /* @__PURE__ */ jsx20(
33317
- Text9,
33886
+ Text10,
33318
33887
  {
33319
33888
  style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark],
33320
33889
  children: t.language
@@ -33332,7 +33901,7 @@ var SettingsSheet = ({
33332
33901
  locale === "en" && { backgroundColor: accentColor }
33333
33902
  ],
33334
33903
  children: /* @__PURE__ */ jsx20(
33335
- Text9,
33904
+ Text10,
33336
33905
  {
33337
33906
  style: [
33338
33907
  styles16.optionText,
@@ -33355,7 +33924,7 @@ var SettingsSheet = ({
33355
33924
  locale === "pt-BR" && { backgroundColor: accentColor }
33356
33925
  ],
33357
33926
  children: /* @__PURE__ */ jsx20(
33358
- Text9,
33927
+ Text10,
33359
33928
  {
33360
33929
  style: [
33361
33930
  styles16.optionText,
@@ -33512,7 +34081,7 @@ import { useEffect as useEffect14, useMemo as useMemo8, useRef as useRef10, useS
33512
34081
  import {
33513
34082
  ActivityIndicator,
33514
34083
  StyleSheet as StyleSheet17,
33515
- Text as Text10,
34084
+ Text as Text11,
33516
34085
  UIManager as UIManager2,
33517
34086
  View as View17,
33518
34087
  findNodeHandle as findNodeHandle3
@@ -33635,12 +34204,12 @@ var CoverPreview = ({
33635
34204
  }
33636
34205
  };
33637
34206
  if (!shouldRender || !canRender) {
33638
- return /* @__PURE__ */ jsx21(View17, { style: [styles17.container, style], children: placeholder ?? /* @__PURE__ */ jsx21(View17, { style: styles17.placeholder, children: /* @__PURE__ */ jsx21(Text10, { style: styles17.placeholderText, children: "Preview" }) }) });
34207
+ return /* @__PURE__ */ jsx21(View17, { style: [styles17.container, style], children: placeholder ?? /* @__PURE__ */ jsx21(View17, { style: styles17.placeholder, children: /* @__PURE__ */ jsx21(Text11, { style: styles17.placeholderText, children: "Preview" }) }) });
33639
34208
  }
33640
34209
  return /* @__PURE__ */ jsxs16(View17, { style: [styles17.container, style], onLayout: handleLayout, children: [
33641
34210
  /* @__PURE__ */ jsx21(View17, { style: styles17.previewFrame, pointerEvents: allowInteraction ? "auto" : "none", children: isPdf ? /* @__PURE__ */ jsx21(PapyrusPageView3, { ref: viewRef, style: styles17.pageView }) : engine && /* @__PURE__ */ jsx21(WebViewViewer_default, { engine }) }),
33642
34211
  showLoading && loading && /* @__PURE__ */ jsx21(View17, { pointerEvents: "none", style: styles17.loadingOverlay, children: loadingIndicator ?? /* @__PURE__ */ jsx21(ActivityIndicator, { size: "small", color: "#2563eb" }) }),
33643
- error && /* @__PURE__ */ jsx21(View17, { pointerEvents: "none", style: styles17.errorOverlay, children: /* @__PURE__ */ jsx21(Text10, { style: styles17.errorText, children: "Preview unavailable" }) })
34212
+ error && /* @__PURE__ */ jsx21(View17, { pointerEvents: "none", style: styles17.errorOverlay, children: /* @__PURE__ */ jsx21(Text11, { style: styles17.errorText, children: "Preview unavailable" }) })
33644
34213
  ] });
33645
34214
  };
33646
34215
  var styles17 = StyleSheet17.create({
@@ -33695,7 +34264,7 @@ import { Keyboard as Keyboard2, View as View23, StyleSheet as StyleSheet24 } fro
33695
34264
  import { useViewerStore as useViewerStore21 } from "@papyrus-sdk/core";
33696
34265
 
33697
34266
  // components/DocumentActionsSheet.tsx
33698
- import { StyleSheet as StyleSheet18, Text as Text11, View as View18 } from "react-native";
34267
+ import { StyleSheet as StyleSheet18, Text as Text12, View as View18 } from "react-native";
33699
34268
  import { useViewerStore as useViewerStore15 } from "@papyrus-sdk/core";
33700
34269
  import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
33701
34270
  function DocumentActionsSheet({
@@ -33732,7 +34301,7 @@ function DocumentActionsSheet({
33732
34301
  borderColor: palette.divider
33733
34302
  }
33734
34303
  ],
33735
- children: /* @__PURE__ */ jsx22(Text11, { style: [styles18.actionText, { color: palette.text }], children: "Share" })
34304
+ children: /* @__PURE__ */ jsx22(Text12, { style: [styles18.actionText, { color: palette.text }], children: "Share" })
33736
34305
  }
33737
34306
  ),
33738
34307
  /* @__PURE__ */ jsx22(
@@ -33745,7 +34314,7 @@ function DocumentActionsSheet({
33745
34314
  borderColor: palette.divider
33746
34315
  }
33747
34316
  ],
33748
- children: /* @__PURE__ */ jsx22(Text11, { style: [styles18.actionText, { color: palette.text }], children: "Export" })
34317
+ children: /* @__PURE__ */ jsx22(Text12, { style: [styles18.actionText, { color: palette.text }], children: "Export" })
33749
34318
  }
33750
34319
  )
33751
34320
  ] })
@@ -33770,7 +34339,7 @@ var styles18 = StyleSheet18.create({
33770
34339
  });
33771
34340
 
33772
34341
  // components/InfoSheet.tsx
33773
- import { StyleSheet as StyleSheet19, Text as Text12, View as View19 } from "react-native";
34342
+ import { StyleSheet as StyleSheet19, Text as Text13, View as View19 } from "react-native";
33774
34343
  import { useViewerStore as useViewerStore16 } from "@papyrus-sdk/core";
33775
34344
 
33776
34345
  // components/infoSheetModel.ts
@@ -33924,15 +34493,15 @@ function InfoSheet({
33924
34493
  contentContainerStyle: styles19.contentInner,
33925
34494
  showsVerticalScrollIndicator: false,
33926
34495
  children: [
33927
- /* @__PURE__ */ jsx23(Text12, { style: [styles19.sectionTitle, { color: palette.mutedText }], children: isPortuguese ? "Resumo do documento" : "Document summary" }),
34496
+ /* @__PURE__ */ jsx23(Text13, { style: [styles19.sectionTitle, { color: palette.mutedText }], children: isPortuguese ? "Resumo do documento" : "Document summary" }),
33928
34497
  /* @__PURE__ */ jsx23(View19, { style: [styles19.rows, { borderTopColor: palette.divider }], children: rows.map((row) => /* @__PURE__ */ jsxs18(
33929
34498
  View19,
33930
34499
  {
33931
34500
  style: [styles19.row, { borderBottomColor: palette.divider }],
33932
34501
  children: [
33933
- /* @__PURE__ */ jsx23(Text12, { style: [styles19.label, { color: palette.mutedText }], children: row.label }),
34502
+ /* @__PURE__ */ jsx23(Text13, { style: [styles19.label, { color: palette.mutedText }], children: row.label }),
33934
34503
  /* @__PURE__ */ jsx23(
33935
- Text12,
34504
+ Text13,
33936
34505
  {
33937
34506
  style: [styles19.value, { color: palette.text }],
33938
34507
  selectable: true,
@@ -33995,7 +34564,7 @@ var styles19 = StyleSheet19.create({
33995
34564
  });
33996
34565
 
33997
34566
  // components/OverflowSheet.tsx
33998
- import { Pressable as Pressable12, StyleSheet as StyleSheet20, Text as Text13, View as View20 } from "react-native";
34567
+ import { Pressable as Pressable12, StyleSheet as StyleSheet20, Text as Text14, View as View20 } from "react-native";
33999
34568
  import { useViewerStore as useViewerStore17 } from "@papyrus-sdk/core";
34000
34569
  import { jsx as jsx24 } from "react/jsx-runtime";
34001
34570
  var actionLabels = [{ key: "actions", label: "Document actions" }];
@@ -34035,7 +34604,7 @@ function OverflowSheet({
34035
34604
  ],
34036
34605
  accessibilityRole: "button",
34037
34606
  accessibilityLabel: action.label,
34038
- children: /* @__PURE__ */ jsx24(Text13, { style: [styles20.actionText, { color: palette.text }], children: action.label })
34607
+ children: /* @__PURE__ */ jsx24(Text14, { style: [styles20.actionText, { color: palette.text }], children: action.label })
34039
34608
  },
34040
34609
  action.key
34041
34610
  )) })
@@ -34060,10 +34629,11 @@ import {
34060
34629
  PanResponder as PanResponder2,
34061
34630
  Pressable as Pressable13,
34062
34631
  StyleSheet as StyleSheet21,
34063
- Text as Text14,
34632
+ Text as Text15,
34064
34633
  View as View21,
34065
34634
  useWindowDimensions as useWindowDimensions9
34066
34635
  } from "react-native";
34636
+ import Reanimated, { useAnimatedStyle as useAnimatedStyle3 } from "react-native-reanimated";
34067
34637
  import { useViewerStore as useViewerStore18 } from "@papyrus-sdk/core";
34068
34638
 
34069
34639
  // components/progressPillMode.ts
@@ -34083,9 +34653,11 @@ import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
34083
34653
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
34084
34654
  function ProgressPill({
34085
34655
  documentType,
34656
+ navigationInDock = false,
34086
34657
  onPress,
34087
34658
  onOpenPageJump,
34088
34659
  onNavigateToPage,
34660
+ onTurnPage,
34089
34661
  onScrubbingChange
34090
34662
  }) {
34091
34663
  const {
@@ -34095,12 +34667,20 @@ function ProgressPill({
34095
34667
  textLength,
34096
34668
  comicLayoutMode,
34097
34669
  viewMode,
34670
+ epubReadingMode,
34671
+ epubLocation,
34098
34672
  locale,
34099
34673
  uiTheme,
34100
34674
  accentColor,
34101
34675
  mobileChromeVisible,
34102
34676
  mobileProgressPillVisible
34103
34677
  } = useViewerStore18();
34678
+ const chromeVisible = mobileChromeVisible && mobileProgressPillVisible;
34679
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
34680
+ const chromeMotion = useAnimatedStyle3(() => {
34681
+ "worklet";
34682
+ return { opacity: chromeProgress.value };
34683
+ });
34104
34684
  const t = getStrings(locale);
34105
34685
  const isDark = uiTheme === "dark";
34106
34686
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
@@ -34145,10 +34725,10 @@ function ProgressPill({
34145
34725
  return `${displayedPage}/${pageCount || 0}`;
34146
34726
  }
34147
34727
  if (documentType === "epub") {
34148
- return `Cap. ${displayedPage} \xB7 ${percent}%`;
34728
+ return `Cap. ${displayedPage} \xB7 ${scrubPreviewPage === null && epubLocation ? clampPercent(epubLocation.progress * 100) : percent}%`;
34149
34729
  }
34150
34730
  return `${percent}%`;
34151
- }, [displayedPage, documentType, pageCount]);
34731
+ }, [displayedPage, documentType, pageCount, epubLocation, scrubPreviewPage]);
34152
34732
  const thumbTop = resolvePageScrubberThumbTop({
34153
34733
  currentPage,
34154
34734
  trackHeight,
@@ -34190,7 +34770,7 @@ function ProgressPill({
34190
34770
  },
34191
34771
  []
34192
34772
  );
34193
- if (!shouldRenderPageScrubber({
34773
+ if (!(documentType === "epub" && mobileProgressPillVisible) && !shouldRenderPageScrubber({
34194
34774
  mobileChromeVisible,
34195
34775
  mobileProgressPillVisible,
34196
34776
  isScrubbing: isScrubbingRef.current
@@ -34212,7 +34792,7 @@ function ProgressPill({
34212
34792
  onPress: () => onPressRef.current(),
34213
34793
  style: [styles21.singlePill, isDark && styles21.pillDark],
34214
34794
  testID: "papyrus-progress-text",
34215
- children: /* @__PURE__ */ jsxs19(Text14, { style: [styles21.label, isDark && styles21.labelDark], children: [
34795
+ children: /* @__PURE__ */ jsxs19(Text15, { style: [styles21.label, isDark && styles21.labelDark], children: [
34216
34796
  progress,
34217
34797
  "%"
34218
34798
  ] })
@@ -34221,16 +34801,17 @@ function ProgressPill({
34221
34801
  }
34222
34802
  );
34223
34803
  }
34224
- const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : viewMode;
34804
+ const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : documentType === "epub" ? epubReadingMode === "paged" ? "single" : "continuous" : viewMode;
34225
34805
  const pillMode = resolveProgressPillMode(progressViewMode, currentPage, pageCount);
34806
+ if (pillMode.kind === "navigation" && navigationInDock) return null;
34226
34807
  if (pillMode.kind === "navigation") {
34227
- const previousDisabled = pillMode.previousPage === null || !onNavigateToPage;
34228
- const nextDisabled = pillMode.nextPage === null || !onNavigateToPage;
34808
+ const previousDisabled = !onTurnPage && (pillMode.previousPage === null || !onNavigateToPage);
34809
+ const nextDisabled = !onTurnPage && (pillMode.nextPage === null || !onNavigateToPage);
34229
34810
  return /* @__PURE__ */ jsx25(
34230
34811
  View21,
34231
34812
  {
34232
34813
  pointerEvents: "box-none",
34233
- style: [styles21.frame, styles21.singleFrame, { top: offsets.progress, right: offsets.right }],
34814
+ style: [{ position: "absolute", zIndex: 18, bottom: offsets.bottom + MOBILE_CHROME_METRICS.bottomBarVisualHeight + 12, left: offsets.left, right: offsets.right, alignItems: "center" }],
34234
34815
  children: /* @__PURE__ */ jsxs19(View21, { style: [styles21.singlePill, isDark && styles21.pillDark], children: [
34235
34816
  /* @__PURE__ */ jsx25(
34236
34817
  Pressable13,
@@ -34239,10 +34820,10 @@ function ProgressPill({
34239
34820
  accessibilityLabel: t.previousPage,
34240
34821
  accessibilityState: { disabled: previousDisabled },
34241
34822
  disabled: previousDisabled,
34242
- onPress: () => pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34823
+ onPress: () => onTurnPage ? onTurnPage(-1) : pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34243
34824
  style: styles21.pageArrowHit,
34244
34825
  testID: "papyrus-progress-page-previous",
34245
- children: /* @__PURE__ */ jsx25(Text14, { style: [styles21.pageArrow, isDark && styles21.labelDark, previousDisabled && styles21.pageArrowDisabled], children: "\u2039" })
34826
+ children: /* @__PURE__ */ jsx25(Text15, { style: [styles21.pageArrow, isDark && styles21.labelDark, previousDisabled && styles21.pageArrowDisabled], children: "\u2039" })
34246
34827
  }
34247
34828
  ),
34248
34829
  /* @__PURE__ */ jsx25(
@@ -34253,7 +34834,7 @@ function ProgressPill({
34253
34834
  onPress: () => onPressRef.current(),
34254
34835
  style: styles21.singlePageCenter,
34255
34836
  testID: "papyrus-progress-page-grid",
34256
- children: /* @__PURE__ */ jsxs19(Text14, { style: [styles21.label, isDark && styles21.labelDark], children: [
34837
+ children: /* @__PURE__ */ jsxs19(Text15, { style: [styles21.label, isDark && styles21.labelDark], children: [
34257
34838
  currentPage,
34258
34839
  "/",
34259
34840
  pageCount || 0
@@ -34267,10 +34848,10 @@ function ProgressPill({
34267
34848
  accessibilityLabel: t.nextPage,
34268
34849
  accessibilityState: { disabled: nextDisabled },
34269
34850
  disabled: nextDisabled,
34270
- onPress: () => pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34851
+ onPress: () => onTurnPage ? onTurnPage(1) : pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34271
34852
  style: styles21.pageArrowHit,
34272
34853
  testID: "papyrus-progress-page-next",
34273
- children: /* @__PURE__ */ jsx25(Text14, { style: [styles21.pageArrow, isDark && styles21.labelDark, nextDisabled && styles21.pageArrowDisabled], children: "\u203A" })
34854
+ children: /* @__PURE__ */ jsx25(Text15, { style: [styles21.pageArrow, isDark && styles21.labelDark, nextDisabled && styles21.pageArrowDisabled], children: "\u203A" })
34274
34855
  }
34275
34856
  )
34276
34857
  ] })
@@ -34411,11 +34992,14 @@ function ProgressPill({
34411
34992
  }
34412
34993
  const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
34413
34994
  return /* @__PURE__ */ jsx25(
34414
- View21,
34995
+ Reanimated.View,
34415
34996
  {
34416
- pointerEvents: "box-none",
34997
+ pointerEvents: chromeVisible ? "box-none" : "none",
34998
+ accessibilityElementsHidden: !chromeVisible,
34999
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
34417
35000
  style: [
34418
35001
  styles21.frame,
35002
+ chromeMotion,
34419
35003
  resolvePageScrubberOverlayStyle(),
34420
35004
  {
34421
35005
  top: offsets.progress,
@@ -34466,7 +35050,7 @@ function ProgressPill({
34466
35050
  ...responderPanHandlers,
34467
35051
  testID: "papyrus-progress-pill",
34468
35052
  children: [
34469
- /* @__PURE__ */ jsx25(View21, { style: styles21.labelHit, children: /* @__PURE__ */ jsx25(Text14, { style: [styles21.label, isDark && styles21.labelDark], children: label }) }),
35053
+ /* @__PURE__ */ jsx25(View21, { style: styles21.labelHit, children: /* @__PURE__ */ jsx25(Text15, { style: [styles21.label, isDark && styles21.labelDark], children: label }) }),
34470
35054
  /* @__PURE__ */ jsx25(View21, { style: styles21.iconHit, children: /* @__PURE__ */ jsx25(
34471
35055
  IconPageNav,
34472
35056
  {
@@ -34597,7 +35181,7 @@ import {
34597
35181
  Keyboard,
34598
35182
  Pressable as Pressable14,
34599
35183
  StyleSheet as StyleSheet22,
34600
- Text as Text15,
35184
+ Text as Text16,
34601
35185
  TextInput as TextInput3,
34602
35186
  View as View22,
34603
35187
  useWindowDimensions as useWindowDimensions10
@@ -34730,7 +35314,7 @@ function SearchOverlay({
34730
35314
  onPress: handleClose,
34731
35315
  style: [styles22.closeButton, isDark && styles22.closeButtonDark],
34732
35316
  accessibilityLabel: "Close search",
34733
- children: /* @__PURE__ */ jsx26(Text15, { style: [styles22.closeText, isDark && styles22.closeTextDark], children: t.cancel })
35317
+ children: /* @__PURE__ */ jsx26(Text16, { style: [styles22.closeText, isDark && styles22.closeTextDark], children: t.cancel })
34734
35318
  }
34735
35319
  )
34736
35320
  ] }),
@@ -34749,7 +35333,7 @@ function SearchOverlay({
34749
35333
  children: /* @__PURE__ */ jsx26(IconChevronLeft, { size: 16, color: isDark ? "#e5e7eb" : "#111827" })
34750
35334
  }
34751
35335
  ),
34752
- /* @__PURE__ */ jsx26(Text15, { style: [styles22.metaText, isDark && styles22.metaTextDark], children: isSearching ? t.searching : `${currentCount}/${results.length} ${targetLabel}` }),
35336
+ /* @__PURE__ */ jsx26(Text16, { style: [styles22.metaText, isDark && styles22.metaTextDark], children: isSearching ? t.searching : `${currentCount}/${results.length} ${targetLabel}` }),
34753
35337
  /* @__PURE__ */ jsx26(
34754
35338
  Pressable14,
34755
35339
  {
@@ -34773,7 +35357,7 @@ function SearchOverlay({
34773
35357
  style: [styles22.primaryAction, { backgroundColor: accentColor }],
34774
35358
  accessibilityLabel: "Run search",
34775
35359
  testID: "papyrus-search-run",
34776
- children: /* @__PURE__ */ jsx26(Text15, { style: styles22.primaryActionText, children: t.searchGo })
35360
+ children: /* @__PURE__ */ jsx26(Text16, { style: styles22.primaryActionText, children: t.searchGo })
34777
35361
  }
34778
35362
  ),
34779
35363
  /* @__PURE__ */ jsx26(
@@ -34788,7 +35372,7 @@ function SearchOverlay({
34788
35372
  ],
34789
35373
  accessibilityLabel: "Open all results",
34790
35374
  children: /* @__PURE__ */ jsx26(
34791
- Text15,
35375
+ Text16,
34792
35376
  {
34793
35377
  style: [
34794
35378
  styles22.secondaryActionText,
@@ -34947,7 +35531,7 @@ var styles22 = StyleSheet22.create({
34947
35531
  });
34948
35532
 
34949
35533
  // components/SearchResultsSheet.tsx
34950
- import { Pressable as Pressable15, StyleSheet as StyleSheet23, Text as Text16 } from "react-native";
35534
+ import { Pressable as Pressable15, StyleSheet as StyleSheet23, Text as Text17 } from "react-native";
34951
35535
  import { useViewerStore as useViewerStore20 } from "@papyrus-sdk/core";
34952
35536
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
34953
35537
  function SearchResultsSheet({
@@ -34995,12 +35579,12 @@ function SearchResultsSheet({
34995
35579
  borderTopColor: palette.divider
34996
35580
  },
34997
35581
  children: [
34998
- /* @__PURE__ */ jsxs21(Text16, { style: [styles23.meta, { color: palette.mutedText }], children: [
35582
+ /* @__PURE__ */ jsxs21(Text17, { style: [styles23.meta, { color: palette.mutedText }], children: [
34999
35583
  results.length,
35000
35584
  " ",
35001
35585
  t.results
35002
35586
  ] }),
35003
- /* @__PURE__ */ jsx27(NativeSheetScrollView, { contentContainerStyle: styles23.content, children: results.length === 0 ? /* @__PURE__ */ jsx27(Text16, { style: [styles23.emptyText, { color: palette.mutedText }], children: t.noResults }) : results.map((result, index) => {
35587
+ /* @__PURE__ */ jsx27(NativeSheetScrollView, { contentContainerStyle: styles23.content, children: results.length === 0 ? /* @__PURE__ */ jsx27(Text17, { style: [styles23.emptyText, { color: palette.mutedText }], children: t.noResults }) : results.map((result, index) => {
35004
35588
  const isActive = index === activeSearchIndex;
35005
35589
  const isTextResult = "kind" in result && result.kind === "text";
35006
35590
  return /* @__PURE__ */ jsxs21(
@@ -35030,8 +35614,8 @@ function SearchResultsSheet({
35030
35614
  }
35031
35615
  ],
35032
35616
  children: [
35033
- /* @__PURE__ */ jsx27(Text16, { style: [styles23.resultLabel, { color: accentColor }], children: isTextResult ? getTextResultLabel(result.location.start) : getResultLabel(result.pageIndex) }),
35034
- /* @__PURE__ */ jsx27(Text16, { style: [styles23.resultText, { color: palette.text }], children: result.text })
35617
+ /* @__PURE__ */ jsx27(Text17, { style: [styles23.resultLabel, { color: accentColor }], children: isTextResult ? getTextResultLabel(result.location.start) : getResultLabel(result.pageIndex) }),
35618
+ /* @__PURE__ */ jsx27(Text17, { style: [styles23.resultText, { color: palette.text }], children: result.text })
35035
35619
  ]
35036
35620
  },
35037
35621
  isTextResult ? `text-${result.location.start}-${index}` : `page-${result.pageIndex}-${index}`
@@ -35152,15 +35736,10 @@ function ReadingShell({
35152
35736
  /* @__PURE__ */ jsx28(
35153
35737
  Topbar_default,
35154
35738
  {
35739
+ documentType,
35155
35740
  engine,
35156
35741
  title,
35157
- onOpenOverflow: () => {
35158
- closeMobileDestination();
35159
- setInfoOpen(false);
35160
- setActionsOpen(false);
35161
- setSettingsOpen(false);
35162
- setOverflowOpen(true);
35163
- },
35742
+ onOpenSettings: () => openDestination("display"),
35164
35743
  onOpenPageJump: () => setPageJumpOpen(true),
35165
35744
  showPageNavigationControls: false
35166
35745
  }
@@ -35168,6 +35747,7 @@ function ReadingShell({
35168
35747
  /* @__PURE__ */ jsx28(View23, { style: styles24.viewerStage, children: /* @__PURE__ */ jsx28(
35169
35748
  Viewer_default,
35170
35749
  {
35750
+ documentType,
35171
35751
  engine,
35172
35752
  ...viewerProps,
35173
35753
  pageScrubActive
@@ -35176,7 +35756,13 @@ function ReadingShell({
35176
35756
  /* @__PURE__ */ jsx28(
35177
35757
  ProgressPill,
35178
35758
  {
35759
+ navigationInDock: true,
35179
35760
  documentType,
35761
+ onTurnPage: documentType === "epub" ? (direction) => {
35762
+ const paginatedEngine = engine;
35763
+ void paginatedEngine.turnEpubPage?.(direction).catch(() => {
35764
+ });
35765
+ } : void 0,
35180
35766
  onNavigateToPage: navigateToPage,
35181
35767
  onPress: () => openDestination(
35182
35768
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
@@ -35188,6 +35774,12 @@ function ReadingShell({
35188
35774
  /* @__PURE__ */ jsx28(
35189
35775
  BottomBar_default,
35190
35776
  {
35777
+ onNavigateToPage: navigateToPage,
35778
+ onOpenPages: () => setPageJumpOpen(true),
35779
+ onTurnPage: documentType === "epub" ? (direction) => {
35780
+ const paginatedEngine = engine;
35781
+ void paginatedEngine.turnEpubPage?.(direction).catch((error) => console.warn("[Papyrus] page navigation failed", error));
35782
+ } : void 0,
35191
35783
  documentType,
35192
35784
  onOpenDestination: openDestination,
35193
35785
  onOpenInfo: () => openDestination("info"),
@@ -35267,6 +35859,7 @@ function ReadingShell({
35267
35859
  /* @__PURE__ */ jsx28(
35268
35860
  SettingsSheet_default,
35269
35861
  {
35862
+ onOpenInfo: () => openDestination("info"),
35270
35863
  engine,
35271
35864
  documentType,
35272
35865
  visible: settingsOpen,
@@ -35279,6 +35872,7 @@ function ReadingShell({
35279
35872
  /* @__PURE__ */ jsx28(
35280
35873
  PageJumpModal,
35281
35874
  {
35875
+ documentType,
35282
35876
  visible: pageJumpOpen,
35283
35877
  currentPage,
35284
35878
  pageCount,