@papyrus-sdk/ui-react-native 0.2.35 → 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
@@ -20559,7 +20559,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20559
20559
  var doc=contents.document,win=contents.window,start=null,blocked=false;
20560
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
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]'));}
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
20563
  function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20564
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
20565
  function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
@@ -20584,6 +20584,84 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20584
20584
  typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
20585
20585
  typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
20586
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
+ }
20587
20665
  function textNodes(document) {
20588
20666
  var walker = document.createTreeWalker(document.body, 4);
20589
20667
  var nodes = [], text = '', node;
@@ -20633,13 +20711,196 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20633
20711
  if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
20634
20712
  doc.body.appendChild(overlay);
20635
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);
20636
20893
  function render() {
20637
20894
  frame = 0; if (!alive || !options.isCurrent()) return;
20638
- var keep = new Set(), noteGroups = new Map();
20895
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20639
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)));
20640
20900
  options.annotations().forEach(function(annotation) {
20641
20901
  var anchor = annotation.anchor;
20642
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;}
20643
20904
  var range = resolveRange(contents,anchor); if (!range) return;
20644
20905
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20645
20906
  if (!rects.length) return;
@@ -20678,22 +20939,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20678
20939
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20679
20940
  });
20680
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);}});
20681
20943
  }
20682
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();}
20683
20948
  function tapMarkup(event) {
20684
20949
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20685
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;});});
20686
20951
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20687
20952
  }
20688
20953
  doc.addEventListener('click',tapMarkup);
20689
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20954
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20690
20955
  if (observer) observer.observe(doc.body);
20691
- win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
20692
- 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();}});
20693
20958
  schedule();
20694
- return {schedule:schedule, 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;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);win.removeEventListener('scroll',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();}};
20695
20960
  }
20696
- 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};
20697
20962
  });
20698
20963
 
20699
20964
  /* @papyrus-epub-annotations:end */
@@ -20709,6 +20974,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20709
20974
  let annotationDocumentId;
20710
20975
  let annotationLabels = {};
20711
20976
  let epubAnnotations = [];
20977
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20712
20978
  const epubAnnotationRenderers = new Map();
20713
20979
  let spineItems = [];
20714
20980
  let textPages = [];
@@ -21823,11 +22089,14 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21823
22089
  if (href && window.PapyrusEpubAnnotations) {
21824
22090
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21825
22091
  href, get documentId() { return annotationDocumentId; },
22092
+ spineIndex:contents.sectionIndex,
21826
22093
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21827
22094
  pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21828
22095
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22096
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21829
22097
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21830
22098
  });
22099
+ renderer.setInkMode(epubInkMode);
21831
22100
  epubAnnotationRenderers.set(contents,renderer);
21832
22101
  }
21833
22102
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21919,7 +22188,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21919
22188
  sendEvent('EPUB_TEXT_SELECTED', payload);
21920
22189
  });
21921
22190
  ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21922
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22191
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21923
22192
  }));
21924
22193
  rendition.on('relocated', (location) => {
21925
22194
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
@@ -22216,6 +22485,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22216
22485
  documentSessionId = payload.documentSessionId || null;
22217
22486
  epubAnnotations = [];
22218
22487
  annotationDocumentId = undefined;
22488
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22219
22489
  zoom = 1.0;
22220
22490
  epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22221
22491
  epubLocation = null;
@@ -22347,6 +22617,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22347
22617
  }
22348
22618
  sendResponse(id,true,null);return;
22349
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
+ }
22350
22626
  if(kind === 'epub-go-to-location'){
22351
22627
  if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22352
22628
  epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
@@ -25936,9 +26212,15 @@ var parseWebViewInteraction = (raw) => {
25936
26212
  };
25937
26213
 
25938
26214
  // components/epubAnnotationEvents.ts
25939
- 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
+ }
25940
26222
  function parseEpubAnnotationEvent(raw, session, documentId) {
25941
- if (!session || raw.length > 2e5) return null;
26223
+ if (!session || raw.length > 75e4) return null;
25942
26224
  let message;
25943
26225
  try {
25944
26226
  message = JSON.parse(raw);
@@ -25948,6 +26230,13 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
25948
26230
  const p = message?.payload;
25949
26231
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
25950
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
+ }
25951
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;
25952
26241
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
25953
26242
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
@@ -25984,7 +26273,7 @@ var WebViewViewer = ({
25984
26273
  }) => {
25985
26274
  const webViewRef = useRef2(null);
25986
26275
  const insets = usePapyrusSafeAreaInsets();
25987
- const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
26276
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
25988
26277
  const [selection, setSelection] = useState2(null);
25989
26278
  const t = getStrings(locale);
25990
26279
  const bridgeEngine = engine;
@@ -26069,6 +26358,15 @@ var WebViewViewer = ({
26069
26358
  if (!isLoaded) return;
26070
26359
  webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26071
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]);
26072
26370
  const sendSelectionCommand = (key) => {
26073
26371
  webViewRef.current?.postMessage(JSON.stringify({
26074
26372
  type: "epub-selection-action",
@@ -26128,6 +26426,10 @@ var WebViewViewer = ({
26128
26426
  else setSelectedAnnotation(annotationEvent.id);
26129
26427
  return;
26130
26428
  }
26429
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26430
+ addAnnotation(annotationEvent.annotation);
26431
+ setSelection(null);
26432
+ }
26131
26433
  const interaction = parseWebViewInteraction(raw);
26132
26434
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
26133
26435
  if (interaction?.kind === "tap") onTap?.();
@@ -32747,7 +33049,7 @@ function buildBottomBarLayout({
32747
33049
  toolDockOpen
32748
33050
  }) {
32749
33051
  const leftSlots = [];
32750
- if (documentType === "pdf") {
33052
+ if (documentType === "pdf" || documentType === "epub") {
32751
33053
  leftSlots.push({
32752
33054
  key: "annotate",
32753
33055
  active: toolDockOpen || activeMobileDestination === "annotate"