@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.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,196 @@ 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 inkCanvas = doc.createElement('canvas');
20725
+ inkCanvas.dataset.papyrusInkCapture = '';
20726
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
20727
+ doc.body.appendChild(inkCanvas);
20728
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
20729
+ function resizeCanvas() {
20730
+ var bodyRect=doc.body.getBoundingClientRect(), ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
20731
+ var width=Math.max(1,Math.min(4096,doc.body.clientWidth||bodyRect.width||Math.min(4096,win.innerWidth||1)));
20732
+ var height=Math.max(1,Math.min(4096,doc.body.clientHeight||bodyRect.height||Math.min(4096,win.innerHeight||1)));
20733
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
20734
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
20735
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
20736
+ }
20737
+ function collectTextRects() {
20738
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
20739
+ while ((node=walker.nextNode())) {
20740
+ var parent=node.parentElement;
20741
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
20742
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
20743
+ for (var start=0; start<text.length;) {
20744
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
20745
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
20746
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
20747
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20748
+ rects.forEach(function(rect) {
20749
+ if (!(rect.width>0 && rect.height>0)) return;
20750
+ entries.push({left:rect.left+win.scrollX,top:rect.top+win.scrollY,right:rect.right+win.scrollX,bottom:rect.bottom+win.scrollY,width:rect.width,height:rect.height,
20751
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
20752
+ });
20753
+ start=end;
20754
+ }
20755
+ }
20756
+ return entries;
20757
+ }
20758
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
20759
+ function inkViewportExtent() {
20760
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
20761
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
20762
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
20763
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
20764
+ }
20765
+ function inkStrokeWidthPx(width,preset) {
20766
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
20767
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
20768
+ }
20769
+ function closestGlyph(entry,x,y,maxDistance) {
20770
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
20771
+ for (var glyph of source) {
20772
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
20773
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
20774
+ rects.forEach(function(rect) {
20775
+ if (!(rect.width>0 && rect.height>0)) return;
20776
+ var nx=Math.max(rect.left+win.scrollX,Math.min(rect.right+win.scrollX,x)), ny=Math.max(rect.top+win.scrollY,Math.min(rect.bottom+win.scrollY,y));
20777
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
20778
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
20779
+ });
20780
+ }
20781
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
20782
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
20783
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\\([^\\n<>]+\\)$/.test(cfiRange)) return null;
20784
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
20785
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
20786
+ }
20787
+ function renderInkAnnotation(annotation,keep,bodyRect) {
20788
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
20789
+ var data=annotation.epubInk;
20790
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
20791
+ keep.add(annotation.id);
20792
+ var group=inkRegistry.get(annotation.id);
20793
+ if (!group) { group=doc.createElementNS('http://www.w3.org/2000/svg','g'); group.setAttribute('data-epub-ink-id',annotation.id); inkSvg.appendChild(group); inkRegistry.set(annotation.id,group); }
20794
+ group.replaceChildren();
20795
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
20796
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
20797
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
20798
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
20799
+ data.segments.forEach(function(segment) {
20800
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
20801
+ var paths=[], current=[];
20802
+ segment.points.forEach(function(point) {
20803
+ var range=rangeCache.get(point.cfiRange);
20804
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
20805
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
20806
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
20807
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
20808
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
20809
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
20810
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
20811
+ current.push({x:x,y:y,lineHeight:lineHeight});
20812
+ });
20813
+ if(current.length>1)paths.push(current);
20814
+ paths.forEach(function(points) {
20815
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
20816
+ for(var i=1;i<points.length;i++){var prev=points[i-1],point=points[i],mx=(prev.x+point.x)/2,my=(prev.y+point.y)/2;d+=' Q '+prev.x.toFixed(1)+' '+prev.y.toFixed(1)+' '+mx.toFixed(1)+' '+my.toFixed(1);}
20817
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
20818
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');path.setAttribute('d',d);path.setAttribute('fill','none');path.setAttribute('stroke',color);path.setAttribute('stroke-width',String(stroke));path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(opacity));group.appendChild(path);
20819
+ });
20820
+ });
20821
+ }
20822
+ function clearPreview() {
20823
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
20824
+ inkSvg.querySelectorAll('[data-papyrus-ink-preview]').forEach(function(path){path.remove();});
20825
+ }
20826
+ function createPreviewPath(x,y) {
20827
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
20828
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
20829
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
20830
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
20831
+ path.style.pointerEvents='none';inkSvg.appendChild(path);return path;
20832
+ }
20833
+ function updatePreviewPath(clientX,clientY) {
20834
+ var bodyRect=doc.body.getBoundingClientRect(),x=clientX-bodyRect.left,y=clientY-bodyRect.top;
20835
+ var gesture=activeGesture;
20836
+ if(!gesture.previewPath||gesture.previewPathPoints>=32){
20837
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
20838
+ gesture.previewPathPoints=1;
20839
+ }
20840
+ var pathData=gesture.previewPath.getAttribute('d')||'';
20841
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
20842
+ gesture.previewPathPoints++;
20843
+ gesture.previewLast={x:x,y:y};
20844
+ }
20845
+ function cancelGesture() {
20846
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20847
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
20848
+ }
20849
+ function commitGesture() {
20850
+ var gesture=activeGesture; activeGesture=null; clearPreview();
20851
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
20852
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
20853
+ anchorSamples.forEach(function(point) {
20854
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
20855
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
20856
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
20857
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
20858
+ current.push(anchored);
20859
+ });
20860
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
20861
+ segments=segments.slice(0,64);
20862
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
20863
+ if(!pointCount)return;
20864
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
20865
+ var normalized=simplified.map(function(p){return {x:Math.max(0,Math.min(1,p.x/width)),y:Math.max(0,Math.min(1,p.y/height))};});
20866
+ var minX=Math.min.apply(null,normalized.map(function(p){return p.x;})),maxX=Math.max.apply(null,normalized.map(function(p){return p.x;}));
20867
+ var minY=Math.min.apply(null,normalized.map(function(p){return p.y;})),maxY=Math.max.apply(null,normalized.map(function(p){return p.y;}));
20868
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
20869
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
20870
+ rect:{x:minX,y:minY,width:Math.min(1-minX,Math.max(0.001,maxX-minX)),height:Math.min(1-minY,Math.max(0.001,maxY-minY))},path:normalized,color:inkConfig.color,opacity:inkConfig.opacity,strokeWidth:inkConfig.strokeWidth,createdAt:Date.now(),content:first.quote,
20871
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
20872
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
20873
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
20874
+ }
20875
+ function pointerDown(event) {
20876
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
20877
+ if(activeGesture){cancelGesture();return;}
20878
+ event.preventDefault();event.stopPropagation();
20879
+ activeGesture={pointerId:event.pointerId,points:[{x:event.clientX+win.scrollX,y:event.clientY+win.scrollY}],last:{x:event.clientX,y:event.clientY},previewPath:null,previewPathPoints:0,previewLast:null};
20880
+ updatePreviewPath(event.clientX,event.clientY);
20881
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
20882
+ }
20883
+ function pointerMove(event) {
20884
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
20885
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
20886
+ event.preventDefault();event.stopPropagation();
20887
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
20888
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
20889
+ if(inkContext){inkContext.beginPath();inkContext.moveTo(activeGesture.last.x,activeGesture.last.y);inkContext.lineTo(event.clientX,event.clientY);inkContext.strokeStyle=inkConfig.color;inkContext.globalAlpha=inkConfig.opacity;inkContext.lineWidth=inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset);inkContext.lineCap='round';inkContext.lineJoin='round';inkContext.stroke();}
20890
+ updatePreviewPath(event.clientX,event.clientY);
20891
+ activeGesture.last={x:event.clientX,y:event.clientY};
20892
+ }
20893
+ function pointerUp(event) {if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;event.preventDefault();event.stopPropagation();var end={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY},last=activeGesture.points[activeGesture.points.length-1];if(Math.hypot(end.x-last.x,end.y-last.y)>0.5){activeGesture.points.push(end);updatePreviewPath(event.clientX,event.clientY);}commitGesture();}
20894
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
20895
+ resizeCanvas();rebuildSpatialIndex();
20896
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
20897
+ win.addEventListener('blur',pointerCancel);win.addEventListener('resize',resizeCanvas);
20641
20898
  function render() {
20642
20899
  frame = 0; if (!alive || !options.isCurrent()) return;
20643
- var keep = new Set(), noteGroups = new Map();
20900
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
20644
20901
  hitRegions = [];
20902
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
20903
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
20904
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
20645
20905
  options.annotations().forEach(function(annotation) {
20646
20906
  var anchor = annotation.anchor;
20647
20907
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
20908
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
20648
20909
  var range = resolveRange(contents,anchor); if (!range) return;
20649
20910
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
20650
20911
  if (!rects.length) return;
@@ -20683,22 +20944,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20683
20944
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
20684
20945
  });
20685
20946
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
20947
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
20686
20948
  }
20687
20949
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
20950
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
20951
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
20952
+ function handleDocumentLoad(){rebuildAndSchedule();}
20688
20953
  function tapMarkup(event) {
20689
20954
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
20690
20955
  var hit=hitRegions.find(function(item){return item.rects.some(function(r){return event.clientX>=r.left && event.clientX<=r.right && event.clientY>=r.top && event.clientY<=r.bottom;});});
20691
20956
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
20692
20957
  }
20693
20958
  doc.addEventListener('click',tapMarkup);
20694
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20959
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
20695
20960
  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);
20961
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
20962
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
20698
20963
  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();}};
20964
+ return {schedule:schedule, setInkMode:function(config){var preset=['ink','highlight','underline'].indexOf(config?.preset)>=0?config.preset:'ink';inkConfig={enabled:!!config?.enabled,preset:preset,color:/^#[0-9a-f]{6}$/i.test(config?.color)?config.color:'#2563eb',opacity:Number.isFinite(config?.opacity)?Math.max(0,Math.min(1,config.opacity)):1,strokeWidth:Number.isFinite(config?.strokeWidth)?Math.max(0.0025,Math.min(0.02,config.strokeWidth)):0.006};inkCanvas.style.pointerEvents=inkConfig.enabled?'auto':'none';if(!inkConfig.enabled)cancelGesture();}, invalidateLayout:rebuildAndSchedule, hit:function(x,y){return hitRegions.some(function(item){return item.rects.some(function(r){return x>=r.left && x<=r.right && y>=r.top && y<=r.bottom;});});}, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;cancelGesture();if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',handleResize);win.removeEventListener('scroll',schedule);win.removeEventListener('blur',pointerCancel);doc.removeEventListener('load',handleDocumentLoad,true);doc.removeEventListener('click',tapMarkup);inkCanvas.removeEventListener('pointerdown',pointerDown);inkCanvas.removeEventListener('pointermove',pointerMove);inkCanvas.removeEventListener('pointerup',pointerUp);inkCanvas.removeEventListener('pointercancel',pointerCancel);inkCanvas.removeEventListener('lostpointercapture',pointerCancel);inkCanvas.remove();overlay.remove();doc.body.style.position=originalPosition;registry.clear();inkRegistry.clear();rangeCache.clear();}};
20700
20965
  }
20701
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20966
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20702
20967
  });
20703
20968
 
20704
20969
  /* @papyrus-epub-annotations:end */
@@ -20714,6 +20979,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20714
20979
  let annotationDocumentId;
20715
20980
  let annotationLabels = {};
20716
20981
  let epubAnnotations = [];
20982
+ let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
20717
20983
  const epubAnnotationRenderers = new Map();
20718
20984
  let spineItems = [];
20719
20985
  let textPages = [];
@@ -21828,11 +22094,14 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21828
22094
  if (href && window.PapyrusEpubAnnotations) {
21829
22095
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21830
22096
  href, get documentId() { return annotationDocumentId; },
22097
+ spineIndex:contents.sectionIndex,
21831
22098
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21832
22099
  pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21833
22100
  annotations: () => epubAnnotations, labels: () => annotationLabels,
22101
+ onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
21834
22102
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21835
22103
  });
22104
+ renderer.setInkMode(epubInkMode);
21836
22105
  epubAnnotationRenderers.set(contents,renderer);
21837
22106
  }
21838
22107
  const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
@@ -21924,7 +22193,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21924
22193
  sendEvent('EPUB_TEXT_SELECTED', payload);
21925
22194
  });
21926
22195
  ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21927
- if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22196
+ if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
21928
22197
  }));
21929
22198
  rendition.on('relocated', (location) => {
21930
22199
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
@@ -22221,6 +22490,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22221
22490
  documentSessionId = payload.documentSessionId || null;
22222
22491
  epubAnnotations = [];
22223
22492
  annotationDocumentId = undefined;
22493
+ epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
22224
22494
  zoom = 1.0;
22225
22495
  epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22226
22496
  epubLocation = null;
@@ -22352,6 +22622,12 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22352
22622
  }
22353
22623
  sendResponse(id,true,null);return;
22354
22624
  }
22625
+ if (kind === 'set-epub-ink-mode') {
22626
+ if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
22627
+ epubInkMode={enabled:payload.enabled===true,preset:['ink','highlight','underline'].indexOf(payload.preset)>=0?payload.preset:'ink',color:/^#[0-9a-f]{6}$/i.test(payload.color||'')?payload.color:'#2563eb',opacity:Number.isFinite(payload.opacity)?Math.max(0,Math.min(1,payload.opacity)):1,strokeWidth:Number.isFinite(payload.strokeWidth)?Math.max(0.0025,Math.min(0.02,payload.strokeWidth)):0.006};
22628
+ epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
22629
+ sendResponse(id,true,null);return;
22630
+ }
22355
22631
  if(kind === 'epub-go-to-location'){
22356
22632
  if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22357
22633
  epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
@@ -26144,8 +26420,14 @@ var parseWebViewInteraction = (raw) => {
26144
26420
 
26145
26421
  // components/epubAnnotationEvents.ts
26146
26422
  var import_core3 = require("@papyrus-sdk/core");
26423
+ var EPUB_INK_PRESETS = ["ink", "highlight", "underline"];
26424
+ function resolveEpubInkMode(input) {
26425
+ if (!input.isLoaded || input.documentType !== "epub") return null;
26426
+ const preset = EPUB_INK_PRESETS.includes(input.preset) ? input.preset : "ink";
26427
+ return { enabled: input.activeTool === "ink" && EPUB_INK_PRESETS.includes(input.preset), preset, color: input.color, opacity: input.opacity, strokeWidth: input.strokeWidth };
26428
+ }
26147
26429
  function parseEpubAnnotationEvent(raw, session, documentId) {
26148
- if (!session || raw.length > 2e5) return null;
26430
+ if (!session || raw.length > 75e4) return null;
26149
26431
  let message;
26150
26432
  try {
26151
26433
  message = JSON.parse(raw);
@@ -26155,6 +26437,13 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
26155
26437
  const p = message?.payload;
26156
26438
  if (message?.type !== "event" || !p || p.documentSessionId !== session) return null;
26157
26439
  if (message.name === "EPUB_ANNOTATION_TAP") return typeof p.id === "string" && p.id.length <= 256 ? { kind: "tap", id: p.id, ids: Array.isArray(p.ids) && p.ids.length <= 100 && p.ids.every((id) => typeof id === "string" && id.length <= 256) ? p.ids : void 0 } : null;
26440
+ if (message.name === "EPUB_INK_CREATED") {
26441
+ const a = p.annotation, sidecar = a?.epubInk, first = sidecar?.segments?.[0], point = first?.points?.[0];
26442
+ if (!a || a.type !== "ink" || typeof a.id !== "string" || !a.id || a.id.length > 256 || !Number.isSafeInteger(a.pageIndex) || a.pageIndex < 1 || !(0, import_core3.validateAnnotationAnchor)(a.anchor) || a.anchor.kind !== "epub-cfi" || !(0, import_core3.validateEpubInkData)(sidecar) || !point || a.anchor.cfiRange !== point.cfiRange || a.anchor.quote !== point.quote || a.anchor.href !== first.href || documentId && (sidecar.documentId && sidecar.documentId !== documentId || a.anchor.documentId && a.anchor.documentId !== documentId) || sidecar.documentId && a.anchor.documentId && sidecar.documentId !== a.anchor.documentId) return null;
26443
+ const rect = a.rect, path = a.path;
26444
+ if (!rect || ![rect.x, rect.y, rect.width, rect.height].every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 1) || rect.x + rect.width > 1.000001 || rect.y + rect.height > 1.000001 || !Array.isArray(path) || path.length < 2 || path.length > 512 || path.some((p2) => !p2 || ![p2.x, p2.y].every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 1)) || typeof a.color !== "string" || !/^#[0-9a-f]{6}$/i.test(a.color) || !Number.isFinite(a.createdAt) || a.opacity !== void 0 && (!Number.isFinite(a.opacity) || a.opacity < 0 || a.opacity > 1) || a.strokeWidth !== void 0 && (!Number.isFinite(a.strokeWidth) || a.strokeWidth < 25e-4 || a.strokeWidth > 0.02)) return null;
26445
+ return { kind: "ink", session, annotation: a };
26446
+ }
26158
26447
  if (message.name !== "EPUB_TEXT_SELECTED" || !(0, import_core3.validateAnnotationAnchor)(p.anchor) || p.anchor.kind !== "epub-cfi" || p.text !== p.anchor.quote || !Number.isSafeInteger(p.pageIndex) || p.pageIndex < 1) return null;
26159
26448
  if (documentId && p.anchor.documentId && p.anchor.documentId !== documentId) return null;
26160
26449
  return { kind: "selection", session, text: p.text, pageIndex: p.pageIndex, anchor: { ...p.anchor, documentId: documentId ?? p.anchor.documentId } };
@@ -26191,7 +26480,7 @@ var WebViewViewer = ({
26191
26480
  }) => {
26192
26481
  const webViewRef = (0, import_react4.useRef)(null);
26193
26482
  const insets = usePapyrusSafeAreaInsets();
26194
- const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26483
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, inkStrokeWidth, activeTool, activeDrawToolPreset, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26195
26484
  const [selection, setSelection] = (0, import_react4.useState)(null);
26196
26485
  const t = getStrings(locale);
26197
26486
  const bridgeEngine = engine;
@@ -26276,6 +26565,15 @@ var WebViewViewer = ({
26276
26565
  if (!isLoaded) return;
26277
26566
  webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26278
26567
  }, [isLoaded, epubReadingMode, bridgeEngine]);
26568
+ (0, import_react4.useEffect)(() => {
26569
+ if (!isLoaded || documentType !== "epub") return;
26570
+ const inkMode = resolveEpubInkMode({ documentType, isLoaded, activeTool, preset: activeDrawToolPreset, color: annotationColor, opacity: annotationOpacity, strokeWidth: inkStrokeWidth });
26571
+ if (!inkMode) return;
26572
+ webViewRef.current?.postMessage(JSON.stringify({ id: `epub-ink-mode-${Date.now()}`, kind: "set-epub-ink-mode", payload: {
26573
+ ...inkMode,
26574
+ documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.()
26575
+ } }));
26576
+ }, [isLoaded, documentType, activeTool, activeDrawToolPreset, annotationColor, annotationOpacity, inkStrokeWidth, bridgeEngine]);
26279
26577
  const sendSelectionCommand = (key) => {
26280
26578
  webViewRef.current?.postMessage(JSON.stringify({
26281
26579
  type: "epub-selection-action",
@@ -26335,6 +26633,10 @@ var WebViewViewer = ({
26335
26633
  else setSelectedAnnotation(annotationEvent.id);
26336
26634
  return;
26337
26635
  }
26636
+ if (annotationEvent?.kind === "ink" && isLoaded && annotationEvent.session === bridgeEngine.getWebViewDocumentSessionId?.()) {
26637
+ addAnnotation(annotationEvent.annotation);
26638
+ setSelection(null);
26639
+ }
26338
26640
  const interaction = parseWebViewInteraction(raw);
26339
26641
  if (interaction?.kind === "scroll") onScrollOffset?.(interaction.offsetY);
26340
26642
  if (interaction?.kind === "tap") onTap?.();
@@ -32903,7 +33205,7 @@ function buildBottomBarLayout({
32903
33205
  toolDockOpen
32904
33206
  }) {
32905
33207
  const leftSlots = [];
32906
- if (documentType === "pdf") {
33208
+ if (documentType === "pdf" || documentType === "epub") {
32907
33209
  leftSlots.push({
32908
33210
  key: "annotate",
32909
33211
  active: toolDockOpen || activeMobileDestination === "annotate"