@papyrus-sdk/ui-react-native 0.2.35 → 0.2.37

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,205 @@ 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 previewSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
20720
+ previewSvg.setAttribute('data-papyrus-ink-preview-overlay','');
20721
+ previewSvg.style.cssText = 'position:fixed;left:0;top:0;overflow:hidden;pointer-events:none;z-index:2147482999';
20722
+ doc.documentElement.appendChild(previewSvg);
20723
+ var inkCanvas = doc.createElement('canvas');
20724
+ inkCanvas.dataset.papyrusInkCapture = '';
20725
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
20726
+ doc.documentElement.appendChild(inkCanvas);
20727
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
20728
+ function resizeCanvas() {
20729
+ var ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
20730
+ var width=Math.max(1,Math.min(4096,win.innerWidth||1));
20731
+ var height=Math.max(1,Math.min(4096,win.innerHeight||1));
20732
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
20733
+ previewSvg.setAttribute('width',String(width));previewSvg.setAttribute('height',String(height));
20734
+ previewSvg.setAttribute('viewBox','0 0 '+width+' '+height);
20735
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
20736
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
20737
+ }
20738
+ function collectTextRects() {
20739
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
20740
+ while ((node=walker.nextNode())) {
20741
+ var parent=node.parentElement;
20742
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
20743
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
20744
+ for (var start=0; start<text.length;) {
20745
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
20746
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
20747
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
20748
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20749
+ rects.forEach(function(rect) {
20750
+ if (!(rect.width>0 && rect.height>0)) return;
20751
+ 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,
20752
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
20753
+ });
20754
+ start=end;
20755
+ }
20756
+ }
20757
+ return entries;
20758
+ }
20759
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
20760
+ function inkViewportExtent() {
20761
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
20762
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
20763
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
20764
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
20765
+ }
20766
+ function inkStrokeWidthPx(width,preset) {
20767
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
20768
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
20769
+ }
20770
+ function closestGlyph(entry,x,y,maxDistance) {
20771
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
20772
+ for (var glyph of source) {
20773
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
20774
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20775
+ rects.forEach(function(rect) {
20776
+ if (!(rect.width>0 && rect.height>0)) return;
20777
+ 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));
20778
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
20779
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
20780
+ });
20781
+ }
20782
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
20783
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
20784
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\\([^\\n<>]+\\)$/.test(cfiRange)) return null;
20785
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
20786
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
20787
+ }
20788
+ function renderInkAnnotation(annotation,keep,bodyRect) {
20789
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
20790
+ var data=annotation.epubInk;
20791
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
20792
+ keep.add(annotation.id);
20793
+ var group=inkRegistry.get(annotation.id);
20794
+ 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); }
20795
+ group.replaceChildren();
20796
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
20797
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
20798
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
20799
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
20800
+ data.segments.forEach(function(segment) {
20801
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
20802
+ var paths=[], current=[];
20803
+ segment.points.forEach(function(point) {
20804
+ var range=rangeCache.get(point.cfiRange);
20805
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
20806
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
20807
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
20808
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
20809
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
20810
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
20811
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
20812
+ current.push({x:x,y:y,lineHeight:lineHeight});
20813
+ });
20814
+ if(current.length>1)paths.push(current);
20815
+ paths.forEach(function(points) {
20816
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
20817
+ 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);}
20818
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
20819
+ 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);
20820
+ });
20821
+ });
20822
+ }
20823
+ function clearPreview() {
20824
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
20825
+ previewSvg.replaceChildren();
20826
+ }
20827
+ function createPreviewPath(x,y) {
20828
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
20829
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
20830
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
20831
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
20832
+ path.style.pointerEvents='none';previewSvg.appendChild(path);return path;
20833
+ }
20834
+ function updatePreviewPath(clientX,clientY) {
20835
+ var x=clientX,y=clientY;
20836
+ var gesture=activeGesture;
20837
+ var startsSegment=!gesture.previewPath||gesture.previewPathPoints>=32;
20838
+ if(startsSegment){
20839
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
20840
+ gesture.previewPathPoints=1;
20841
+ }
20842
+ if(!startsSegment||gesture.previewLast){
20843
+ var pathData=gesture.previewPath.getAttribute('d')||'';
20844
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
20845
+ gesture.previewPathPoints++;
20846
+ }
20847
+ gesture.previewLast={x:x,y:y};
20848
+ }
20849
+ function cancelGesture() {
20850
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20851
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
20852
+ }
20853
+ function commitGesture() {
20854
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20855
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
20856
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
20857
+ anchorSamples.forEach(function(point) {
20858
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
20859
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
20860
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
20861
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
20862
+ current.push(anchored);
20863
+ });
20864
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
20865
+ segments=segments.slice(0,64);
20866
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
20867
+ if(!pointCount)return;
20868
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
20869
+ 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))};});
20870
+ 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;}));
20871
+ 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;}));
20872
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
20873
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
20874
+ 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,
20875
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
20876
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
20877
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
20878
+ }
20879
+ function pointerDown(event) {
20880
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
20881
+ if(activeGesture){cancelGesture();return;}
20882
+ event.preventDefault();event.stopPropagation();
20883
+ 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};
20884
+ updatePreviewPath(event.clientX,event.clientY);
20885
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
20886
+ }
20887
+ function pointerMove(event) {
20888
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
20889
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
20890
+ event.preventDefault();event.stopPropagation();
20891
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
20892
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
20893
+ 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();}
20894
+ updatePreviewPath(event.clientX,event.clientY);
20895
+ activeGesture.last={x:event.clientX,y:event.clientY};
20896
+ }
20897
+ 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();}
20898
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
20899
+ resizeCanvas();rebuildSpatialIndex();
20900
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
20901
+ win.addEventListener('blur',pointerCancel);
20636
20902
  function render() {
20637
20903
  frame = 0; if (!alive || !options.isCurrent()) return;
20638
- var keep = new Set(), noteGroups = new Map();
20904
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20639
20905
  hitRegions = [];
20906
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
20907
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
20908
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
20640
20909
  options.annotations().forEach(function(annotation) {
20641
20910
  var anchor = annotation.anchor;
20642
20911
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
20912
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
20643
20913
  var range = resolveRange(contents,anchor); if (!range) return;
20644
20914
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20645
20915
  if (!rects.length) return;
@@ -20678,22 +20948,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20678
20948
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20679
20949
  });
20680
20950
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
20951
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
20681
20952
  }
20682
20953
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
20954
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
20955
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
20956
+ function handleDocumentLoad(){rebuildAndSchedule();}
20683
20957
  function tapMarkup(event) {
20684
20958
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20685
20959
  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
20960
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20687
20961
  }
20688
20962
  doc.addEventListener('click',tapMarkup);
20689
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20963
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20690
20964
  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);
20965
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
20966
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
20693
20967
  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();}};
20968
+ 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();previewSvg.remove();overlay.remove();doc.body.style.position=originalPosition;registry.clear();inkRegistry.clear();rangeCache.clear();}};
20695
20969
  }
20696
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20970
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20697
20971
  });
20698
20972
 
20699
20973
  /* @papyrus-epub-annotations:end */
@@ -20709,6 +20983,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20709
20983
  let annotationDocumentId;
20710
20984
  let annotationLabels = {};
20711
20985
  let epubAnnotations = [];
20986
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20712
20987
  const epubAnnotationRenderers = new Map();
20713
20988
  let spineItems = [];
20714
20989
  let textPages = [];
@@ -21823,11 +22098,14 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21823
22098
  if (href && window.PapyrusEpubAnnotations) {
21824
22099
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21825
22100
  href, get documentId() { return annotationDocumentId; },
22101
+ spineIndex:contents.sectionIndex,
21826
22102
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21827
22103
  pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21828
22104
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22105
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21829
22106
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21830
22107
  });
22108
+ renderer.setInkMode(epubInkMode);
21831
22109
  epubAnnotationRenderers.set(contents,renderer);
21832
22110
  }
21833
22111
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21919,7 +22197,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21919
22197
  sendEvent('EPUB_TEXT_SELECTED', payload);
21920
22198
  });
21921
22199
  ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21922
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22200
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21923
22201
  }));
21924
22202
  rendition.on('relocated', (location) => {
21925
22203
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
@@ -22216,6 +22494,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22216
22494
  documentSessionId = payload.documentSessionId || null;
22217
22495
  epubAnnotations = [];
22218
22496
  annotationDocumentId = undefined;
22497
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22219
22498
  zoom = 1.0;
22220
22499
  epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22221
22500
  epubLocation = null;
@@ -22347,6 +22626,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22347
22626
  }
22348
22627
  sendResponse(id,true,null);return;
22349
22628
  }
22629
+ if (kind === 'set-epub-ink-mode') {
22630
+ if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
22631
+ 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};
22632
+ epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
22633
+ sendResponse(id,true,null);return;
22634
+ }
22350
22635
  if(kind === 'epub-go-to-location'){
22351
22636
  if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22352
22637
  epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
@@ -25936,9 +26221,15 @@ var parseWebViewInteraction = (raw) => {
25936
26221
  };
25937
26222
 
25938
26223
  // components/epubAnnotationEvents.ts
25939
- import { validateAnnotationAnchor } from "@papyrus-sdk/core";
26224
+ import { validateAnnotationAnchor, validateEpubInkData } from "@papyrus-sdk/core";
26225
+ var EPUB_INK_PRESETS = ["ink", "highlight", "underline"];
26226
+ function resolveEpubInkMode(input) {
26227
+ if (!input.isLoaded || input.documentType !== "epub") return null;
26228
+ const preset = EPUB_INK_PRESETS.includes(input.preset) ? input.preset : "ink";
26229
+ return { enabled: input.activeTool === "ink" && EPUB_INK_PRESETS.includes(input.preset), preset, color: input.color, opacity: input.opacity, strokeWidth: input.strokeWidth };
26230
+ }
25940
26231
  function parseEpubAnnotationEvent(raw, session, documentId) {
25941
- if (!session || raw.length > 2e5) return null;
26232
+ if (!session || raw.length > 75e4) return null;
25942
26233
  let message;
25943
26234
  try {
25944
26235
  message = JSON.parse(raw);
@@ -25948,6 +26239,13 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
25948
26239
  const p = message?.payload;
25949
26240
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
25950
26241
  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;
26242
+ if (message.name === "EPUB_INK_CREATED") {
26243
+ const a = p.annotation, sidecar = a?.epubInk, first = sidecar?.segments?.[0], point = first?.points?.[0];
26244
+ 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;
26245
+ const rect = a.rect, path = a.path;
26246
+ 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;
26247
+ return { kind: "ink", session, annotation: a };
26248
+ }
25951
26249
  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
26250
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
25953
26251
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
@@ -25984,7 +26282,7 @@ var WebViewViewer = ({
25984
26282
  }) => {
25985
26283
  const webViewRef = useRef2(null);
25986
26284
  const insets = usePapyrusSafeAreaInsets();
25987
- const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
26285
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = useViewerStore2();
25988
26286
  const [selection, setSelection] = useState2(null);
25989
26287
  const t = getStrings(locale);
25990
26288
  const bridgeEngine = engine;
@@ -26069,6 +26367,15 @@ var WebViewViewer = ({
26069
26367
  if (!isLoaded) return;
26070
26368
  webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26071
26369
  }, [isLoaded, epubReadingMode, bridgeEngine]);
26370
+ useEffect2(() => {
26371
+ if (!isLoaded || documentType !== "epub") return;
26372
+ const inkMode = resolveEpubInkMode({ documentType, isLoaded, activeTool, preset: activeDrawToolPreset, color: annotationColor, opacity: annotationOpacity, strokeWidth: inkStrokeWidth });
26373
+ if (!inkMode) return;
26374
+ webViewRef.current?.postMessage(JSON.stringify({ id: `epub-ink-mode-${Date.now()}`, kind: "set-epub-ink-mode", payload: {
26375
+ ...inkMode,
26376
+ documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.()
26377
+ } }));
26378
+ }, [isLoaded, documentType, activeTool, activeDrawToolPreset, annotationColor, annotationOpacity, inkStrokeWidth, bridgeEngine]);
26072
26379
  const sendSelectionCommand = (key) => {
26073
26380
  webViewRef.current?.postMessage(JSON.stringify({
26074
26381
  type: "epub-selection-action",
@@ -26128,6 +26435,10 @@ var WebViewViewer = ({
26128
26435
  else setSelectedAnnotation(annotationEvent.id);
26129
26436
  return;
26130
26437
  }
26438
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26439
+ addAnnotation(annotationEvent.annotation);
26440
+ setSelection(null);
26441
+ }
26131
26442
  const interaction = parseWebViewInteraction(raw);
26132
26443
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
26133
26444
  if (interaction?.kind === "tap") onTap?.();
@@ -32747,7 +33058,7 @@ function buildBottomBarLayout({
32747
33058
  toolDockOpen
32748
33059
  }) {
32749
33060
  const leftSlots = [];
32750
- if (documentType === "pdf") {
33061
+ if (documentType === "pdf" || documentType === "epub") {
32751
33062
  leftSlots.push({
32752
33063
  key: "annotate",
32753
33064
  active: toolDockOpen || activeMobileDestination === "annotate"