@papyrus-sdk/ui-react-native 0.2.35 → 0.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +313 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -8
- package/runtime/epubAnnotations.js +271 -6
- package/runtime/epubReader.js +1 -1
- package/runtime/index.html +284 -8
- package/runtime/runtime.js +284 -8
- package/runtime/comicRuntime.test.ts +0 -241
- package/runtime/documentLoadRuntime.test.ts +0 -172
- package/runtime/epubAnnotations.test.ts +0 -35
- package/runtime/epubReader.test.ts +0 -74
- package/runtime/epubRuntime.test.ts +0 -48
- package/runtime/epubScrollDiagnostics.test.ts +0 -107
- package/runtime/epubSelectionMenu.test.ts +0 -39
package/runtime/runtime.js
CHANGED
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
var doc=contents.document,win=contents.window,start=null,blocked=false;
|
|
157
157
|
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;}
|
|
158
158
|
function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
|
|
159
|
-
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
|
|
159
|
+
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay],[data-papyrus-ink-capture]'));}
|
|
160
160
|
function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
|
|
161
161
|
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();}
|
|
162
162
|
function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
|
|
@@ -181,6 +181,84 @@
|
|
|
181
181
|
typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
|
|
182
182
|
typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
|
|
183
183
|
}
|
|
184
|
+
function simplifyPath(points, tolerance, maxPoints) {
|
|
185
|
+
if (!Array.isArray(points) || points.length <= 2) return Array.isArray(points) ? points.slice() : [];
|
|
186
|
+
tolerance = Number.isFinite(tolerance) ? Math.max(0, tolerance) : 1.5;
|
|
187
|
+
maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
|
|
188
|
+
var keep = new Uint8Array(points.length); keep[0] = 1; keep[points.length - 1] = 1;
|
|
189
|
+
var stack = [[0, points.length - 1]], toleranceSquared = tolerance * tolerance;
|
|
190
|
+
while (stack.length) {
|
|
191
|
+
var interval = stack.pop(), first = interval[0], last = interval[1], a = points[first], b = points[last];
|
|
192
|
+
var dx = b.x - a.x, dy = b.y - a.y, lengthSquared = dx * dx + dy * dy, maxDistance = toleranceSquared, split = -1;
|
|
193
|
+
for (var i = first + 1; i < last; i++) {
|
|
194
|
+
var p = points[i], ratio = lengthSquared ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared)) : 0;
|
|
195
|
+
var px = a.x + ratio * dx, py = a.y + ratio * dy, ox = p.x - px, oy = p.y - py, distance = ox * ox + oy * oy;
|
|
196
|
+
if (distance > maxDistance) { maxDistance = distance; split = i; }
|
|
197
|
+
}
|
|
198
|
+
if (split >= 0) { keep[split] = 1; stack.push([first, split], [split, last]); }
|
|
199
|
+
}
|
|
200
|
+
var result = []; for (var j = 0; j < points.length; j++) if (keep[j]) result.push(points[j]);
|
|
201
|
+
if (result.length <= maxPoints) return result;
|
|
202
|
+
var capped = []; for (var k = 0; k < maxPoints; k++) capped.push(result[Math.round(k * (result.length - 1) / (maxPoints - 1))]);
|
|
203
|
+
return capped;
|
|
204
|
+
}
|
|
205
|
+
function compactGesturePoints(points, maxPoints) {
|
|
206
|
+
if (!Array.isArray(points)) return [];
|
|
207
|
+
maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 1024;
|
|
208
|
+
if (points.length <= maxPoints) return points.slice();
|
|
209
|
+
var compacted = [];
|
|
210
|
+
for (var i = 0; i < maxPoints; i++) compacted.push(points[Math.round(i * (points.length - 1) / (maxPoints - 1))]);
|
|
211
|
+
return compacted;
|
|
212
|
+
}
|
|
213
|
+
function densifyPath(points, maxStep, maxPoints) {
|
|
214
|
+
if (!Array.isArray(points) || !points.length) return [];
|
|
215
|
+
if (points.length === 1) return points.slice();
|
|
216
|
+
maxStep = Number.isFinite(maxStep) ? Math.max(1, maxStep) : 12;
|
|
217
|
+
maxPoints = Number.isSafeInteger(maxPoints) ? Math.max(2, maxPoints) : 512;
|
|
218
|
+
var cumulative = [0], total = 0;
|
|
219
|
+
for (var i = 1; i < points.length; i++) {
|
|
220
|
+
if (![points[i-1].x,points[i-1].y,points[i].x,points[i].y].every(Number.isFinite)) return [];
|
|
221
|
+
total += Math.hypot(points[i].x-points[i-1].x, points[i].y-points[i-1].y);
|
|
222
|
+
cumulative.push(total);
|
|
223
|
+
}
|
|
224
|
+
if (!total) return [points[0], points[points.length-1]];
|
|
225
|
+
var intervals = Math.min(maxPoints-1, Math.max(1, Math.ceil(total/maxStep))), result = [], segment = 1;
|
|
226
|
+
for (var sample = 0; sample <= intervals; sample++) {
|
|
227
|
+
var distance = total*sample/intervals;
|
|
228
|
+
while (segment < cumulative.length-1 && cumulative[segment] < distance) segment++;
|
|
229
|
+
var start = points[segment-1], end = points[segment], span = cumulative[segment]-cumulative[segment-1];
|
|
230
|
+
var ratio = span ? (distance-cumulative[segment-1])/span : 0;
|
|
231
|
+
result.push({x:start.x+(end.x-start.x)*ratio,y:start.y+(end.y-start.y)*ratio});
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
function createSpatialIndex(entries, cellSize) {
|
|
236
|
+
cellSize = Number.isFinite(cellSize) ? Math.max(8, cellSize) : 64;
|
|
237
|
+
var cells = new Map();
|
|
238
|
+
entries.forEach(function(entry) {
|
|
239
|
+
var left = Math.floor(entry.left / cellSize), right = Math.floor(entry.right / cellSize);
|
|
240
|
+
var top = Math.floor(entry.top / cellSize), bottom = Math.floor(entry.bottom / cellSize);
|
|
241
|
+
for (var x = left; x <= right; x++) for (var y = top; y <= bottom; y++) {
|
|
242
|
+
var key = x + ':' + y, bucket = cells.get(key); if (!bucket) { bucket = []; cells.set(key, bucket); } bucket.push(entry);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return {cells:cells,cellSize:cellSize};
|
|
246
|
+
}
|
|
247
|
+
function queryNearestTextRect(index, x, y, maxDistance) {
|
|
248
|
+
if (!index || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(maxDistance) || maxDistance < 0) return null;
|
|
249
|
+
var size = index.cellSize, minX = Math.floor((x-maxDistance)/size), maxX = Math.floor((x+maxDistance)/size);
|
|
250
|
+
var minY = Math.floor((y-maxDistance)/size), maxY = Math.floor((y+maxDistance)/size), seen = new Set(), best = null;
|
|
251
|
+
for (var cx=minX; cx<=maxX; cx++) for (var cy=minY; cy<=maxY; cy++) {
|
|
252
|
+
var bucket = index.cells.get(cx+':'+cy); if (!bucket) continue;
|
|
253
|
+
bucket.forEach(function(entry) {
|
|
254
|
+
if (seen.has(entry)) return; seen.add(entry);
|
|
255
|
+
var nearestX = Math.max(entry.left,Math.min(entry.right,x)), nearestY = Math.max(entry.top,Math.min(entry.bottom,y));
|
|
256
|
+
var dx=x-nearestX,dy=y-nearestY,distance=Math.sqrt(dx*dx+dy*dy);
|
|
257
|
+
if (distance<=maxDistance && (!best || distance<best.distance)) best={entry:entry,distance:distance,x:nearestX,y:nearestY};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return best;
|
|
261
|
+
}
|
|
184
262
|
function textNodes(document) {
|
|
185
263
|
var walker = document.createTreeWalker(document.body, 4);
|
|
186
264
|
var nodes = [], text = '', node;
|
|
@@ -230,13 +308,196 @@
|
|
|
230
308
|
if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
|
|
231
309
|
doc.body.appendChild(overlay);
|
|
232
310
|
var registry = new Map(), hitRegions = [];
|
|
311
|
+
var inkRegistry = new Map(), rangeCache = new Map(), spatialIndex = null, activeGesture = null, inkConfig = {enabled:false,preset:'ink'};
|
|
312
|
+
var inkSvg = doc.createElementNS('http://www.w3.org/2000/svg','svg');
|
|
313
|
+
inkSvg.setAttribute('data-papyrus-ink-overlay','');
|
|
314
|
+
inkSvg.style.cssText = 'position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1';
|
|
315
|
+
overlay.appendChild(inkSvg);
|
|
316
|
+
var inkCanvas = doc.createElement('canvas');
|
|
317
|
+
inkCanvas.dataset.papyrusInkCapture = '';
|
|
318
|
+
inkCanvas.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483000;background:transparent;pointer-events:none;touch-action:none';
|
|
319
|
+
doc.body.appendChild(inkCanvas);
|
|
320
|
+
var inkContext = null; try { if(typeof win.CanvasRenderingContext2D==='function')inkContext = inkCanvas.getContext('2d'); } catch (_) {}
|
|
321
|
+
function resizeCanvas() {
|
|
322
|
+
var bodyRect=doc.body.getBoundingClientRect(), ratio = Math.max(1,Math.min(3,win.devicePixelRatio || 1));
|
|
323
|
+
var width=Math.max(1,Math.min(4096,doc.body.clientWidth||bodyRect.width||Math.min(4096,win.innerWidth||1)));
|
|
324
|
+
var height=Math.max(1,Math.min(4096,doc.body.clientHeight||bodyRect.height||Math.min(4096,win.innerHeight||1)));
|
|
325
|
+
inkCanvas.style.width=width+'px';inkCanvas.style.height=height+'px';
|
|
326
|
+
inkCanvas.width = Math.round(width*ratio); inkCanvas.height = Math.round(height*ratio);
|
|
327
|
+
if (inkContext) { inkContext.setTransform(ratio,0,0,ratio,0,0); inkContext.clearRect(0,0,width,height); }
|
|
328
|
+
}
|
|
329
|
+
function collectTextRects() {
|
|
330
|
+
var walker = doc.createTreeWalker(doc.body,4), node, entries=[];
|
|
331
|
+
while ((node=walker.nextNode())) {
|
|
332
|
+
var parent=node.parentElement;
|
|
333
|
+
if (!parent || parent.closest('script,style,noscript,textarea,[data-papyrus-overlay],[data-papyrus-ink-capture]')) continue;
|
|
334
|
+
var text=node.nodeValue || '', style=win.getComputedStyle(parent), computed=parseFloat(style.lineHeight);
|
|
335
|
+
for (var start=0; start<text.length;) {
|
|
336
|
+
var end=Math.min(text.length,start+24), last=text.charCodeAt(end-1), next=text.charCodeAt(end);
|
|
337
|
+
if(end<text.length&&last>=0xD800&&last<=0xDBFF&&next>=0xDC00&&next<=0xDFFF)end++;
|
|
338
|
+
var range=doc.createRange(); range.setStart(node,start); range.setEnd(node,end);
|
|
339
|
+
var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
|
|
340
|
+
rects.forEach(function(rect) {
|
|
341
|
+
if (!(rect.width>0 && rect.height>0)) return;
|
|
342
|
+
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,
|
|
343
|
+
node:node,start:start,end:end,lineHeight:Number.isFinite(computed)&&computed>0?computed:rect.height});
|
|
344
|
+
});
|
|
345
|
+
start=end;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return entries;
|
|
349
|
+
}
|
|
350
|
+
function rebuildSpatialIndex() { spatialIndex=createSpatialIndex(collectTextRects(),64); rangeCache.clear(); }
|
|
351
|
+
function inkViewportExtent() {
|
|
352
|
+
var root=doc.documentElement,body=doc.body,bodyRect=body.getBoundingClientRect();
|
|
353
|
+
var width=root.clientWidth||body.clientWidth||bodyRect.width||win.innerWidth||1;
|
|
354
|
+
var height=root.clientHeight||body.clientHeight||bodyRect.height||win.innerHeight||1;
|
|
355
|
+
return Math.max(1,Math.min(4096,width),Math.min(4096,height));
|
|
356
|
+
}
|
|
357
|
+
function inkStrokeWidthPx(width,preset) {
|
|
358
|
+
var scale=preset==='highlight'?0.8:preset==='underline'?0.1:0.15;
|
|
359
|
+
return Math.max(1,Math.min(24,(Number.isFinite(width)?width:0.006)*inkViewportExtent()*scale));
|
|
360
|
+
}
|
|
361
|
+
function closestGlyph(entry,x,y,maxDistance) {
|
|
362
|
+
var source=(entry.node.nodeValue || '').slice(entry.start,entry.end), best=null, offset=0;
|
|
363
|
+
for (var glyph of source) {
|
|
364
|
+
var range=doc.createRange(); range.setStart(entry.node,entry.start+offset); range.setEnd(entry.node,entry.start+offset+glyph.length); offset+=glyph.length;
|
|
365
|
+
var rects=[]; try { rects=Array.from(range.getClientRects()); } catch (_) {}
|
|
366
|
+
rects.forEach(function(rect) {
|
|
367
|
+
if (!(rect.width>0 && rect.height>0)) return;
|
|
368
|
+
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));
|
|
369
|
+
var dx=x-nx,dy=y-ny,distance=Math.sqrt(dx*dx+dy*dy);
|
|
370
|
+
if (distance<=maxDistance && (!best || distance<best.distance)) best={range:range,rect:rect,distance:distance};
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (!best || typeof contents.cfiFromRange!=='function') return null;
|
|
374
|
+
var quote=best.range.toString(), cfiRange; try { cfiRange=contents.cfiFromRange(best.range); } catch (_) { return null; }
|
|
375
|
+
if (!quote || typeof cfiRange!=='string' || !/^epubcfi\([^\n<>]+\)$/.test(cfiRange)) return null;
|
|
376
|
+
var lineHeight=Math.max(1,entry.lineHeight || best.rect.height), px=x-(best.rect.left+win.scrollX), py=y-(best.rect.top+win.scrollY);
|
|
377
|
+
return {cfiRange:cfiRange,quote:quote,x:Math.max(-3,Math.min(3,px/lineHeight)),y:Math.max(-3,Math.min(3,py/lineHeight))};
|
|
378
|
+
}
|
|
379
|
+
function renderInkAnnotation(annotation,keep,bodyRect) {
|
|
380
|
+
var baseRect=bodyRect || doc.body.getBoundingClientRect() || {left:0,top:0};
|
|
381
|
+
var data=annotation.epubInk;
|
|
382
|
+
if (!data || data.version!==1 || data.kind!=='epub-ink' || !Array.isArray(data.segments)) return;
|
|
383
|
+
keep.add(annotation.id);
|
|
384
|
+
var group=inkRegistry.get(annotation.id);
|
|
385
|
+
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); }
|
|
386
|
+
group.replaceChildren();
|
|
387
|
+
var color=/^#[0-9a-f]{6}$/i.test(annotation.color)?annotation.color:'#2563eb';
|
|
388
|
+
var opacity=Number.isFinite(annotation.opacity)?Math.max(0,Math.min(1,annotation.opacity)):1;
|
|
389
|
+
var preset=['ink','highlight','underline'].indexOf(data.preset)>=0?data.preset:'ink';
|
|
390
|
+
var stroke=inkStrokeWidthPx(annotation.strokeWidth,preset);
|
|
391
|
+
data.segments.forEach(function(segment) {
|
|
392
|
+
if (segment.href!==options.href || !Array.isArray(segment.points)) return;
|
|
393
|
+
var paths=[], current=[];
|
|
394
|
+
segment.points.forEach(function(point) {
|
|
395
|
+
var range=rangeCache.get(point.cfiRange);
|
|
396
|
+
if (!range) { try { range=contents.range(point.cfiRange); } catch (_) { range=null; } if (range) rangeCache.set(point.cfiRange,range); }
|
|
397
|
+
if (!range || range.toString()!==point.quote) { if(current.length>1)paths.push(current);current=[];return; }
|
|
398
|
+
var rect; try { rect=range.getBoundingClientRect(); } catch (_) { rect=null; }
|
|
399
|
+
if (!rect || !(rect.width>0 && rect.height>0)) { if(current.length>1)paths.push(current);current=[];return; }
|
|
400
|
+
var lineHeight=Math.max(1,rect.height), x=rect.left-baseRect.left+point.x*lineHeight, y=rect.top-baseRect.top+point.y*lineHeight;
|
|
401
|
+
var previous=current[current.length-1], jumpLimit=Math.max(24,lineHeight*2.5);
|
|
402
|
+
if (previous && Math.hypot(x-previous.x,y-previous.y)>jumpLimit) { if(current.length>1)paths.push(current);current=[]; }
|
|
403
|
+
current.push({x:x,y:y,lineHeight:lineHeight});
|
|
404
|
+
});
|
|
405
|
+
if(current.length>1)paths.push(current);
|
|
406
|
+
paths.forEach(function(points) {
|
|
407
|
+
var d='M '+points[0].x.toFixed(1)+' '+points[0].y.toFixed(1);
|
|
408
|
+
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);}
|
|
409
|
+
var last=points[points.length-1];d+=' L '+last.x.toFixed(1)+' '+last.y.toFixed(1);
|
|
410
|
+
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);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function clearPreview() {
|
|
415
|
+
if(inkContext)inkContext.clearRect(0,0,inkCanvas.width,inkCanvas.height);
|
|
416
|
+
inkSvg.querySelectorAll('[data-papyrus-ink-preview]').forEach(function(path){path.remove();});
|
|
417
|
+
}
|
|
418
|
+
function createPreviewPath(x,y) {
|
|
419
|
+
var path=doc.createElementNS('http://www.w3.org/2000/svg','path');
|
|
420
|
+
path.setAttribute('data-papyrus-ink-preview','');path.setAttribute('d','M '+x.toFixed(1)+' '+y.toFixed(1));path.setAttribute('fill','none');
|
|
421
|
+
path.setAttribute('stroke',inkConfig.color);path.setAttribute('stroke-width',String(inkStrokeWidthPx(inkConfig.strokeWidth,inkConfig.preset)));
|
|
422
|
+
path.setAttribute('stroke-linecap','round');path.setAttribute('stroke-linejoin','round');path.setAttribute('opacity',String(inkConfig.opacity));
|
|
423
|
+
path.style.pointerEvents='none';inkSvg.appendChild(path);return path;
|
|
424
|
+
}
|
|
425
|
+
function updatePreviewPath(clientX,clientY) {
|
|
426
|
+
var bodyRect=doc.body.getBoundingClientRect(),x=clientX-bodyRect.left,y=clientY-bodyRect.top;
|
|
427
|
+
var gesture=activeGesture;
|
|
428
|
+
if(!gesture.previewPath||gesture.previewPathPoints>=32){
|
|
429
|
+
gesture.previewPath=createPreviewPath(gesture.previewLast?gesture.previewLast.x:x,gesture.previewLast?gesture.previewLast.y:y);
|
|
430
|
+
gesture.previewPathPoints=1;
|
|
431
|
+
}
|
|
432
|
+
var pathData=gesture.previewPath.getAttribute('d')||'';
|
|
433
|
+
gesture.previewPath.setAttribute('d',pathData+' L '+x.toFixed(1)+' '+y.toFixed(1));
|
|
434
|
+
gesture.previewPathPoints++;
|
|
435
|
+
gesture.previewLast={x:x,y:y};
|
|
436
|
+
}
|
|
437
|
+
function cancelGesture() {
|
|
438
|
+
var gesture=activeGesture; activeGesture=null; clearPreview();
|
|
439
|
+
if(gesture)try{inkCanvas.releasePointerCapture(gesture.pointerId);}catch(_){}
|
|
440
|
+
}
|
|
441
|
+
function commitGesture() {
|
|
442
|
+
var gesture=activeGesture; activeGesture=null; clearPreview();
|
|
443
|
+
if(!gesture || !alive || !options.isCurrent() || !inkConfig.enabled || !spatialIndex) return;
|
|
444
|
+
var simplified=simplifyPath(gesture.points,1.5,512), anchorSamples=densifyPath(simplified,12,512), segments=[], current=[];
|
|
445
|
+
anchorSamples.forEach(function(point) {
|
|
446
|
+
var coarse=queryNearestTextRect(spatialIndex,point.x,point.y,28);
|
|
447
|
+
var cutoff=coarse?Math.max(12,Math.min(28,0.8*coarse.entry.lineHeight)):12;
|
|
448
|
+
var anchored=coarse && closestGlyph(coarse.entry,point.x,point.y,cutoff);
|
|
449
|
+
if(!anchored){if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});current=[];return;}
|
|
450
|
+
current.push(anchored);
|
|
451
|
+
});
|
|
452
|
+
if(current.length>1)segments.push({href:options.href,spineIndex:options.spineIndex,points:current});
|
|
453
|
+
segments=segments.slice(0,64);
|
|
454
|
+
var pointCount=segments.reduce(function(total,segment){return total+segment.points.length;},0);
|
|
455
|
+
if(!pointCount)return;
|
|
456
|
+
var width=Math.max(1,doc.documentElement.scrollWidth,win.innerWidth),height=Math.max(1,doc.documentElement.scrollHeight,win.innerHeight);
|
|
457
|
+
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))};});
|
|
458
|
+
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;}));
|
|
459
|
+
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;}));
|
|
460
|
+
var first=segments[0].points[0], href=segments[0].href, documentId=typeof options.documentId==='function'?options.documentId():options.documentId;
|
|
461
|
+
var annotation={id:'epub-ink-'+Date.now()+'-'+Math.random().toString(36).slice(2,9),type:'ink',pageIndex:(options.spineIndex||0)+1,
|
|
462
|
+
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,
|
|
463
|
+
anchor:{version:1,kind:'epub-cfi',cfiRange:first.cfiRange,href:href,spineIndex:options.spineIndex,documentId:documentId,quote:first.quote},
|
|
464
|
+
epubInk:{version:1,kind:'epub-ink',documentId:documentId,preset:inkConfig.preset,segments:segments}};
|
|
465
|
+
if(typeof options.onInkCommit==='function')options.onInkCommit(annotation);
|
|
466
|
+
}
|
|
467
|
+
function pointerDown(event) {
|
|
468
|
+
if(!inkConfig.enabled || !alive || !options.isCurrent() || event.button!==undefined && event.button!==0)return;
|
|
469
|
+
if(activeGesture){cancelGesture();return;}
|
|
470
|
+
event.preventDefault();event.stopPropagation();
|
|
471
|
+
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};
|
|
472
|
+
updatePreviewPath(event.clientX,event.clientY);
|
|
473
|
+
try{inkCanvas.setPointerCapture(event.pointerId);}catch(_){}
|
|
474
|
+
}
|
|
475
|
+
function pointerMove(event) {
|
|
476
|
+
if(!activeGesture || activeGesture.pointerId!==event.pointerId)return;
|
|
477
|
+
if(!options.isCurrent()||!inkConfig.enabled){cancelGesture();return;}
|
|
478
|
+
event.preventDefault();event.stopPropagation();
|
|
479
|
+
var point={x:event.clientX+win.scrollX,y:event.clientY+win.scrollY};activeGesture.points.push(point);
|
|
480
|
+
if(activeGesture.points.length>2048)activeGesture.points=compactGesturePoints(activeGesture.points,1024);
|
|
481
|
+
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();}
|
|
482
|
+
updatePreviewPath(event.clientX,event.clientY);
|
|
483
|
+
activeGesture.last={x:event.clientX,y:event.clientY};
|
|
484
|
+
}
|
|
485
|
+
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();}
|
|
486
|
+
function pointerCancel(event) {if(activeGesture && (event.pointerId===undefined || activeGesture.pointerId===event.pointerId))cancelGesture();}
|
|
487
|
+
resizeCanvas();rebuildSpatialIndex();
|
|
488
|
+
inkCanvas.addEventListener('pointerdown',pointerDown);inkCanvas.addEventListener('pointermove',pointerMove);inkCanvas.addEventListener('pointerup',pointerUp);inkCanvas.addEventListener('pointercancel',pointerCancel);inkCanvas.addEventListener('lostpointercapture',pointerCancel);
|
|
489
|
+
win.addEventListener('blur',pointerCancel);win.addEventListener('resize',resizeCanvas);
|
|
233
490
|
function render() {
|
|
234
491
|
frame = 0; if (!alive || !options.isCurrent()) return;
|
|
235
|
-
var keep = new Set(), noteGroups = new Map();
|
|
492
|
+
var keep = new Set(), keepInk = new Set(), noteGroups = new Map();
|
|
236
493
|
hitRegions = [];
|
|
494
|
+
var bodyRect=doc.body.getBoundingClientRect() || {left:0,top:0};
|
|
495
|
+
inkSvg.setAttribute('width',String(Math.max(doc.documentElement.scrollWidth,win.innerWidth)));
|
|
496
|
+
inkSvg.setAttribute('height',String(Math.max(doc.documentElement.scrollHeight,win.innerHeight)));
|
|
237
497
|
options.annotations().forEach(function(annotation) {
|
|
238
498
|
var anchor = annotation.anchor;
|
|
239
499
|
if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
|
|
500
|
+
if(annotation.type==='ink' && annotation.epubInk){renderInkAnnotation(annotation,keepInk,bodyRect);return;}
|
|
240
501
|
var range = resolveRange(contents,anchor); if (!range) return;
|
|
241
502
|
var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
|
|
242
503
|
if (!rects.length) return;
|
|
@@ -275,22 +536,26 @@
|
|
|
275
536
|
group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
|
|
276
537
|
});
|
|
277
538
|
registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
|
|
539
|
+
inkRegistry.forEach(function(group,id){if (!keepInk.has(id)) {group.remove();inkRegistry.delete(id);}});
|
|
278
540
|
}
|
|
279
541
|
function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
|
|
542
|
+
function rebuildAndSchedule(){if(!alive)return;rebuildSpatialIndex();schedule();}
|
|
543
|
+
function handleResize(){resizeCanvas();rebuildAndSchedule();}
|
|
544
|
+
function handleDocumentLoad(){rebuildAndSchedule();}
|
|
280
545
|
function tapMarkup(event) {
|
|
281
546
|
if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
|
|
282
547
|
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;});});
|
|
283
548
|
if(hit) {event.stopPropagation();options.onTap(hit.id);}
|
|
284
549
|
}
|
|
285
550
|
doc.addEventListener('click',tapMarkup);
|
|
286
|
-
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(
|
|
551
|
+
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(rebuildAndSchedule) : null;
|
|
287
552
|
if (observer) observer.observe(doc.body);
|
|
288
|
-
win.addEventListener('resize',
|
|
289
|
-
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
|
|
553
|
+
win.addEventListener('resize',handleResize);win.addEventListener('scroll',schedule); doc.addEventListener('load',handleDocumentLoad,true);
|
|
554
|
+
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(function(){if(alive){rebuildSpatialIndex();schedule();}});
|
|
290
555
|
schedule();
|
|
291
|
-
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',
|
|
556
|
+
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();}};
|
|
292
557
|
}
|
|
293
|
-
return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
558
|
+
return {validAnchor:validAnchor,simplifyPath:simplifyPath,compactGesturePoints:compactGesturePoints,densifyPath:densifyPath,createSpatialIndex:createSpatialIndex,queryNearestTextRect:queryNearestTextRect,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
294
559
|
});
|
|
295
560
|
|
|
296
561
|
/* @papyrus-epub-annotations:end */
|
|
@@ -306,6 +571,7 @@
|
|
|
306
571
|
let annotationDocumentId;
|
|
307
572
|
let annotationLabels = {};
|
|
308
573
|
let epubAnnotations = [];
|
|
574
|
+
let epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
|
|
309
575
|
const epubAnnotationRenderers = new Map();
|
|
310
576
|
let spineItems = [];
|
|
311
577
|
let textPages = [];
|
|
@@ -1420,11 +1686,14 @@
|
|
|
1420
1686
|
if (href && window.PapyrusEpubAnnotations) {
|
|
1421
1687
|
const renderer = window.PapyrusEpubAnnotations.install(contents, {
|
|
1422
1688
|
href, get documentId() { return annotationDocumentId; },
|
|
1689
|
+
spineIndex:contents.sectionIndex,
|
|
1423
1690
|
isCurrent: () => session === documentSessionId && activeRendition === rendition,
|
|
1424
1691
|
pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
|
|
1425
1692
|
annotations: () => epubAnnotations, labels: () => annotationLabels,
|
|
1693
|
+
onInkCommit: annotation => sendEvent('EPUB_INK_CREATED',{documentSessionId:session,annotation}),
|
|
1426
1694
|
onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
|
|
1427
1695
|
});
|
|
1696
|
+
renderer.setInkMode(epubInkMode);
|
|
1428
1697
|
epubAnnotationRenderers.set(contents,renderer);
|
|
1429
1698
|
}
|
|
1430
1699
|
const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
|
|
@@ -1516,7 +1785,7 @@
|
|
|
1516
1785
|
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1517
1786
|
});
|
|
1518
1787
|
['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
|
|
1519
|
-
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1788
|
+
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => name==='relocated'?renderer.schedule():renderer.invalidateLayout());
|
|
1520
1789
|
}));
|
|
1521
1790
|
rendition.on('relocated', (location) => {
|
|
1522
1791
|
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
@@ -1813,6 +2082,7 @@
|
|
|
1813
2082
|
documentSessionId = payload.documentSessionId || null;
|
|
1814
2083
|
epubAnnotations = [];
|
|
1815
2084
|
annotationDocumentId = undefined;
|
|
2085
|
+
epubInkMode = {enabled:false,color:'#2563eb',opacity:1,strokeWidth:0.006};
|
|
1816
2086
|
zoom = 1.0;
|
|
1817
2087
|
epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
|
|
1818
2088
|
epubLocation = null;
|
|
@@ -1944,6 +2214,12 @@
|
|
|
1944
2214
|
}
|
|
1945
2215
|
sendResponse(id,true,null);return;
|
|
1946
2216
|
}
|
|
2217
|
+
if (kind === 'set-epub-ink-mode') {
|
|
2218
|
+
if(currentType!=='epub'||!documentSessionId||payload.documentSessionId!==documentSessionId)throw new Error('Stale EPUB ink session');
|
|
2219
|
+
epubInkMode={enabled:payload.enabled===true,preset:['ink','highlight','underline'].indexOf(payload.preset)>=0?payload.preset:'ink',color:/^#[0-9a-f]{6}$/i.test(payload.color||'')?payload.color:'#2563eb',opacity:Number.isFinite(payload.opacity)?Math.max(0,Math.min(1,payload.opacity)):1,strokeWidth:Number.isFinite(payload.strokeWidth)?Math.max(0.0025,Math.min(0.02,payload.strokeWidth)):0.006};
|
|
2220
|
+
epubAnnotationRenderers.forEach(renderer=>renderer.setInkMode(epubInkMode));
|
|
2221
|
+
sendResponse(id,true,null);return;
|
|
2222
|
+
}
|
|
1947
2223
|
if(kind === 'epub-go-to-location'){
|
|
1948
2224
|
if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
|
|
1949
2225
|
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
getComicPreviewSize,
|
|
6
|
-
getComicPageAspectRatio,
|
|
7
|
-
getProtectedComicPageIndexes,
|
|
8
|
-
isCurrentComicPageLoad,
|
|
9
|
-
isComicImageName,
|
|
10
|
-
patchCbrWorkerSource,
|
|
11
|
-
sortComicPageNames,
|
|
12
|
-
} from "./comicRuntime";
|
|
13
|
-
|
|
14
|
-
describe("mobile comic runtime helpers", () => {
|
|
15
|
-
it("keeps only supported comic image entries", () => {
|
|
16
|
-
expect(isComicImageName("pages/001.jpg")).toBe(true);
|
|
17
|
-
expect(isComicImageName("pages/002.webp")).toBe(true);
|
|
18
|
-
expect(isComicImageName("pages/readme.txt")).toBe(false);
|
|
19
|
-
expect(isComicImageName("pages/")).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("sorts page names naturally", () => {
|
|
23
|
-
expect(
|
|
24
|
-
sortComicPageNames(["page-10.jpg", "page-2.jpg", "page-1.jpg"])
|
|
25
|
-
).toEqual(["page-1.jpg", "page-2.jpg", "page-10.jpg"]);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("patches the shipped CBR worker to use the packaged WASM asset", () => {
|
|
29
|
-
const worker = readFileSync(
|
|
30
|
-
resolve(
|
|
31
|
-
process.cwd(),
|
|
32
|
-
"packages/engine-cbr-mobile/runtime/worker-bundle.js.txt"
|
|
33
|
-
),
|
|
34
|
-
"utf8"
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const patched = patchCbrWorkerSource(worker, "/assets/libarchive.wasm");
|
|
38
|
-
|
|
39
|
-
expect(patched).not.toContain(
|
|
40
|
-
'new URL("libarchive.wasm",import.meta.url).href'
|
|
41
|
-
);
|
|
42
|
-
expect(patched).toContain(JSON.stringify("/assets/libarchive.wasm"));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("caps comic previews without upscaling them", () => {
|
|
46
|
-
expect(getComicPreviewSize(1600, 2400)).toEqual({ width: 240, height: 360 });
|
|
47
|
-
expect(getComicPreviewSize(120, 180)).toEqual({ width: 120, height: 180 });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("keeps the page geometry when a cached image URL is evicted", () => {
|
|
51
|
-
expect(getComicPageAspectRatio(1200, 1800)).toBe("1200 / 1800");
|
|
52
|
-
expect(getComicPageAspectRatio(0, 0)).toBe("1 / 1");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("protects visible and pending pages from URL eviction", () => {
|
|
56
|
-
expect(
|
|
57
|
-
Array.from(getProtectedComicPageIndexes(1, [3, 4], [5])).sort(
|
|
58
|
-
(left, right) => left - right
|
|
59
|
-
)
|
|
60
|
-
).toEqual([-1, 0, 1, 3, 4, 5]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("rejects page extraction completions from an older document", () => {
|
|
64
|
-
const entry = { name: "old.jpg" };
|
|
65
|
-
const currentEntry = { name: "new.jpg" };
|
|
66
|
-
|
|
67
|
-
expect(isCurrentComicPageLoad(2, 3, entry, currentEntry)).toBe(false);
|
|
68
|
-
expect(isCurrentComicPageLoad(3, 3, entry, currentEntry)).toBe(false);
|
|
69
|
-
expect(isCurrentComicPageLoad(3, 3, entry, entry)).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("embeds the tested helpers in the shipped runtimes", () => {
|
|
73
|
-
const runtime = readFileSync(
|
|
74
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
75
|
-
"utf8"
|
|
76
|
-
);
|
|
77
|
-
const html = readFileSync(
|
|
78
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
79
|
-
"utf8"
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
expect(runtime.match(/function getComicPageAspectRatio/g)).toHaveLength(1);
|
|
83
|
-
expect(html.match(/function getComicPageAspectRatio/g)).toHaveLength(1);
|
|
84
|
-
expect(html).toContain("root.PapyrusComicRuntime");
|
|
85
|
-
expect(runtime).toContain("getProtectedComicPageIndexes");
|
|
86
|
-
expect(html).toContain("getProtectedComicPageIndexes");
|
|
87
|
-
expect(runtime).toContain("file-chunk-request");
|
|
88
|
-
expect(html).toContain("file-chunk-request");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("loads local EPUB files through the native chunk bridge", () => {
|
|
92
|
-
const runtime = readFileSync(
|
|
93
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
94
|
-
"utf8"
|
|
95
|
-
);
|
|
96
|
-
const html = readFileSync(
|
|
97
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
98
|
-
"utf8"
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
expect(runtime).toContain("sourceToArrayBuffer(source),");
|
|
102
|
-
expect(html).toContain("sourceToArrayBuffer(source),");
|
|
103
|
-
expect(runtime).not.toContain("data = source.uri;");
|
|
104
|
-
expect(html).not.toContain("data = source.uri;");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("passes an exact ArrayBuffer to epub.js for base64 sources", () => {
|
|
108
|
-
const runtime = readFileSync(
|
|
109
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
110
|
-
"utf8"
|
|
111
|
-
);
|
|
112
|
-
const html = readFileSync(
|
|
113
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
114
|
-
"utf8"
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
expect(runtime).toContain("const toExactArrayBuffer = (bytes) =>");
|
|
118
|
-
expect(html).toContain("const toExactArrayBuffer = (bytes) =>");
|
|
119
|
-
expect(runtime).toContain("data = toExactArrayBuffer(decodeBase64(source.data));");
|
|
120
|
-
expect(html).toContain("data = toExactArrayBuffer(decodeBase64(source.data));");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("correlates EPUB diagnostics and load terminals by request id", () => {
|
|
124
|
-
const runtime = readFileSync(
|
|
125
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
126
|
-
"utf8"
|
|
127
|
-
);
|
|
128
|
-
const html = readFileSync(
|
|
129
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
130
|
-
"utf8"
|
|
131
|
-
);
|
|
132
|
-
for (const artifact of [runtime, html]) {
|
|
133
|
-
expect(artifact).toContain("epub.load.start");
|
|
134
|
-
expect(artifact).toContain("epub.book.ready");
|
|
135
|
-
expect(artifact).toContain("epub.display.start");
|
|
136
|
-
expect(artifact).toContain("epub.load.ready");
|
|
137
|
-
expect(artifact).toContain("epub.load.error");
|
|
138
|
-
expect(artifact).toContain("epub.load.timeout");
|
|
139
|
-
expect(artifact).toContain("loadId");
|
|
140
|
-
expect(artifact).toContain("document.ready");
|
|
141
|
-
expect(artifact).toContain("document.error");
|
|
142
|
-
expect(artifact).toContain("withEpubStageTimeout");
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("invalidates a pending EPUB load when another document starts", () => {
|
|
147
|
-
const runtime = readFileSync(
|
|
148
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
149
|
-
"utf8"
|
|
150
|
-
);
|
|
151
|
-
const loadStart = runtime.indexOf("if (kind === 'load')");
|
|
152
|
-
const generationInvalidation = runtime.indexOf(
|
|
153
|
-
"epubLoadGeneration += 1",
|
|
154
|
-
loadStart
|
|
155
|
-
);
|
|
156
|
-
const currentTypeAssignment = runtime.indexOf(
|
|
157
|
-
"currentType = payload.type",
|
|
158
|
-
loadStart
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
expect(generationInvalidation).toBeGreaterThan(loadStart);
|
|
162
|
-
expect(generationInvalidation).toBeLessThan(currentTypeAssignment);
|
|
163
|
-
expect(runtime).toContain(
|
|
164
|
-
"const loadEpub = async (source, loadId, generation) =>"
|
|
165
|
-
);
|
|
166
|
-
expect(runtime).toContain(
|
|
167
|
-
"const result = await loadEpub(payload.source, id, generation);"
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("rejects a stale EPUB load after outline resolution", () => {
|
|
172
|
-
const runtime = readFileSync(
|
|
173
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
174
|
-
"utf8"
|
|
175
|
-
);
|
|
176
|
-
const outlineStart = runtime.indexOf("const outline = await withEpubStageTimeout(");
|
|
177
|
-
const outlineGuard = runtime.indexOf(
|
|
178
|
-
"if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');",
|
|
179
|
-
outlineStart
|
|
180
|
-
);
|
|
181
|
-
const readyEvent = runtime.indexOf(
|
|
182
|
-
"sendEpubDiagnostic('epub.load.ready'",
|
|
183
|
-
outlineStart
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
expect(outlineStart).toBeGreaterThan(-1);
|
|
187
|
-
expect(outlineGuard).toBeGreaterThan(outlineStart);
|
|
188
|
-
expect(outlineGuard).toBeLessThan(readyEvent);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("ships the paginated and chapter-scroll EPUB reading policy", () => {
|
|
192
|
-
const runtime = readFileSync(
|
|
193
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
194
|
-
"utf8"
|
|
195
|
-
);
|
|
196
|
-
const html = readFileSync(
|
|
197
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
198
|
-
"utf8"
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
expect(runtime).toContain("manager:'default'");
|
|
202
|
-
expect(html).toContain("manager:'default'");
|
|
203
|
-
expect(runtime).toContain("'scrolled-doc'");
|
|
204
|
-
expect(html).toContain("'scrolled-doc'");
|
|
205
|
-
expect(runtime).toContain("rendition.on('relocated'");
|
|
206
|
-
expect(html).toContain("rendition.on('relocated'");
|
|
207
|
-
expect(runtime).toContain("sendEvent('VIEWER_SCROLL'");
|
|
208
|
-
expect(html).toContain("sendEvent('VIEWER_SCROLL'");
|
|
209
|
-
expect(runtime).toContain("sendEvent('EPUB_CONTENT_TAP'");
|
|
210
|
-
expect(html).toContain("sendEvent('EPUB_CONTENT_TAP'");
|
|
211
|
-
expect(runtime).toContain("'paginated'");
|
|
212
|
-
expect(html).toContain("'paginated'");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("observes the real EPUB manager queue without changing queue semantics", () => {
|
|
216
|
-
const runtime = readFileSync(
|
|
217
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/runtime.js"),
|
|
218
|
-
"utf8"
|
|
219
|
-
);
|
|
220
|
-
const html = readFileSync(
|
|
221
|
-
resolve(process.cwd(), "packages/ui-react-native/runtime/index.html"),
|
|
222
|
-
"utf8"
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
for (const artifact of [runtime, html]) {
|
|
226
|
-
expect(artifact).toContain("const originalCheck = typeof manager.check");
|
|
227
|
-
expect(artifact).toContain("manager.check = function (...args)");
|
|
228
|
-
expect(artifact).toContain("const originalEnqueue = queue && typeof queue.enqueue");
|
|
229
|
-
expect(artifact).toContain("queue.enqueue = function (...args)");
|
|
230
|
-
expect(artifact).toContain("manager.queue.enqueue");
|
|
231
|
-
expect(artifact).toContain("queue.enqueue = originalEnqueue");
|
|
232
|
-
expect(artifact).toContain("manager.check.request");
|
|
233
|
-
expect(artifact).toContain("manager.check.start");
|
|
234
|
-
expect(artifact).toContain("manager.check.end");
|
|
235
|
-
expect(artifact).toContain("epubSelectionCleanups");
|
|
236
|
-
expect(artifact).toContain("__papyrusSelectionCleanup");
|
|
237
|
-
expect(artifact).toContain("setSelectionActive(false)");
|
|
238
|
-
expect(artifact).not.toContain("String(task)");
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
});
|