@papyrus-sdk/ui-react-native 0.2.35-beta.9 → 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-beta.9",
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-beta.3",
46
- "@papyrus-sdk/engine-native": "0.2.22-beta.7",
47
- "@papyrus-sdk/types": "0.2.16-beta.3",
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/types": "0.2.16-beta.3",
70
- "@papyrus-sdk/core": "0.2.23-beta.3",
71
- "@papyrus-sdk/engine-native": "0.2.22-beta.7"
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;
@@ -75,6 +336,7 @@
75
336
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
76
337
  var style = annotation.markupStyle || annotation.type;
77
338
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
339
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
78
340
  rects.forEach(function(rect) {
79
341
  var mark = doc.createElement('div');
80
342
  var bodyRect=doc.body.getBoundingClientRect();
@@ -87,8 +349,10 @@
87
349
  if (hasNote) {
88
350
  var last = rects[rects.length-1], button = doc.createElement('button');
89
351
  button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
90
- button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+Math.max(0,Math.min(doc.documentElement.clientWidth-44,last.right-doc.body.getBoundingClientRect().left))+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
91
- var key = Math.floor(last.top/44);
352
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
353
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
354
+ button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+((column+1)*pageWidth-44)+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
355
+ var key = column+":"+Math.floor(last.top/44);
92
356
  var neighbors=noteGroups.get(key);
93
357
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
94
358
  neighbors.ids.push(annotation.id);
@@ -99,20 +363,24 @@
99
363
  group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
100
364
  });
101
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);}});
102
367
  }
103
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();}
104
372
  function tapMarkup(event) {
105
373
  if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
106
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;});});
107
375
  if(hit) {event.stopPropagation();options.onTap(hit.id);}
108
376
  }
109
377
  doc.addEventListener('click',tapMarkup);
110
- var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
378
+ var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
111
379
  if (observer) observer.observe(doc.body);
112
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
113
- 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();}});
114
382
  schedule();
115
- return {schedule:schedule, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',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();}};
116
384
  }
117
- 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};
118
386
  });
@@ -0,0 +1,58 @@
1
+ (function(root){
2
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
3
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\([^\n<>]+\)$/.test(value);}
4
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
5
+ function gesture(start,end,state){
6
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
7
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
8
+ if(state.mode==='paged' && Math.abs(dx)>=48 && Math.abs(dx)>Math.abs(dy)*1.5 && elapsed>=0 && elapsed<1500)return (dx<0)!==Boolean(state.rtl)?'next':'prev';
9
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
10
+ return null;
11
+ }
12
+ function location(raw,count,readingMode,geometry){
13
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
14
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
15
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
16
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
17
+ var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
18
+ // Chromium may round a column offset down by a fraction of a CSS pixel.
19
+ // Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
20
+ if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
21
+ var boundary=Math.round(geometry.offset/geometry.pageWidth);
22
+ if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
23
+ visualPage=Math.min(total,Math.max(1,boundary+1));
24
+ atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
25
+ atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
26
+ }
27
+ }
28
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
29
+ return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
30
+ }
31
+ function installResizeAnchor(manager,readCfi){
32
+ var original=manager.resize,anchor=null;
33
+ function resize(width,height,target){
34
+ if(validCfi(target))anchor=target;
35
+ if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
36
+ // epub.js accepts a logical target as its third argument. Keep it across
37
+ // keyboard/dialog reflows instead of adopting each new viewport start.
38
+ return original.call(this,width,height,anchor || target);
39
+ }
40
+ manager.resize=resize;
41
+ return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
42
+ }
43
+ function install(contents,config){
44
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
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
+ 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],[data-papyrus-ink-capture]'));}
48
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
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
+ function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
51
+ function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
52
+ function cancel(){start=null;blocked=true;}
53
+ function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
54
+ doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
55
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
56
+ }
57
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
58
+ })(typeof window==='undefined'?globalThis:window);