@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.js CHANGED
@@ -20564,7 +20564,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20564
20564
  var doc=contents.document,win=contents.window,start=null,blocked=false;
20565
20565
  function point(e){var t=e.changedTouches&&e.changedTouches[0]||e.touches&&e.touches[0];return t?{x:t.clientX,y:t.clientY,time:Date.now()}:null;}
20566
20566
  function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20567
- function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
20567
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay],[data-papyrus-ink-capture]'));}
20568
20568
  function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20569
20569
  function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
20570
20570
  function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
@@ -20589,6 +20589,84 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20589
20589
  typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
20590
20590
  typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
20591
20591
  }
20592
+ function simplifyPath(points, tolerance, maxPoints) {
20593
+ if (!Array.isArray(points) || points.length <= 2) return Array.isArray(points) ? points.slice() : [];
20594
+ tolerance = Number.isFinite(tolerance) ? Math.max(0, tolerance) : 1.5;
20595
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20596
+ var keep = new Uint8Array(points.length); keep[0] = 1; keep[points.length - 1] = 1;
20597
+ var stack = [[0, points.length - 1]], toleranceSquared = tolerance * tolerance;
20598
+ while (stack.length) {
20599
+ var interval = stack.pop(), first = interval[0], last = interval[1], a = points[first], b = points[last];
20600
+ var dx = b.x - a.x, dy = b.y - a.y, lengthSquared = dx * dx + dy * dy, maxDistance = toleranceSquared, split = -1;
20601
+ for (var i = first + 1; i < last; i++) {
20602
+ var p = points[i], ratio = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
20603
+ var px = a.x + ratio * dx, py = a.y + ratio * dy, ox = p.x - px, oy = p.y - py, distance = ox * ox + oy * oy;
20604
+ if (distance > maxDistance) { maxDistance = distance; split = i; }
20605
+ }
20606
+ if (split >= 0) { keep[split] = 1; stack.push([first, split], [split, last]); }
20607
+ }
20608
+ var result = []; for (var j = 0; j < points.length; j++) if (keep[j]) result.push(points[j]);
20609
+ if (result.length <= maxPoints) return result;
20610
+ var capped = []; for (var k = 0; k < maxPoints; k++) capped.push(result[Math.round(k * (result.length - 1) / (maxPoints - 1))]);
20611
+ return capped;
20612
+ }
20613
+ function compactGesturePoints(points, maxPoints) {
20614
+ if (!Array.isArray(points)) return [];
20615
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 1024;
20616
+ if (points.length <= maxPoints) return points.slice();
20617
+ var compacted = [];
20618
+ for (var i = 0; i < maxPoints; i++) compacted.push(points[Math.round(i * (points.length - 1) / (maxPoints - 1))]);
20619
+ return compacted;
20620
+ }
20621
+ function densifyPath(points, maxStep, maxPoints) {
20622
+ if (!Array.isArray(points) || !points.length) return [];
20623
+ if (points.length === 1) return points.slice();
20624
+ maxStep = Number.isFinite(maxStep) ? Math.max(1, maxStep) : 12;
20625
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
20626
+ var cumulative = [0], total = 0;
20627
+ for (var i = 1; i < points.length; i++) {
20628
+ if (![points[i-1].x,points[i-1].y,points[i].x,points[i].y].every(Number.isFinite)) return [];
20629
+ total += Math.hypot(points[i].x-points[i-1].x, points[i].y-points[i-1].y);
20630
+ cumulative.push(total);
20631
+ }
20632
+ if (!total) return [points[0], points[points.length-1]];
20633
+ var intervals = Math.min(maxPoints-1, Math.max(1, Math.ceil(total/maxStep))), result = [], segment = 1;
20634
+ for (var sample = 0; sample <= intervals; sample++) {
20635
+ var distance = total*sample/intervals;
20636
+ while (segment < cumulative.length-1 && cumulative[segment] < distance) segment++;
20637
+ var start = points[segment-1], end = points[segment], span = cumulative[segment]-cumulative[segment-1];
20638
+ var ratio = span ? (distance-cumulative[segment-1])/span : 0;
20639
+ result.push({x:start.x+(end.x-start.x)*ratio,y:start.y+(end.y-start.y)*ratio});
20640
+ }
20641
+ return result;
20642
+ }
20643
+ function createSpatialIndex(entries, cellSize) {
20644
+ cellSize = Number.isFinite(cellSize) ? Math.max(8, cellSize) : 64;
20645
+ var cells = new Map();
20646
+ entries.forEach(function(entry) {
20647
+ var left = Math.floor(entry.left / cellSize), right = Math.floor(entry.right / cellSize);
20648
+ var top = Math.floor(entry.top / cellSize), bottom = Math.floor(entry.bottom / cellSize);
20649
+ for (var x = left; x <= right; x++) for (var y = top; y <= bottom; y++) {
20650
+ var key = x + ':' + y, bucket = cells.get(key); if (!bucket) { bucket = []; cells.set(key, bucket); } bucket.push(entry);
20651
+ }
20652
+ });
20653
+ return {cells:cells,cellSize:cellSize};
20654
+ }
20655
+ function queryNearestTextRect(index, x, y, maxDistance) {
20656
+ if (!index || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(maxDistance) || maxDistance < 0) return null;
20657
+ var size = index.cellSize, minX = Math.floor((x-maxDistance)/size), maxX = Math.floor((x+maxDistance)/size);
20658
+ var minY = Math.floor((y-maxDistance)/size), maxY = Math.floor((y+maxDistance)/size), seen = new Set(), best = null;
20659
+ for (var cx=minX; cx<=maxX; cx++) for (var cy=minY; cy<=maxY; cy++) {
20660
+ var bucket = index.cells.get(cx+':'+cy); if (!bucket) continue;
20661
+ bucket.forEach(function(entry) {
20662
+ if (seen.has(entry)) return; seen.add(entry);
20663
+ var nearestX = Math.max(entry.left,Math.min(entry.right,x)), nearestY = Math.max(entry.top,Math.min(entry.bottom,y));
20664
+ var dx=x-nearestX,dy=y-nearestY,distance=Math.sqrt(dx*dx+dy*dy);
20665
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={entry:entry,distance:distance,x:nearestX,y:nearestY};
20666
+ });
20667
+ }
20668
+ return best;
20669
+ }
20592
20670
  function textNodes(document) {
20593
20671
  var walker = document.createTreeWalker(document.body, 4);
20594
20672
  var nodes = [], text = '', node;
@@ -20638,13 +20716,205 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20638
20716
  if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
20639
20717
  doc.body.appendChild(overlay);
20640
20718
  var registry = new Map(), hitRegions = [];
20719
+ var inkRegistry = new Map(), rangeCache = new Map(), spatialIndex = null, activeGesture = null, inkConfig = {enabled:false,preset:'ink'};
20720
+ var inkSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
20721
+ inkSvg.setAttribute('data-papyrus-ink-overlay','');
20722
+ inkSvg.style.cssText = 'position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1';
20723
+ overlay.appendChild(inkSvg);
20724
+ var previewSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
20725
+ previewSvg.setAttribute('data-papyrus-ink-preview-overlay','');
20726
+ previewSvg.style.cssText = 'position:fixed;left:0;top:0;overflow:hidden;pointer-events:none;z-index:2147482999';
20727
+ doc.documentElement.appendChild(previewSvg);
20728
+ var inkCanvas = doc.createElement('canvas');
20729
+ inkCanvas.dataset.papyrusInkCapture = '';
20730
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
20731
+ doc.documentElement.appendChild(inkCanvas);
20732
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
20733
+ function resizeCanvas() {
20734
+ var ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
20735
+ var width=Math.max(1,Math.min(4096,win.innerWidth||1));
20736
+ var height=Math.max(1,Math.min(4096,win.innerHeight||1));
20737
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
20738
+ previewSvg.setAttribute('width',String(width));previewSvg.setAttribute('height',String(height));
20739
+ previewSvg.setAttribute('viewBox','0 0 '+width+' '+height);
20740
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
20741
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
20742
+ }
20743
+ function collectTextRects() {
20744
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
20745
+ while ((node=walker.nextNode())) {
20746
+ var parent=node.parentElement;
20747
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
20748
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
20749
+ for (var start=0; start<text.length;) {
20750
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
20751
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
20752
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
20753
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20754
+ rects.forEach(function(rect) {
20755
+ if (!(rect.width>0 && rect.height>0)) return;
20756
+ 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,
20757
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
20758
+ });
20759
+ start=end;
20760
+ }
20761
+ }
20762
+ return entries;
20763
+ }
20764
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
20765
+ function inkViewportExtent() {
20766
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
20767
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
20768
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
20769
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
20770
+ }
20771
+ function inkStrokeWidthPx(width,preset) {
20772
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
20773
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
20774
+ }
20775
+ function closestGlyph(entry,x,y,maxDistance) {
20776
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
20777
+ for (var glyph of source) {
20778
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
20779
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20780
+ rects.forEach(function(rect) {
20781
+ if (!(rect.width>0 && rect.height>0)) return;
20782
+ 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));
20783
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
20784
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
20785
+ });
20786
+ }
20787
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
20788
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
20789
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\\([^\\n<>]+\\)$/.test(cfiRange)) return null;
20790
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
20791
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
20792
+ }
20793
+ function renderInkAnnotation(annotation,keep,bodyRect) {
20794
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
20795
+ var data=annotation.epubInk;
20796
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
20797
+ keep.add(annotation.id);
20798
+ var group=inkRegistry.get(annotation.id);
20799
+ 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); }
20800
+ group.replaceChildren();
20801
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
20802
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
20803
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
20804
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
20805
+ data.segments.forEach(function(segment) {
20806
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
20807
+ var paths=[], current=[];
20808
+ segment.points.forEach(function(point) {
20809
+ var range=rangeCache.get(point.cfiRange);
20810
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
20811
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
20812
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
20813
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
20814
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
20815
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
20816
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
20817
+ current.push({x:x,y:y,lineHeight:lineHeight});
20818
+ });
20819
+ if(current.length>1)paths.push(current);
20820
+ paths.forEach(function(points) {
20821
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
20822
+ 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);}
20823
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
20824
+ 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);
20825
+ });
20826
+ });
20827
+ }
20828
+ function clearPreview() {
20829
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
20830
+ previewSvg.replaceChildren();
20831
+ }
20832
+ function createPreviewPath(x,y) {
20833
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
20834
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
20835
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
20836
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
20837
+ path.style.pointerEvents='none';previewSvg.appendChild(path);return path;
20838
+ }
20839
+ function updatePreviewPath(clientX,clientY) {
20840
+ var x=clientX,y=clientY;
20841
+ var gesture=activeGesture;
20842
+ var startsSegment=!gesture.previewPath||gesture.previewPathPoints>=32;
20843
+ if(startsSegment){
20844
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
20845
+ gesture.previewPathPoints=1;
20846
+ }
20847
+ if(!startsSegment||gesture.previewLast){
20848
+ var pathData=gesture.previewPath.getAttribute('d')||'';
20849
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
20850
+ gesture.previewPathPoints++;
20851
+ }
20852
+ gesture.previewLast={x:x,y:y};
20853
+ }
20854
+ function cancelGesture() {
20855
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20856
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
20857
+ }
20858
+ function commitGesture() {
20859
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20860
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
20861
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
20862
+ anchorSamples.forEach(function(point) {
20863
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
20864
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
20865
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
20866
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
20867
+ current.push(anchored);
20868
+ });
20869
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
20870
+ segments=segments.slice(0,64);
20871
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
20872
+ if(!pointCount)return;
20873
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
20874
+ 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))};});
20875
+ 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;}));
20876
+ 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;}));
20877
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
20878
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
20879
+ 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,
20880
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
20881
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
20882
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
20883
+ }
20884
+ function pointerDown(event) {
20885
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
20886
+ if(activeGesture){cancelGesture();return;}
20887
+ event.preventDefault();event.stopPropagation();
20888
+ 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};
20889
+ updatePreviewPath(event.clientX,event.clientY);
20890
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
20891
+ }
20892
+ function pointerMove(event) {
20893
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
20894
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
20895
+ event.preventDefault();event.stopPropagation();
20896
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
20897
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
20898
+ 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();}
20899
+ updatePreviewPath(event.clientX,event.clientY);
20900
+ activeGesture.last={x:event.clientX,y:event.clientY};
20901
+ }
20902
+ 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();}
20903
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
20904
+ resizeCanvas();rebuildSpatialIndex();
20905
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
20906
+ win.addEventListener('blur',pointerCancel);
20641
20907
  function render() {
20642
20908
  frame = 0; if (!alive || !options.isCurrent()) return;
20643
- var keep = new Set(), noteGroups = new Map();
20909
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20644
20910
  hitRegions = [];
20911
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
20912
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
20913
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
20645
20914
  options.annotations().forEach(function(annotation) {
20646
20915
  var anchor = annotation.anchor;
20647
20916
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
20917
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
20648
20918
  var range = resolveRange(contents,anchor); if (!range) return;
20649
20919
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20650
20920
  if (!rects.length) return;
@@ -20683,22 +20953,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20683
20953
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20684
20954
  });
20685
20955
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
20956
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
20686
20957
  }
20687
20958
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
20959
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
20960
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
20961
+ function handleDocumentLoad(){rebuildAndSchedule();}
20688
20962
  function tapMarkup(event) {
20689
20963
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20690
20964
  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;});});
20691
20965
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20692
20966
  }
20693
20967
  doc.addEventListener('click',tapMarkup);
20694
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20968
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20695
20969
  if (observer) observer.observe(doc.body);
20696
- win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
20697
- if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20970
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
20971
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
20698
20972
  schedule();
20699
- 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();}};
20973
+ 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();}};
20700
20974
  }
20701
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20975
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20702
20976
  });
20703
20977
 
20704
20978
  /* @papyrus-epub-annotations:end */
@@ -20714,6 +20988,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20714
20988
  let annotationDocumentId;
20715
20989
  let annotationLabels = {};
20716
20990
  let epubAnnotations = [];
20991
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20717
20992
  const epubAnnotationRenderers = new Map();
20718
20993
  let spineItems = [];
20719
20994
  let textPages = [];
@@ -21828,11 +22103,14 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21828
22103
  if (href && window.PapyrusEpubAnnotations) {
21829
22104
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21830
22105
  href, get documentId() { return annotationDocumentId; },
22106
+ spineIndex:contents.sectionIndex,
21831
22107
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21832
22108
  pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21833
22109
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22110
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21834
22111
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21835
22112
  });
22113
+ renderer.setInkMode(epubInkMode);
21836
22114
  epubAnnotationRenderers.set(contents,renderer);
21837
22115
  }
21838
22116
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21924,7 +22202,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21924
22202
  sendEvent('EPUB_TEXT_SELECTED', payload);
21925
22203
  });
21926
22204
  ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21927
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22205
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21928
22206
  }));
21929
22207
  rendition.on('relocated', (location) => {
21930
22208
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
@@ -22221,6 +22499,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22221
22499
  documentSessionId = payload.documentSessionId || null;
22222
22500
  epubAnnotations = [];
22223
22501
  annotationDocumentId = undefined;
22502
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22224
22503
  zoom = 1.0;
22225
22504
  epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22226
22505
  epubLocation = null;
@@ -22352,6 +22631,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22352
22631
  }
22353
22632
  sendResponse(id,true,null);return;
22354
22633
  }
22634
+ if (kind === 'set-epub-ink-mode') {
22635
+ if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
22636
+ 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};
22637
+ epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
22638
+ sendResponse(id,true,null);return;
22639
+ }
22355
22640
  if(kind === 'epub-go-to-location'){
22356
22641
  if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22357
22642
  epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
@@ -26144,8 +26429,14 @@ var parseWebViewInteraction = (raw) => {
26144
26429
 
26145
26430
  // components/epubAnnotationEvents.ts
26146
26431
  var import_core3 = require("@papyrus-sdk/core");
26432
+ var EPUB_INK_PRESETS = ["ink", "highlight", "underline"];
26433
+ function resolveEpubInkMode(input) {
26434
+ if (!input.isLoaded || input.documentType !== "epub") return null;
26435
+ const preset = EPUB_INK_PRESETS.includes(input.preset) ? input.preset : "ink";
26436
+ return { enabled: input.activeTool === "ink" && EPUB_INK_PRESETS.includes(input.preset), preset, color: input.color, opacity: input.opacity, strokeWidth: input.strokeWidth };
26437
+ }
26147
26438
  function parseEpubAnnotationEvent(raw, session, documentId) {
26148
- if (!session || raw.length > 2e5) return null;
26439
+ if (!session || raw.length > 75e4) return null;
26149
26440
  let message;
26150
26441
  try {
26151
26442
  message = JSON.parse(raw);
@@ -26155,6 +26446,13 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
26155
26446
  const p = message?.payload;
26156
26447
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
26157
26448
  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;
26449
+ if (message.name === "EPUB_INK_CREATED") {
26450
+ const a = p.annotation, sidecar = a?.epubInk, first = sidecar?.segments?.[0], point = first?.points?.[0];
26451
+ if (!a || a.type !== "ink" || typeof a.id !== "string" || !a.id || a.id.length > 256 || !Number.isSafeInteger(a.pageIndex) || a.pageIndex < 1 || !(0, import_core3.validateAnnotationAnchor)(a.anchor) || a.anchor.kind !== "epub-cfi" || !(0, import_core3.validateEpubInkData)(sidecar) || !point || a.anchor.cfiRange !== point.cfiRange || a.anchor.quote !== point.quote || a.anchor.href !== first.href || documentId && (sidecar.documentId && sidecar.documentId !== documentId || a.anchor.documentId && a.anchor.documentId !== documentId) || sidecar.documentId && a.anchor.documentId && sidecar.documentId !== a.anchor.documentId) return null;
26452
+ const rect = a.rect, path = a.path;
26453
+ 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;
26454
+ return { kind: "ink", session, annotation: a };
26455
+ }
26158
26456
  if (message.name !== "EPUB_TEXT_SELECTED" || !(0, import_core3.validateAnnotationAnchor)(p.anchor) || p.anchor.kind !== "epub-cfi" || p.text !== p.anchor.quote || !Number.isSafeInteger(p.pageIndex) || p.pageIndex < 1) return null;
26159
26457
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
26160
26458
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
@@ -26191,7 +26489,7 @@ var WebViewViewer = ({
26191
26489
  }) => {
26192
26490
  const webViewRef = (0, import_react4.useRef)(null);
26193
26491
  const insets = usePapyrusSafeAreaInsets();
26194
- const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26492
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26195
26493
  const [selection, setSelection] = (0, import_react4.useState)(null);
26196
26494
  const t = getStrings(locale);
26197
26495
  const bridgeEngine = engine;
@@ -26276,6 +26574,15 @@ var WebViewViewer = ({
26276
26574
  if (!isLoaded) return;
26277
26575
  webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26278
26576
  }, [isLoaded, epubReadingMode, bridgeEngine]);
26577
+ (0, import_react4.useEffect)(() => {
26578
+ if (!isLoaded || documentType !== "epub") return;
26579
+ const inkMode = resolveEpubInkMode({ documentType, isLoaded, activeTool, preset: activeDrawToolPreset, color: annotationColor, opacity: annotationOpacity, strokeWidth: inkStrokeWidth });
26580
+ if (!inkMode) return;
26581
+ webViewRef.current?.postMessage(JSON.stringify({ id: `epub-ink-mode-${Date.now()}`, kind: "set-epub-ink-mode", payload: {
26582
+ ...inkMode,
26583
+ documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.()
26584
+ } }));
26585
+ }, [isLoaded, documentType, activeTool, activeDrawToolPreset, annotationColor, annotationOpacity, inkStrokeWidth, bridgeEngine]);
26279
26586
  const sendSelectionCommand = (key) => {
26280
26587
  webViewRef.current?.postMessage(JSON.stringify({
26281
26588
  type: "epub-selection-action",
@@ -26335,6 +26642,10 @@ var WebViewViewer = ({
26335
26642
  else setSelectedAnnotation(annotationEvent.id);
26336
26643
  return;
26337
26644
  }
26645
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26646
+ addAnnotation(annotationEvent.annotation);
26647
+ setSelection(null);
26648
+ }
26338
26649
  const interaction = parseWebViewInteraction(raw);
26339
26650
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
26340
26651
  if (interaction?.kind === "tap") onTap?.();
@@ -32903,7 +33214,7 @@ function buildBottomBarLayout({
32903
33214
  toolDockOpen
32904
33215
  }) {
32905
33216
  const leftSlots = [];
32906
- if (documentType === "pdf") {
33217
+ if (documentType === "pdf" || documentType === "epub") {
32907
33218
  leftSlots.push({
32908
33219
  key: "annotate",
32909
33220
  active: toolDockOpen || activeMobileDestination === "annotate"