@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papyrus-sdk/ui-react-native",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "license": "MIT",
5
5
  "keywords": [
6
6
  "papyrus",
@@ -25,7 +25,9 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "runtime"
28
+ "runtime",
29
+ "!runtime/**/*.test.*",
30
+ "!runtime/**/*.bench.*"
29
31
  ],
30
32
  "publishConfig": {
31
33
  "access": "public"
@@ -42,9 +44,9 @@
42
44
  "react-native-svg": "^15.5.0"
43
45
  },
44
46
  "peerDependencies": {
45
- "@papyrus-sdk/core": "0.2.23",
46
- "@papyrus-sdk/engine-native": "0.2.22",
47
- "@papyrus-sdk/types": "0.2.16",
47
+ "@papyrus-sdk/core": "0.2.24",
48
+ "@papyrus-sdk/engine-native": "0.2.23",
49
+ "@papyrus-sdk/types": "0.2.17",
48
50
  "@react-native-clipboard/clipboard": "^1.16.3",
49
51
  "react": ">=18.2.0 <20",
50
52
  "react-native": ">=0.81.0",
@@ -66,9 +68,9 @@
66
68
  "react-native-gesture-handler": "~2.28.0",
67
69
  "react-native-safe-area-context": "^5.6.2",
68
70
  "react-native-reanimated": "3.19.5",
69
- "@papyrus-sdk/core": "0.2.23",
70
- "@papyrus-sdk/engine-native": "0.2.22",
71
- "@papyrus-sdk/types": "0.2.16"
71
+ "@papyrus-sdk/core": "0.2.24",
72
+ "@papyrus-sdk/engine-native": "0.2.23",
73
+ "@papyrus-sdk/types": "0.2.17"
72
74
  },
73
75
  "scripts": {
74
76
  "build": "node runtime/syncComicRuntime.mjs && tsup",
@@ -8,6 +8,84 @@
8
8
  typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
9
9
  typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
10
10
  }
11
+ function simplifyPath(points, tolerance, maxPoints) {
12
+ if (!Array.isArray(points) || points.length <= 2) return Array.isArray(points) ? points.slice() : [];
13
+ tolerance = Number.isFinite(tolerance) ? Math.max(0, tolerance) : 1.5;
14
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
15
+ var keep = new Uint8Array(points.length); keep[0] = 1; keep[points.length - 1] = 1;
16
+ var stack = [[0, points.length - 1]], toleranceSquared = tolerance * tolerance;
17
+ while (stack.length) {
18
+ var interval = stack.pop(), first = interval[0], last = interval[1], a = points[first], b = points[last];
19
+ var dx = b.x - a.x, dy = b.y - a.y, lengthSquared = dx * dx + dy * dy, maxDistance = toleranceSquared, split = -1;
20
+ for (var i = first + 1; i < last; i++) {
21
+ var p = points[i], ratio = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
22
+ var px = a.x + ratio * dx, py = a.y + ratio * dy, ox = p.x - px, oy = p.y - py, distance = ox * ox + oy * oy;
23
+ if (distance > maxDistance) { maxDistance = distance; split = i; }
24
+ }
25
+ if (split >= 0) { keep[split] = 1; stack.push([first, split], [split, last]); }
26
+ }
27
+ var result = []; for (var j = 0; j < points.length; j++) if (keep[j]) result.push(points[j]);
28
+ if (result.length <= maxPoints) return result;
29
+ var capped = []; for (var k = 0; k < maxPoints; k++) capped.push(result[Math.round(k * (result.length - 1) / (maxPoints - 1))]);
30
+ return capped;
31
+ }
32
+ function compactGesturePoints(points, maxPoints) {
33
+ if (!Array.isArray(points)) return [];
34
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 1024;
35
+ if (points.length <= maxPoints) return points.slice();
36
+ var compacted = [];
37
+ for (var i = 0; i < maxPoints; i++) compacted.push(points[Math.round(i * (points.length - 1) / (maxPoints - 1))]);
38
+ return compacted;
39
+ }
40
+ function densifyPath(points, maxStep, maxPoints) {
41
+ if (!Array.isArray(points) || !points.length) return [];
42
+ if (points.length === 1) return points.slice();
43
+ maxStep = Number.isFinite(maxStep) ? Math.max(1, maxStep) : 12;
44
+ maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
45
+ var cumulative = [0], total = 0;
46
+ for (var i = 1; i < points.length; i++) {
47
+ if (![points[i-1].x,points[i-1].y,points[i].x,points[i].y].every(Number.isFinite)) return [];
48
+ total += Math.hypot(points[i].x-points[i-1].x, points[i].y-points[i-1].y);
49
+ cumulative.push(total);
50
+ }
51
+ if (!total) return [points[0], points[points.length-1]];
52
+ var intervals = Math.min(maxPoints-1, Math.max(1, Math.ceil(total/maxStep))), result = [], segment = 1;
53
+ for (var sample = 0; sample <= intervals; sample++) {
54
+ var distance = total*sample/intervals;
55
+ while (segment < cumulative.length-1 && cumulative[segment] < distance) segment++;
56
+ var start = points[segment-1], end = points[segment], span = cumulative[segment]-cumulative[segment-1];
57
+ var ratio = span ? (distance-cumulative[segment-1])/span : 0;
58
+ result.push({x:start.x+(end.x-start.x)*ratio,y:start.y+(end.y-start.y)*ratio});
59
+ }
60
+ return result;
61
+ }
62
+ function createSpatialIndex(entries, cellSize) {
63
+ cellSize = Number.isFinite(cellSize) ? Math.max(8, cellSize) : 64;
64
+ var cells = new Map();
65
+ entries.forEach(function(entry) {
66
+ var left = Math.floor(entry.left / cellSize), right = Math.floor(entry.right / cellSize);
67
+ var top = Math.floor(entry.top / cellSize), bottom = Math.floor(entry.bottom / cellSize);
68
+ for (var x = left; x <= right; x++) for (var y = top; y <= bottom; y++) {
69
+ var key = x + ':' + y, bucket = cells.get(key); if (!bucket) { bucket = []; cells.set(key, bucket); } bucket.push(entry);
70
+ }
71
+ });
72
+ return {cells:cells,cellSize:cellSize};
73
+ }
74
+ function queryNearestTextRect(index, x, y, maxDistance) {
75
+ if (!index || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(maxDistance) || maxDistance < 0) return null;
76
+ var size = index.cellSize, minX = Math.floor((x-maxDistance)/size), maxX = Math.floor((x+maxDistance)/size);
77
+ var minY = Math.floor((y-maxDistance)/size), maxY = Math.floor((y+maxDistance)/size), seen = new Set(), best = null;
78
+ for (var cx=minX; cx<=maxX; cx++) for (var cy=minY; cy<=maxY; cy++) {
79
+ var bucket = index.cells.get(cx+':'+cy); if (!bucket) continue;
80
+ bucket.forEach(function(entry) {
81
+ if (seen.has(entry)) return; seen.add(entry);
82
+ var nearestX = Math.max(entry.left,Math.min(entry.right,x)), nearestY = Math.max(entry.top,Math.min(entry.bottom,y));
83
+ var dx=x-nearestX,dy=y-nearestY,distance=Math.sqrt(dx*dx+dy*dy);
84
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={entry:entry,distance:distance,x:nearestX,y:nearestY};
85
+ });
86
+ }
87
+ return best;
88
+ }
11
89
  function textNodes(document) {
12
90
  var walker = document.createTreeWalker(document.body, 4);
13
91
  var nodes = [], text = '', node;
@@ -57,13 +135,196 @@
57
135
  if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
58
136
  doc.body.appendChild(overlay);
59
137
  var registry = new Map(), hitRegions = [];
138
+ var inkRegistry = new Map(), rangeCache = new Map(), spatialIndex = null, activeGesture = null, inkConfig = {enabled:false,preset:'ink'};
139
+ var inkSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
140
+ inkSvg.setAttribute('data-papyrus-ink-overlay','');
141
+ inkSvg.style.cssText = 'position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1';
142
+ overlay.appendChild(inkSvg);
143
+ var inkCanvas = doc.createElement('canvas');
144
+ inkCanvas.dataset.papyrusInkCapture = '';
145
+ inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
146
+ doc.body.appendChild(inkCanvas);
147
+ var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
148
+ function resizeCanvas() {
149
+ var bodyRect=doc.body.getBoundingClientRect(), ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
150
+ var width=Math.max(1,Math.min(4096,doc.body.clientWidth||bodyRect.width||Math.min(4096,win.innerWidth||1)));
151
+ var height=Math.max(1,Math.min(4096,doc.body.clientHeight||bodyRect.height||Math.min(4096,win.innerHeight||1)));
152
+ inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
153
+ inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
154
+ if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
155
+ }
156
+ function collectTextRects() {
157
+ var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
158
+ while ((node=walker.nextNode())) {
159
+ var parent=node.parentElement;
160
+ if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
161
+ var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
162
+ for (var start=0; start<text.length;) {
163
+ var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
164
+ if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
165
+ var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
166
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
167
+ rects.forEach(function(rect) {
168
+ if (!(rect.width>0 && rect.height>0)) return;
169
+ 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,
170
+ node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
171
+ });
172
+ start=end;
173
+ }
174
+ }
175
+ return entries;
176
+ }
177
+ function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
178
+ function inkViewportExtent() {
179
+ var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
180
+ var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
181
+ var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
182
+ return Math.max(1,Math.min(4096,width),Math.min(4096,height));
183
+ }
184
+ function inkStrokeWidthPx(width,preset) {
185
+ var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
186
+ return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
187
+ }
188
+ function closestGlyph(entry,x,y,maxDistance) {
189
+ var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
190
+ for (var glyph of source) {
191
+ var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
192
+ var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
193
+ rects.forEach(function(rect) {
194
+ if (!(rect.width>0 && rect.height>0)) return;
195
+ 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));
196
+ var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
197
+ if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
198
+ });
199
+ }
200
+ if (!best || typeof contents.cfiFromRange!=='function') return null;
201
+ var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
202
+ if (!quote || typeof cfiRange!=='string' || !/^epubcfi\([^\n<>]+\)$/.test(cfiRange)) return null;
203
+ var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
204
+ return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
205
+ }
206
+ function renderInkAnnotation(annotation,keep,bodyRect) {
207
+ var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
208
+ var data=annotation.epubInk;
209
+ if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
210
+ keep.add(annotation.id);
211
+ var group=inkRegistry.get(annotation.id);
212
+ 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); }
213
+ group.replaceChildren();
214
+ var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
215
+ var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
216
+ var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
217
+ var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
218
+ data.segments.forEach(function(segment) {
219
+ if (segment.href!==options.href || !Array.isArray(segment.points)) return;
220
+ var paths=[], current=[];
221
+ segment.points.forEach(function(point) {
222
+ var range=rangeCache.get(point.cfiRange);
223
+ if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
224
+ if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
225
+ var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
226
+ if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
227
+ var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
228
+ var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
229
+ if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
230
+ current.push({x:x,y:y,lineHeight:lineHeight});
231
+ });
232
+ if(current.length>1)paths.push(current);
233
+ paths.forEach(function(points) {
234
+ var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
235
+ 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);}
236
+ var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
237
+ 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);
238
+ });
239
+ });
240
+ }
241
+ function clearPreview() {
242
+ if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
243
+ inkSvg.querySelectorAll('[data-papyrus-ink-preview]').forEach(function(path){path.remove();});
244
+ }
245
+ function createPreviewPath(x,y) {
246
+ var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
247
+ path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
248
+ path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
249
+ path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
250
+ path.style.pointerEvents='none';inkSvg.appendChild(path);return path;
251
+ }
252
+ function updatePreviewPath(clientX,clientY) {
253
+ var bodyRect=doc.body.getBoundingClientRect(),x=clientX-bodyRect.left,y=clientY-bodyRect.top;
254
+ var gesture=activeGesture;
255
+ if(!gesture.previewPath||gesture.previewPathPoints>=32){
256
+ gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
257
+ gesture.previewPathPoints=1;
258
+ }
259
+ var pathData=gesture.previewPath.getAttribute('d')||'';
260
+ gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
261
+ gesture.previewPathPoints++;
262
+ gesture.previewLast={x:x,y:y};
263
+ }
264
+ function cancelGesture() {
265
+ var gesture=activeGesture; activeGesture=null; clearPreview();
266
+ if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
267
+ }
268
+ function commitGesture() {
269
+ var gesture=activeGesture; activeGesture=null; clearPreview();
270
+ if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
271
+ var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
272
+ anchorSamples.forEach(function(point) {
273
+ var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
274
+ var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
275
+ var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
276
+ if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
277
+ current.push(anchored);
278
+ });
279
+ if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
280
+ segments=segments.slice(0,64);
281
+ var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
282
+ if(!pointCount)return;
283
+ var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
284
+ 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))};});
285
+ 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;}));
286
+ 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;}));
287
+ var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
288
+ var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
289
+ 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,
290
+ anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
291
+ epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
292
+ if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
293
+ }
294
+ function pointerDown(event) {
295
+ if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
296
+ if(activeGesture){cancelGesture();return;}
297
+ event.preventDefault();event.stopPropagation();
298
+ 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};
299
+ updatePreviewPath(event.clientX,event.clientY);
300
+ try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
301
+ }
302
+ function pointerMove(event) {
303
+ if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
304
+ if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
305
+ event.preventDefault();event.stopPropagation();
306
+ var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
307
+ if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
308
+ 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();}
309
+ updatePreviewPath(event.clientX,event.clientY);
310
+ activeGesture.last={x:event.clientX,y:event.clientY};
311
+ }
312
+ 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();}
313
+ function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
314
+ resizeCanvas();rebuildSpatialIndex();
315
+ inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
316
+ win.addEventListener('blur',pointerCancel);win.addEventListener('resize',resizeCanvas);
60
317
  function render() {
61
318
  frame = 0; if (!alive || !options.isCurrent()) return;
62
- var keep = new Set(), noteGroups = new Map();
319
+ var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
63
320
  hitRegions = [];
321
+ var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
322
+ inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
323
+ inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
64
324
  options.annotations().forEach(function(annotation) {
65
325
  var anchor = annotation.anchor;
66
326
  if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
327
+ if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
67
328
  var range = resolveRange(contents,anchor); if (!range) return;
68
329
  var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
69
330
  if (!rects.length) return;
@@ -102,20 +363,24 @@
102
363
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
103
364
  });
104
365
  registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
366
+ inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
105
367
  }
106
368
  function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
369
+ function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
370
+ function handleResize(){resizeCanvas();rebuildAndSchedule();}
371
+ function handleDocumentLoad(){rebuildAndSchedule();}
107
372
  function tapMarkup(event) {
108
373
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
109
374
  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;});});
110
375
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
111
376
  }
112
377
  doc.addEventListener('click',tapMarkup);
113
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
378
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
114
379
  if (observer) observer.observe(doc.body);
115
- win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
116
- if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
380
+ win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
381
+ if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
117
382
  schedule();
118
- 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();}};
383
+ 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();}};
119
384
  }
120
- return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
385
+ return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
121
386
  });
@@ -44,7 +44,7 @@
44
44
  var doc=contents.document,win=contents.window,start=null,blocked=false;
45
45
  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;}
46
46
  function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
47
- function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
47
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay],[data-papyrus-ink-capture]'));}
48
48
  function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
49
49
  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();}
50
50
  function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}