@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/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +829 -236
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +843 -249
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -8
- package/runtime/epubAnnotations.js +276 -8
- package/runtime/epubReader.js +58 -0
- package/runtime/index.html +505 -84
- package/runtime/runtime.js +505 -84
- package/runtime/syncComicRuntime.mjs +6 -3
- package/runtime/comicRuntime.test.ts +0 -241
- package/runtime/documentLoadRuntime.test.ts +0 -172
- package/runtime/epubAnnotations.test.ts +0 -31
- package/runtime/epubScrollDiagnostics.test.ts +0 -107
- package/runtime/epubSelectionMenu.test.ts +0 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papyrus-sdk/ui-react-native",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
46
|
-
"@papyrus-sdk/engine-native": "0.2.
|
|
47
|
-
"@papyrus-sdk/types": "0.2.
|
|
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/
|
|
70
|
-
"@papyrus-sdk/
|
|
71
|
-
"@papyrus-sdk/
|
|
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
|
-
|
|
91
|
-
var
|
|
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(
|
|
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',
|
|
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',
|
|
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);
|