@embedpdf/plugin-annotation 2.8.0 → 2.9.1
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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +829 -148
- package/dist/index.js.map +1 -1
- package/dist/lib/geometry/cloudy-border.d.ts +90 -0
- package/dist/lib/geometry/index.d.ts +1 -0
- package/dist/lib/handlers/types.d.ts +2 -1
- package/dist/lib/tools/default-tools.d.ts +43 -88
- package/dist/lib/tools/types.d.ts +34 -1
- package/dist/lib/types.d.ts +3 -0
- package/dist/preact/index.cjs +1 -1
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.js +371 -242
- package/dist/preact/index.js.map +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +371 -242
- package/dist/react/index.js.map +1 -1
- package/dist/shared/components/annotation-container.d.ts +4 -2
- package/dist/shared/components/annotations/circle.d.ts +6 -2
- package/dist/shared/components/annotations/polygon.d.ts +3 -1
- package/dist/shared/components/annotations/square.d.ts +6 -2
- package/dist/shared/components/types.d.ts +6 -2
- package/dist/shared-preact/components/annotation-container.d.ts +4 -2
- package/dist/shared-preact/components/annotations/circle.d.ts +6 -2
- package/dist/shared-preact/components/annotations/polygon.d.ts +3 -1
- package/dist/shared-preact/components/annotations/square.d.ts +6 -2
- package/dist/shared-preact/components/types.d.ts +6 -2
- package/dist/shared-react/components/annotation-container.d.ts +4 -2
- package/dist/shared-react/components/annotations/circle.d.ts +6 -2
- package/dist/shared-react/components/annotations/polygon.d.ts +3 -1
- package/dist/shared-react/components/annotations/square.d.ts +6 -2
- package/dist/shared-react/components/types.d.ts +6 -2
- package/dist/svelte/components/annotations/Circle.svelte.d.ts +3 -1
- package/dist/svelte/components/annotations/Polygon.svelte.d.ts +1 -0
- package/dist/svelte/components/annotations/Square.svelte.d.ts +3 -1
- package/dist/svelte/components/types.d.ts +2 -1
- package/dist/svelte/context/types.d.ts +6 -2
- package/dist/svelte/index.cjs +1 -1
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.js +525 -298
- package/dist/svelte/index.js.map +1 -1
- package/dist/vue/components/annotation-container.vue.d.ts +7 -6
- package/dist/vue/components/annotations/circle.vue.d.ts +5 -1
- package/dist/vue/components/annotations/polygon.vue.d.ts +2 -0
- package/dist/vue/components/annotations/square.vue.d.ts +5 -1
- package/dist/vue/components/annotations.vue.d.ts +8 -9
- package/dist/vue/context/types.d.ts +6 -2
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.js +289 -135
- package/dist/vue/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -340,6 +340,62 @@ const getSelectionGroupingAction = (s) => {
|
|
|
340
340
|
}
|
|
341
341
|
return selected.length >= 2 ? "group" : "disabled";
|
|
342
342
|
};
|
|
343
|
+
const inkTools = [
|
|
344
|
+
{
|
|
345
|
+
id: "ink",
|
|
346
|
+
name: "Pen",
|
|
347
|
+
matchScore: (a) => a.type === PdfAnnotationSubtype.INK && a.intent !== "InkHighlight" ? 5 : 0,
|
|
348
|
+
interaction: {
|
|
349
|
+
exclusive: false,
|
|
350
|
+
cursor: "crosshair",
|
|
351
|
+
isDraggable: true,
|
|
352
|
+
isResizable: true,
|
|
353
|
+
lockAspectRatio: false
|
|
354
|
+
},
|
|
355
|
+
defaults: {
|
|
356
|
+
type: PdfAnnotationSubtype.INK,
|
|
357
|
+
strokeColor: "#E44234",
|
|
358
|
+
color: "#E44234",
|
|
359
|
+
// deprecated alias
|
|
360
|
+
opacity: 1,
|
|
361
|
+
strokeWidth: 6
|
|
362
|
+
},
|
|
363
|
+
behavior: {
|
|
364
|
+
commitDelay: 800
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: "inkHighlighter",
|
|
369
|
+
name: "Ink Highlighter",
|
|
370
|
+
matchScore: (a) => a.type === PdfAnnotationSubtype.INK && a.intent === "InkHighlight" ? 10 : 0,
|
|
371
|
+
interaction: {
|
|
372
|
+
exclusive: false,
|
|
373
|
+
cursor: "crosshair",
|
|
374
|
+
isDraggable: true,
|
|
375
|
+
isResizable: true,
|
|
376
|
+
lockAspectRatio: false,
|
|
377
|
+
lockGroupAspectRatio: (a) => {
|
|
378
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
379
|
+
return r2 >= 6 && r2 <= 84;
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
defaults: {
|
|
383
|
+
type: PdfAnnotationSubtype.INK,
|
|
384
|
+
intent: "InkHighlight",
|
|
385
|
+
strokeColor: "#FFCD45",
|
|
386
|
+
color: "#FFCD45",
|
|
387
|
+
// deprecated alias
|
|
388
|
+
opacity: 1,
|
|
389
|
+
strokeWidth: 14,
|
|
390
|
+
blendMode: PdfBlendMode.Multiply
|
|
391
|
+
},
|
|
392
|
+
behavior: {
|
|
393
|
+
commitDelay: 800,
|
|
394
|
+
smartLineRecognition: true,
|
|
395
|
+
smartLineThreshold: 0.15
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
];
|
|
343
399
|
const defaultTools = [
|
|
344
400
|
// Text Markup Tools
|
|
345
401
|
{
|
|
@@ -482,52 +538,7 @@ const defaultTools = [
|
|
|
482
538
|
}
|
|
483
539
|
},
|
|
484
540
|
// Drawing Tools
|
|
485
|
-
|
|
486
|
-
id: "ink",
|
|
487
|
-
name: "Pen",
|
|
488
|
-
matchScore: (a) => a.type === PdfAnnotationSubtype.INK && a.intent !== "InkHighlight" ? 5 : 0,
|
|
489
|
-
interaction: {
|
|
490
|
-
exclusive: false,
|
|
491
|
-
cursor: "crosshair",
|
|
492
|
-
isDraggable: true,
|
|
493
|
-
isResizable: true,
|
|
494
|
-
lockAspectRatio: false
|
|
495
|
-
},
|
|
496
|
-
defaults: {
|
|
497
|
-
type: PdfAnnotationSubtype.INK,
|
|
498
|
-
strokeColor: "#E44234",
|
|
499
|
-
color: "#E44234",
|
|
500
|
-
// deprecated alias
|
|
501
|
-
opacity: 1,
|
|
502
|
-
strokeWidth: 6
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
id: "inkHighlighter",
|
|
507
|
-
name: "Ink Highlighter",
|
|
508
|
-
matchScore: (a) => a.type === PdfAnnotationSubtype.INK && a.intent === "InkHighlight" ? 10 : 0,
|
|
509
|
-
interaction: {
|
|
510
|
-
exclusive: false,
|
|
511
|
-
cursor: "crosshair",
|
|
512
|
-
isDraggable: true,
|
|
513
|
-
isResizable: true,
|
|
514
|
-
lockAspectRatio: false,
|
|
515
|
-
lockGroupAspectRatio: (a) => {
|
|
516
|
-
const r = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
517
|
-
return r >= 6 && r <= 84;
|
|
518
|
-
}
|
|
519
|
-
},
|
|
520
|
-
defaults: {
|
|
521
|
-
type: PdfAnnotationSubtype.INK,
|
|
522
|
-
intent: "InkHighlight",
|
|
523
|
-
strokeColor: "#FFCD45",
|
|
524
|
-
color: "#FFCD45",
|
|
525
|
-
// deprecated alias
|
|
526
|
-
opacity: 1,
|
|
527
|
-
strokeWidth: 14,
|
|
528
|
-
blendMode: PdfBlendMode.Multiply
|
|
529
|
-
}
|
|
530
|
-
},
|
|
541
|
+
...inkTools,
|
|
531
542
|
// Shape Tools
|
|
532
543
|
{
|
|
533
544
|
id: "circle",
|
|
@@ -540,8 +551,8 @@ const defaultTools = [
|
|
|
540
551
|
isResizable: true,
|
|
541
552
|
lockAspectRatio: false,
|
|
542
553
|
lockGroupAspectRatio: (a) => {
|
|
543
|
-
const
|
|
544
|
-
return
|
|
554
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
555
|
+
return r2 >= 6 && r2 <= 84;
|
|
545
556
|
}
|
|
546
557
|
},
|
|
547
558
|
defaults: {
|
|
@@ -568,8 +579,8 @@ const defaultTools = [
|
|
|
568
579
|
isResizable: true,
|
|
569
580
|
lockAspectRatio: false,
|
|
570
581
|
lockGroupAspectRatio: (a) => {
|
|
571
|
-
const
|
|
572
|
-
return
|
|
582
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
583
|
+
return r2 >= 6 && r2 <= 84;
|
|
573
584
|
}
|
|
574
585
|
},
|
|
575
586
|
defaults: {
|
|
@@ -599,8 +610,8 @@ const defaultTools = [
|
|
|
599
610
|
isGroupResizable: true,
|
|
600
611
|
// Scales proportionally in a group
|
|
601
612
|
lockGroupAspectRatio: (a) => {
|
|
602
|
-
const
|
|
603
|
-
return
|
|
613
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
614
|
+
return r2 >= 6 && r2 <= 84;
|
|
604
615
|
}
|
|
605
616
|
},
|
|
606
617
|
defaults: {
|
|
@@ -630,8 +641,8 @@ const defaultTools = [
|
|
|
630
641
|
isGroupResizable: true,
|
|
631
642
|
// Scales proportionally in a group
|
|
632
643
|
lockGroupAspectRatio: (a) => {
|
|
633
|
-
const
|
|
634
|
-
return
|
|
644
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
645
|
+
return r2 >= 6 && r2 <= 84;
|
|
635
646
|
}
|
|
636
647
|
},
|
|
637
648
|
defaults: {
|
|
@@ -666,8 +677,8 @@ const defaultTools = [
|
|
|
666
677
|
isGroupResizable: true,
|
|
667
678
|
// Scales proportionally in a group
|
|
668
679
|
lockGroupAspectRatio: (a) => {
|
|
669
|
-
const
|
|
670
|
-
return
|
|
680
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
681
|
+
return r2 >= 6 && r2 <= 84;
|
|
671
682
|
}
|
|
672
683
|
},
|
|
673
684
|
defaults: {
|
|
@@ -692,8 +703,8 @@ const defaultTools = [
|
|
|
692
703
|
isGroupResizable: true,
|
|
693
704
|
// Scales proportionally in a group
|
|
694
705
|
lockGroupAspectRatio: (a) => {
|
|
695
|
-
const
|
|
696
|
-
return
|
|
706
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
707
|
+
return r2 >= 6 && r2 <= 84;
|
|
697
708
|
}
|
|
698
709
|
},
|
|
699
710
|
defaults: {
|
|
@@ -736,8 +747,8 @@ const defaultTools = [
|
|
|
736
747
|
isResizable: true,
|
|
737
748
|
lockAspectRatio: false,
|
|
738
749
|
lockGroupAspectRatio: (a) => {
|
|
739
|
-
const
|
|
740
|
-
return
|
|
750
|
+
const r2 = ((a.rotation ?? 0) % 90 + 90) % 90;
|
|
751
|
+
return r2 >= 6 && r2 <= 84;
|
|
741
752
|
}
|
|
742
753
|
},
|
|
743
754
|
defaults: {
|
|
@@ -760,7 +771,9 @@ const defaultTools = [
|
|
|
760
771
|
defaultContent: "Insert text"
|
|
761
772
|
},
|
|
762
773
|
behavior: {
|
|
763
|
-
insertUpright: true
|
|
774
|
+
insertUpright: true,
|
|
775
|
+
editAfterCreate: true,
|
|
776
|
+
selectAfterCreate: true
|
|
764
777
|
}
|
|
765
778
|
},
|
|
766
779
|
{
|
|
@@ -865,13 +878,14 @@ const initialState = (cfg) => {
|
|
|
865
878
|
}
|
|
866
879
|
});
|
|
867
880
|
const tools = Array.from(toolMap.values()).map((t) => {
|
|
868
|
-
var _a, _b;
|
|
881
|
+
var _a, _b, _c;
|
|
869
882
|
return {
|
|
870
883
|
...t,
|
|
871
884
|
behavior: {
|
|
872
885
|
...t.behavior,
|
|
873
886
|
deactivateToolAfterCreate: ((_a = t.behavior) == null ? void 0 : _a.deactivateToolAfterCreate) ?? cfg.deactivateToolAfterCreate ?? false,
|
|
874
|
-
selectAfterCreate: ((_b = t.behavior) == null ? void 0 : _b.selectAfterCreate) ?? cfg.selectAfterCreate ?? true
|
|
887
|
+
selectAfterCreate: ((_b = t.behavior) == null ? void 0 : _b.selectAfterCreate) ?? cfg.selectAfterCreate ?? true,
|
|
888
|
+
editAfterCreate: ((_c = t.behavior) == null ? void 0 : _c.editAfterCreate) ?? cfg.editAfterCreate ?? false
|
|
875
889
|
}
|
|
876
890
|
};
|
|
877
891
|
});
|
|
@@ -1180,6 +1194,18 @@ function useState(initialValue) {
|
|
|
1180
1194
|
};
|
|
1181
1195
|
return [getValue, setValue];
|
|
1182
1196
|
}
|
|
1197
|
+
function isLineLike(points, threshold) {
|
|
1198
|
+
if (points.length < 3) return true;
|
|
1199
|
+
const A = points[0];
|
|
1200
|
+
const B = points[points.length - 1];
|
|
1201
|
+
const len = Math.hypot(B.x - A.x, B.y - A.y);
|
|
1202
|
+
if (len < 5) return false;
|
|
1203
|
+
const maxDev = points.reduce((max, P) => {
|
|
1204
|
+
const d = Math.abs((B.x - A.x) * (A.y - P.y) - (A.x - P.x) * (B.y - A.y)) / len;
|
|
1205
|
+
return Math.max(max, d);
|
|
1206
|
+
}, 0);
|
|
1207
|
+
return maxDev / len < threshold;
|
|
1208
|
+
}
|
|
1183
1209
|
const inkHandlerFactory = {
|
|
1184
1210
|
annotationType: PdfAnnotationSubtype.INK,
|
|
1185
1211
|
create(context) {
|
|
@@ -1215,7 +1241,8 @@ const inkHandlerFactory = {
|
|
|
1215
1241
|
data: {
|
|
1216
1242
|
...defaults,
|
|
1217
1243
|
rect: bounds,
|
|
1218
|
-
inkList: strokes
|
|
1244
|
+
inkList: strokes,
|
|
1245
|
+
blendMode: defaults.blendMode
|
|
1219
1246
|
}
|
|
1220
1247
|
};
|
|
1221
1248
|
};
|
|
@@ -1243,6 +1270,37 @@ const inkHandlerFactory = {
|
|
|
1243
1270
|
var _a;
|
|
1244
1271
|
setIsDrawing(false);
|
|
1245
1272
|
(_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
|
|
1273
|
+
const tool = getTool();
|
|
1274
|
+
const behavior = tool == null ? void 0 : tool.behavior;
|
|
1275
|
+
if (behavior == null ? void 0 : behavior.smartLineRecognition) {
|
|
1276
|
+
const threshold = behavior.smartLineThreshold ?? 0.15;
|
|
1277
|
+
const strokes = getStrokes();
|
|
1278
|
+
const last = strokes[strokes.length - 1];
|
|
1279
|
+
if (last && last.points.length > 1 && isLineLike(last.points, threshold)) {
|
|
1280
|
+
const first = last.points[0];
|
|
1281
|
+
const end = last.points[last.points.length - 1];
|
|
1282
|
+
const dx = end.x - first.x;
|
|
1283
|
+
const dy = end.y - first.y;
|
|
1284
|
+
const angleDeg = Math.atan2(Math.abs(dy), Math.abs(dx)) * (180 / Math.PI);
|
|
1285
|
+
const snapAngleDeg = behavior.snapAngleDeg ?? 15;
|
|
1286
|
+
if (angleDeg <= snapAngleDeg) {
|
|
1287
|
+
const avgY = last.points.reduce((sum, p) => sum + p.y, 0) / last.points.length;
|
|
1288
|
+
last.points = [
|
|
1289
|
+
{ x: first.x, y: avgY },
|
|
1290
|
+
{ x: end.x, y: avgY }
|
|
1291
|
+
];
|
|
1292
|
+
} else if (angleDeg >= 90 - snapAngleDeg) {
|
|
1293
|
+
const avgX = last.points.reduce((sum, p) => sum + p.x, 0) / last.points.length;
|
|
1294
|
+
last.points = [
|
|
1295
|
+
{ x: avgX, y: first.y },
|
|
1296
|
+
{ x: avgX, y: end.y }
|
|
1297
|
+
];
|
|
1298
|
+
}
|
|
1299
|
+
setStrokes([...strokes]);
|
|
1300
|
+
onPreview(getPreview());
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const commitDelay = (behavior == null ? void 0 : behavior.commitDelay) ?? 800;
|
|
1246
1304
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1247
1305
|
timerRef.current = setTimeout(() => {
|
|
1248
1306
|
const strokes = getStrokes();
|
|
@@ -1263,7 +1321,7 @@ const inkHandlerFactory = {
|
|
|
1263
1321
|
}
|
|
1264
1322
|
setStrokes([]);
|
|
1265
1323
|
onPreview(null);
|
|
1266
|
-
},
|
|
1324
|
+
}, commitDelay);
|
|
1267
1325
|
},
|
|
1268
1326
|
onPointerCancel: (_, evt) => {
|
|
1269
1327
|
var _a;
|
|
@@ -1335,14 +1393,14 @@ const LINE_ENDING_HANDLERS = {
|
|
|
1335
1393
|
},
|
|
1336
1394
|
[PdfAnnotationLineEnding.Circle]: {
|
|
1337
1395
|
getSvgPath: (sw) => {
|
|
1338
|
-
const
|
|
1339
|
-
return `M ${
|
|
1396
|
+
const r2 = sw * 5 / 2;
|
|
1397
|
+
return `M ${r2} 0 A ${r2} ${r2} 0 1 1 ${-r2} 0 A ${r2} ${r2} 0 1 1 ${r2} 0`;
|
|
1340
1398
|
},
|
|
1341
1399
|
getLocalPoints: (sw) => {
|
|
1342
|
-
const
|
|
1400
|
+
const r2 = sw * 5 / 2;
|
|
1343
1401
|
return [
|
|
1344
|
-
{ x: -
|
|
1345
|
-
{ x:
|
|
1402
|
+
{ x: -r2, y: -r2 },
|
|
1403
|
+
{ x: r2, y: r2 }
|
|
1346
1404
|
];
|
|
1347
1405
|
},
|
|
1348
1406
|
getRotation: () => 0,
|
|
@@ -1484,7 +1542,7 @@ function createEnding(ending, strokeWidth, rad, px, py) {
|
|
|
1484
1542
|
if (!ending) return null;
|
|
1485
1543
|
const handler = LINE_ENDING_HANDLERS[ending];
|
|
1486
1544
|
if (!handler) return null;
|
|
1487
|
-
const toDeg = (
|
|
1545
|
+
const toDeg = (r2) => r2 * 180 / Math.PI;
|
|
1488
1546
|
const rotationAngle = handler.getRotation(rad);
|
|
1489
1547
|
return {
|
|
1490
1548
|
d: handler.getSvgPath(strokeWidth),
|
|
@@ -1701,8 +1759,8 @@ function useClickDetector({
|
|
|
1701
1759
|
onMove: (pos) => {
|
|
1702
1760
|
const start = getStartPos();
|
|
1703
1761
|
if (!start || getHasMoved()) return;
|
|
1704
|
-
const
|
|
1705
|
-
if (
|
|
1762
|
+
const distance2 = Math.sqrt(Math.pow(pos.x - start.x, 2) + Math.pow(pos.y - start.y, 2));
|
|
1763
|
+
if (distance2 > threshold) {
|
|
1706
1764
|
setHasMoved(true);
|
|
1707
1765
|
}
|
|
1708
1766
|
},
|
|
@@ -2127,6 +2185,554 @@ const polylineHandlerFactory = {
|
|
|
2127
2185
|
};
|
|
2128
2186
|
}
|
|
2129
2187
|
};
|
|
2188
|
+
function convertAABBRectToUnrotatedSpace(newAABBRect, originalAABBRect, originalUnrotatedRect, rotationDegrees) {
|
|
2189
|
+
const theta = rotationDegrees * Math.PI / 180;
|
|
2190
|
+
const A = Math.abs(Math.cos(theta));
|
|
2191
|
+
const B = Math.abs(Math.sin(theta));
|
|
2192
|
+
const det = A * A - B * B;
|
|
2193
|
+
const newAABBw = newAABBRect.size.width;
|
|
2194
|
+
const newAABBh = newAABBRect.size.height;
|
|
2195
|
+
let newWidth;
|
|
2196
|
+
let newHeight;
|
|
2197
|
+
if (Math.abs(det) > 1e-6) {
|
|
2198
|
+
newWidth = (A * newAABBw - B * newAABBh) / det;
|
|
2199
|
+
newHeight = (A * newAABBh - B * newAABBw) / det;
|
|
2200
|
+
newWidth = Math.max(newWidth, 1);
|
|
2201
|
+
newHeight = Math.max(newHeight, 1);
|
|
2202
|
+
} else {
|
|
2203
|
+
const origArea = originalAABBRect.size.width * originalAABBRect.size.height;
|
|
2204
|
+
const newArea = newAABBw * newAABBh;
|
|
2205
|
+
const uniformScale = origArea > 0 ? Math.sqrt(newArea / origArea) : 1;
|
|
2206
|
+
newWidth = originalUnrotatedRect.size.width * uniformScale;
|
|
2207
|
+
newHeight = originalUnrotatedRect.size.height * uniformScale;
|
|
2208
|
+
}
|
|
2209
|
+
const newCenterX = newAABBRect.origin.x + newAABBw / 2;
|
|
2210
|
+
const newCenterY = newAABBRect.origin.y + newAABBh / 2;
|
|
2211
|
+
return {
|
|
2212
|
+
origin: { x: newCenterX - newWidth / 2, y: newCenterY - newHeight / 2 },
|
|
2213
|
+
size: { width: newWidth, height: newHeight }
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
const ANGLE_180 = Math.PI;
|
|
2217
|
+
const ANGLE_90 = Math.PI / 2;
|
|
2218
|
+
const ANGLE_34 = 34 * Math.PI / 180;
|
|
2219
|
+
const ANGLE_30 = 30 * Math.PI / 180;
|
|
2220
|
+
const ANGLE_12 = 12 * Math.PI / 180;
|
|
2221
|
+
class PathBuilder {
|
|
2222
|
+
constructor() {
|
|
2223
|
+
this.parts = [];
|
|
2224
|
+
this.bbox = {
|
|
2225
|
+
minX: Infinity,
|
|
2226
|
+
minY: Infinity,
|
|
2227
|
+
maxX: -Infinity,
|
|
2228
|
+
maxY: -Infinity
|
|
2229
|
+
};
|
|
2230
|
+
this.started = false;
|
|
2231
|
+
}
|
|
2232
|
+
moveTo(x, y) {
|
|
2233
|
+
const sy = -y;
|
|
2234
|
+
this.updateBBox(x, sy);
|
|
2235
|
+
this.parts.push(`M ${r(x)} ${r(sy)}`);
|
|
2236
|
+
this.started = true;
|
|
2237
|
+
}
|
|
2238
|
+
curveTo(x1, y1, x2, y2, x3, y3) {
|
|
2239
|
+
const sy1 = -y1;
|
|
2240
|
+
const sy2 = -y2;
|
|
2241
|
+
const sy3 = -y3;
|
|
2242
|
+
this.updateBBox(x1, sy1);
|
|
2243
|
+
this.updateBBox(x2, sy2);
|
|
2244
|
+
this.updateBBox(x3, sy3);
|
|
2245
|
+
this.parts.push(`C ${r(x1)} ${r(sy1)}, ${r(x2)} ${r(sy2)}, ${r(x3)} ${r(sy3)}`);
|
|
2246
|
+
}
|
|
2247
|
+
close() {
|
|
2248
|
+
if (this.started) {
|
|
2249
|
+
this.parts.push("Z");
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
build(lineWidth) {
|
|
2253
|
+
const d = lineWidth > 0 ? lineWidth / 2 : 0;
|
|
2254
|
+
return {
|
|
2255
|
+
path: this.parts.join(" "),
|
|
2256
|
+
bbox: {
|
|
2257
|
+
minX: this.bbox.minX - d,
|
|
2258
|
+
minY: this.bbox.minY - d,
|
|
2259
|
+
maxX: this.bbox.maxX + d,
|
|
2260
|
+
maxY: this.bbox.maxY + d
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
updateBBox(x, y) {
|
|
2265
|
+
if (x < this.bbox.minX) this.bbox.minX = x;
|
|
2266
|
+
if (y < this.bbox.minY) this.bbox.minY = y;
|
|
2267
|
+
if (x > this.bbox.maxX) this.bbox.maxX = x;
|
|
2268
|
+
if (y > this.bbox.maxY) this.bbox.maxY = y;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
function r(n) {
|
|
2272
|
+
return Number(n.toFixed(4)).toString();
|
|
2273
|
+
}
|
|
2274
|
+
function distance(a, b) {
|
|
2275
|
+
const dx = b.x - a.x;
|
|
2276
|
+
const dy = b.y - a.y;
|
|
2277
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
2278
|
+
}
|
|
2279
|
+
function cosine(dx, hypot) {
|
|
2280
|
+
return hypot === 0 ? 0 : dx / hypot;
|
|
2281
|
+
}
|
|
2282
|
+
function sine(dy, hypot) {
|
|
2283
|
+
return hypot === 0 ? 0 : dy / hypot;
|
|
2284
|
+
}
|
|
2285
|
+
function polygonDirection(pts) {
|
|
2286
|
+
let a = 0;
|
|
2287
|
+
const len = pts.length;
|
|
2288
|
+
for (let i = 0; i < len; i++) {
|
|
2289
|
+
const j = (i + 1) % len;
|
|
2290
|
+
a += pts[i].x * pts[j].y - pts[i].y * pts[j].x;
|
|
2291
|
+
}
|
|
2292
|
+
return a;
|
|
2293
|
+
}
|
|
2294
|
+
function ensurePositiveWinding(pts) {
|
|
2295
|
+
if (polygonDirection(pts) < 0) {
|
|
2296
|
+
pts.reverse();
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
function removeZeroLengthSegments(polygon) {
|
|
2300
|
+
if (polygon.length <= 2) return polygon;
|
|
2301
|
+
const tolerance = 0.5;
|
|
2302
|
+
const result = [polygon[0]];
|
|
2303
|
+
for (let i = 1; i < polygon.length; i++) {
|
|
2304
|
+
const prev = result[result.length - 1];
|
|
2305
|
+
const cur = polygon[i];
|
|
2306
|
+
if (Math.abs(cur.x - prev.x) >= tolerance || Math.abs(cur.y - prev.y) >= tolerance) {
|
|
2307
|
+
result.push(cur);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return result;
|
|
2311
|
+
}
|
|
2312
|
+
function arcSegment(startAng, endAng, cx, cy, rx, ry, out, addMoveTo) {
|
|
2313
|
+
const cosA = Math.cos(startAng);
|
|
2314
|
+
const sinA = Math.sin(startAng);
|
|
2315
|
+
const cosB = Math.cos(endAng);
|
|
2316
|
+
const sinB = Math.sin(endAng);
|
|
2317
|
+
const denom = Math.sin((endAng - startAng) / 2);
|
|
2318
|
+
if (denom === 0) {
|
|
2319
|
+
if (addMoveTo) {
|
|
2320
|
+
out.moveTo(cx + rx * cosA, cy + ry * sinA);
|
|
2321
|
+
}
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
const bcp = 4 / 3 * (1 - Math.cos((endAng - startAng) / 2)) / denom;
|
|
2325
|
+
const p1x = cx + rx * (cosA - bcp * sinA);
|
|
2326
|
+
const p1y = cy + ry * (sinA + bcp * cosA);
|
|
2327
|
+
const p2x = cx + rx * (cosB + bcp * sinB);
|
|
2328
|
+
const p2y = cy + ry * (sinB - bcp * cosB);
|
|
2329
|
+
const p3x = cx + rx * cosB;
|
|
2330
|
+
const p3y = cy + ry * sinB;
|
|
2331
|
+
if (addMoveTo) {
|
|
2332
|
+
out.moveTo(cx + rx * cosA, cy + ry * sinA);
|
|
2333
|
+
}
|
|
2334
|
+
out.curveTo(p1x, p1y, p2x, p2y, p3x, p3y);
|
|
2335
|
+
}
|
|
2336
|
+
function arcSegmentToArray(startAng, endAng, cx, cy, rx, ry) {
|
|
2337
|
+
const cosA = Math.cos(startAng);
|
|
2338
|
+
const sinA = Math.sin(startAng);
|
|
2339
|
+
const cosB = Math.cos(endAng);
|
|
2340
|
+
const sinB = Math.sin(endAng);
|
|
2341
|
+
const denom = Math.sin((endAng - startAng) / 2);
|
|
2342
|
+
if (denom === 0) return [];
|
|
2343
|
+
const bcp = 4 / 3 * (1 - Math.cos((endAng - startAng) / 2)) / denom;
|
|
2344
|
+
return [
|
|
2345
|
+
{ x: cx + rx * (cosA - bcp * sinA), y: cy + ry * (sinA + bcp * cosA) },
|
|
2346
|
+
{ x: cx + rx * (cosB + bcp * sinB), y: cy + ry * (sinB - bcp * cosB) },
|
|
2347
|
+
{ x: cx + rx * cosB, y: cy + ry * sinB }
|
|
2348
|
+
];
|
|
2349
|
+
}
|
|
2350
|
+
function getArc(startAng, endAng, rx, ry, cx, cy, out, addMoveTo) {
|
|
2351
|
+
const angleIncr = ANGLE_90;
|
|
2352
|
+
let angleTodo = endAng - startAng;
|
|
2353
|
+
while (angleTodo < 0) angleTodo += 2 * Math.PI;
|
|
2354
|
+
const sweep = angleTodo;
|
|
2355
|
+
let angleDone = 0;
|
|
2356
|
+
if (addMoveTo) {
|
|
2357
|
+
out.moveTo(cx + rx * Math.cos(startAng), cy + ry * Math.sin(startAng));
|
|
2358
|
+
}
|
|
2359
|
+
while (angleTodo > angleIncr) {
|
|
2360
|
+
arcSegment(startAng + angleDone, startAng + angleDone + angleIncr, cx, cy, rx, ry, out, false);
|
|
2361
|
+
angleDone += angleIncr;
|
|
2362
|
+
angleTodo -= angleIncr;
|
|
2363
|
+
}
|
|
2364
|
+
if (angleTodo > 0) {
|
|
2365
|
+
arcSegment(startAng + angleDone, startAng + sweep, cx, cy, rx, ry, out, false);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function addCornerCurl(anglePrev, angleCur, radius, cx, cy, alpha, alphaPrev, out, addMoveTo) {
|
|
2369
|
+
let a = anglePrev + ANGLE_180 + alphaPrev;
|
|
2370
|
+
const b = anglePrev + ANGLE_180 + alphaPrev - 22 * Math.PI / 180;
|
|
2371
|
+
arcSegment(a, b, cx, cy, radius, radius, out, addMoveTo);
|
|
2372
|
+
a = b;
|
|
2373
|
+
const bEnd = angleCur - alpha;
|
|
2374
|
+
getArc(a, bEnd, radius, radius, cx, cy, out, false);
|
|
2375
|
+
}
|
|
2376
|
+
function addFirstIntermediateCurl(angleCur, r2, alpha, cx, cy, out) {
|
|
2377
|
+
const a = angleCur + ANGLE_180;
|
|
2378
|
+
arcSegment(a + alpha, a + alpha - ANGLE_30, cx, cy, r2, r2, out, false);
|
|
2379
|
+
arcSegment(a + alpha - ANGLE_30, a + ANGLE_90, cx, cy, r2, r2, out, false);
|
|
2380
|
+
arcSegment(a + ANGLE_90, a + ANGLE_180 - ANGLE_34, cx, cy, r2, r2, out, false);
|
|
2381
|
+
}
|
|
2382
|
+
function getIntermediateCurlTemplate(angleCur, r2) {
|
|
2383
|
+
const pts = [];
|
|
2384
|
+
const a = angleCur + ANGLE_180;
|
|
2385
|
+
pts.push(...arcSegmentToArray(a + ANGLE_34, a + ANGLE_12, 0, 0, r2, r2));
|
|
2386
|
+
pts.push(...arcSegmentToArray(a + ANGLE_12, a + ANGLE_90, 0, 0, r2, r2));
|
|
2387
|
+
pts.push(...arcSegmentToArray(a + ANGLE_90, a + ANGLE_180 - ANGLE_34, 0, 0, r2, r2));
|
|
2388
|
+
return pts;
|
|
2389
|
+
}
|
|
2390
|
+
function outputCurlTemplate(template, x, y, out) {
|
|
2391
|
+
for (let i = 0; i + 2 < template.length; i += 3) {
|
|
2392
|
+
const a = template[i];
|
|
2393
|
+
const b = template[i + 1];
|
|
2394
|
+
const c = template[i + 2];
|
|
2395
|
+
out.curveTo(a.x + x, a.y + y, b.x + x, b.y + y, c.x + x, c.y + y);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
function computeParamsPolygon(idealRadius, k, length) {
|
|
2399
|
+
if (length === 0) return { n: -1, adjustedRadius: idealRadius };
|
|
2400
|
+
const cornerSpace = 2 * k * idealRadius;
|
|
2401
|
+
const remaining = length - cornerSpace;
|
|
2402
|
+
if (remaining <= 0) {
|
|
2403
|
+
return { n: 0, adjustedRadius: idealRadius };
|
|
2404
|
+
}
|
|
2405
|
+
const idealAdvance = 2 * k * idealRadius;
|
|
2406
|
+
const n = Math.max(1, Math.ceil(remaining / idealAdvance));
|
|
2407
|
+
const adjustedRadius = remaining / (n * 2 * k);
|
|
2408
|
+
return { n, adjustedRadius };
|
|
2409
|
+
}
|
|
2410
|
+
function cloudyPolygonImpl(vertices, isEllipse, intensity, lineWidth, out) {
|
|
2411
|
+
let polygon = removeZeroLengthSegments(vertices);
|
|
2412
|
+
ensurePositiveWinding(polygon);
|
|
2413
|
+
const numPoints = polygon.length;
|
|
2414
|
+
if (numPoints < 2) return;
|
|
2415
|
+
if (intensity <= 0) {
|
|
2416
|
+
out.moveTo(polygon[0].x, polygon[0].y);
|
|
2417
|
+
for (let i = 1; i < numPoints; i++) {
|
|
2418
|
+
out.curveTo(
|
|
2419
|
+
polygon[i].x,
|
|
2420
|
+
polygon[i].y,
|
|
2421
|
+
polygon[i].x,
|
|
2422
|
+
polygon[i].y,
|
|
2423
|
+
polygon[i].x,
|
|
2424
|
+
polygon[i].y
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
let idealRadius = isEllipse ? getEllipseCloudRadius(intensity, lineWidth) : getPolygonCloudRadius(intensity, lineWidth);
|
|
2430
|
+
if (idealRadius < 0.5) idealRadius = 0.5;
|
|
2431
|
+
const k = Math.cos(ANGLE_34);
|
|
2432
|
+
const edgeAlphas = [];
|
|
2433
|
+
for (let j = 0; j + 1 < numPoints; j++) {
|
|
2434
|
+
const len = distance(polygon[j], polygon[j + 1]);
|
|
2435
|
+
if (len <= 0 || len >= 2 * k * idealRadius) {
|
|
2436
|
+
edgeAlphas.push(ANGLE_34);
|
|
2437
|
+
} else {
|
|
2438
|
+
edgeAlphas.push(Math.acos(Math.min(1, len / (2 * idealRadius))));
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
let anglePrev = 0;
|
|
2442
|
+
let outputStarted = false;
|
|
2443
|
+
for (let j = 0; j + 1 < numPoints; j++) {
|
|
2444
|
+
const pt = polygon[j];
|
|
2445
|
+
const ptNext = polygon[j + 1];
|
|
2446
|
+
const len = distance(pt, ptNext);
|
|
2447
|
+
if (len === 0) continue;
|
|
2448
|
+
const params = computeParamsPolygon(idealRadius, k, len);
|
|
2449
|
+
if (params.n < 0) {
|
|
2450
|
+
if (!outputStarted) {
|
|
2451
|
+
out.moveTo(pt.x, pt.y);
|
|
2452
|
+
outputStarted = true;
|
|
2453
|
+
}
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
const edgeRadius = Math.max(0.5, params.adjustedRadius);
|
|
2457
|
+
const intermAdvance = 2 * k * edgeRadius;
|
|
2458
|
+
const firstAdvance = k * idealRadius + k * edgeRadius;
|
|
2459
|
+
let angleCur = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
|
|
2460
|
+
if (j === 0) {
|
|
2461
|
+
const ptPrev = polygon[numPoints - 2];
|
|
2462
|
+
anglePrev = Math.atan2(pt.y - ptPrev.y, pt.x - ptPrev.x);
|
|
2463
|
+
}
|
|
2464
|
+
const cos = cosine(ptNext.x - pt.x, len);
|
|
2465
|
+
const sin = sine(ptNext.y - pt.y, len);
|
|
2466
|
+
let x = pt.x;
|
|
2467
|
+
let y = pt.y;
|
|
2468
|
+
const alpha = edgeAlphas[j];
|
|
2469
|
+
const prevEdgeIdx = j === 0 ? numPoints - 2 : j - 1;
|
|
2470
|
+
const alphaPrevEdge = edgeAlphas[prevEdgeIdx] ?? ANGLE_34;
|
|
2471
|
+
addCornerCurl(
|
|
2472
|
+
anglePrev,
|
|
2473
|
+
angleCur,
|
|
2474
|
+
idealRadius,
|
|
2475
|
+
pt.x,
|
|
2476
|
+
pt.y,
|
|
2477
|
+
alpha,
|
|
2478
|
+
alphaPrevEdge,
|
|
2479
|
+
out,
|
|
2480
|
+
!outputStarted
|
|
2481
|
+
);
|
|
2482
|
+
outputStarted = true;
|
|
2483
|
+
if (params.n === 0) {
|
|
2484
|
+
x += len * cos;
|
|
2485
|
+
y += len * sin;
|
|
2486
|
+
} else {
|
|
2487
|
+
x += firstAdvance * cos;
|
|
2488
|
+
y += firstAdvance * sin;
|
|
2489
|
+
let numInterm = params.n;
|
|
2490
|
+
if (params.n >= 1) {
|
|
2491
|
+
addFirstIntermediateCurl(angleCur, edgeRadius, ANGLE_34, x, y, out);
|
|
2492
|
+
x += intermAdvance * cos;
|
|
2493
|
+
y += intermAdvance * sin;
|
|
2494
|
+
numInterm = params.n - 1;
|
|
2495
|
+
}
|
|
2496
|
+
const template = getIntermediateCurlTemplate(angleCur, edgeRadius);
|
|
2497
|
+
for (let i = 0; i < numInterm; i++) {
|
|
2498
|
+
outputCurlTemplate(template, x, y, out);
|
|
2499
|
+
x += intermAdvance * cos;
|
|
2500
|
+
y += intermAdvance * sin;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
anglePrev = angleCur;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
function flattenEllipse(left, bottom, right, top) {
|
|
2507
|
+
const cx = (left + right) / 2;
|
|
2508
|
+
const cy = (bottom + top) / 2;
|
|
2509
|
+
const rx = (right - left) / 2;
|
|
2510
|
+
const ry = (top - bottom) / 2;
|
|
2511
|
+
if (rx <= 0 || ry <= 0) return [];
|
|
2512
|
+
const numSegments = Math.max(32, Math.ceil(Math.max(rx, ry) * 2));
|
|
2513
|
+
const points = [];
|
|
2514
|
+
for (let i = 0; i <= numSegments; i++) {
|
|
2515
|
+
const angle = 2 * Math.PI * i / numSegments;
|
|
2516
|
+
points.push({
|
|
2517
|
+
x: cx + rx * Math.cos(angle),
|
|
2518
|
+
y: cy + ry * Math.sin(angle)
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
return points;
|
|
2522
|
+
}
|
|
2523
|
+
function getEllipseCloudRadius(intensity, lineWidth) {
|
|
2524
|
+
return 4.75 * intensity + 0.5 * lineWidth;
|
|
2525
|
+
}
|
|
2526
|
+
function getPolygonCloudRadius(intensity, lineWidth) {
|
|
2527
|
+
return 4 * intensity + 0.5 * lineWidth;
|
|
2528
|
+
}
|
|
2529
|
+
function cloudyEllipseImpl(left, bottom, right, top, intensity, lineWidth, out) {
|
|
2530
|
+
if (intensity <= 0) {
|
|
2531
|
+
const rx = Math.abs(right - left) / 2;
|
|
2532
|
+
const ry = Math.abs(top - bottom) / 2;
|
|
2533
|
+
const cx = (left + right) / 2;
|
|
2534
|
+
const cy = (bottom + top) / 2;
|
|
2535
|
+
getArc(0, 2 * Math.PI, rx, ry, cx, cy, out, true);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const width = right - left;
|
|
2539
|
+
const height = top - bottom;
|
|
2540
|
+
let cloudRadius = getEllipseCloudRadius(intensity, lineWidth);
|
|
2541
|
+
const threshold1 = 0.5 * cloudRadius;
|
|
2542
|
+
if (width < threshold1 && height < threshold1) {
|
|
2543
|
+
const rx = Math.abs(right - left) / 2;
|
|
2544
|
+
const ry = Math.abs(top - bottom) / 2;
|
|
2545
|
+
const cx = (left + right) / 2;
|
|
2546
|
+
const cy = (bottom + top) / 2;
|
|
2547
|
+
getArc(0, 2 * Math.PI, rx, ry, cx, cy, out, true);
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const threshold2 = 5;
|
|
2551
|
+
if (width < threshold2 && height > 20 || width > 20 && height < threshold2) {
|
|
2552
|
+
cloudyPolygonImpl(
|
|
2553
|
+
[
|
|
2554
|
+
{ x: left, y: bottom },
|
|
2555
|
+
{ x: right, y: bottom },
|
|
2556
|
+
{ x: right, y: top },
|
|
2557
|
+
{ x: left, y: top },
|
|
2558
|
+
{ x: left, y: bottom }
|
|
2559
|
+
],
|
|
2560
|
+
true,
|
|
2561
|
+
intensity,
|
|
2562
|
+
lineWidth,
|
|
2563
|
+
out
|
|
2564
|
+
);
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
const radiusAdj = Math.sin(ANGLE_12) * cloudRadius - 1.5;
|
|
2568
|
+
let adjLeft = left;
|
|
2569
|
+
let adjRight = right;
|
|
2570
|
+
let adjBottom = bottom;
|
|
2571
|
+
let adjTop = top;
|
|
2572
|
+
if (width > 2 * radiusAdj) {
|
|
2573
|
+
adjLeft += radiusAdj;
|
|
2574
|
+
adjRight -= radiusAdj;
|
|
2575
|
+
} else {
|
|
2576
|
+
const mid = (left + right) / 2;
|
|
2577
|
+
adjLeft = mid - 0.1;
|
|
2578
|
+
adjRight = mid + 0.1;
|
|
2579
|
+
}
|
|
2580
|
+
if (height > 2 * radiusAdj) {
|
|
2581
|
+
adjBottom += radiusAdj;
|
|
2582
|
+
adjTop -= radiusAdj;
|
|
2583
|
+
} else {
|
|
2584
|
+
const mid = (top + bottom) / 2;
|
|
2585
|
+
adjTop = mid + 0.1;
|
|
2586
|
+
adjBottom = mid - 0.1;
|
|
2587
|
+
}
|
|
2588
|
+
const flatPolygon = flattenEllipse(adjLeft, adjBottom, adjRight, adjTop);
|
|
2589
|
+
const numFlatPts = flatPolygon.length;
|
|
2590
|
+
if (numFlatPts < 2) return;
|
|
2591
|
+
let totLen = 0;
|
|
2592
|
+
for (let i = 1; i < numFlatPts; i++) {
|
|
2593
|
+
totLen += distance(flatPolygon[i - 1], flatPolygon[i]);
|
|
2594
|
+
}
|
|
2595
|
+
const k = Math.cos(ANGLE_34);
|
|
2596
|
+
let curlAdvance = 2 * k * cloudRadius;
|
|
2597
|
+
let n = Math.ceil(totLen / curlAdvance);
|
|
2598
|
+
if (n < 2) {
|
|
2599
|
+
const rx = Math.abs(right - left) / 2;
|
|
2600
|
+
const ry = Math.abs(top - bottom) / 2;
|
|
2601
|
+
const cx = (left + right) / 2;
|
|
2602
|
+
const cy = (bottom + top) / 2;
|
|
2603
|
+
getArc(0, 2 * Math.PI, rx, ry, cx, cy, out, true);
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
curlAdvance = totLen / n;
|
|
2607
|
+
cloudRadius = curlAdvance / (2 * k);
|
|
2608
|
+
if (cloudRadius < 0.5) {
|
|
2609
|
+
cloudRadius = 0.5;
|
|
2610
|
+
curlAdvance = 2 * k * cloudRadius;
|
|
2611
|
+
} else if (cloudRadius < 3) {
|
|
2612
|
+
const rx = Math.abs(right - left) / 2;
|
|
2613
|
+
const ry = Math.abs(top - bottom) / 2;
|
|
2614
|
+
const cx = (left + right) / 2;
|
|
2615
|
+
const cy = (bottom + top) / 2;
|
|
2616
|
+
getArc(0, 2 * Math.PI, rx, ry, cx, cy, out, true);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const centerPoints = [];
|
|
2620
|
+
let lengthRemain = 0;
|
|
2621
|
+
const comparisonToler = lineWidth * 0.1;
|
|
2622
|
+
for (let i = 0; i + 1 < numFlatPts; i++) {
|
|
2623
|
+
const p1 = flatPolygon[i];
|
|
2624
|
+
const p2 = flatPolygon[i + 1];
|
|
2625
|
+
const segDx = p2.x - p1.x;
|
|
2626
|
+
const segDy = p2.y - p1.y;
|
|
2627
|
+
const segLen = distance(p1, p2);
|
|
2628
|
+
if (segLen === 0) continue;
|
|
2629
|
+
let lengthTodo = segLen + lengthRemain;
|
|
2630
|
+
if (lengthTodo >= curlAdvance - comparisonToler || i === numFlatPts - 2) {
|
|
2631
|
+
const cos = cosine(segDx, segLen);
|
|
2632
|
+
const sin = sine(segDy, segLen);
|
|
2633
|
+
let d = curlAdvance - lengthRemain;
|
|
2634
|
+
while (lengthTodo >= curlAdvance - comparisonToler) {
|
|
2635
|
+
centerPoints.push({ x: p1.x + d * cos, y: p1.y + d * sin });
|
|
2636
|
+
lengthTodo -= curlAdvance;
|
|
2637
|
+
d += curlAdvance;
|
|
2638
|
+
}
|
|
2639
|
+
lengthRemain = Math.max(0, lengthTodo);
|
|
2640
|
+
} else {
|
|
2641
|
+
lengthRemain += segLen;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
const cpLen = centerPoints.length;
|
|
2645
|
+
let epAnglePrev = 0;
|
|
2646
|
+
let epAlphaPrev = 0;
|
|
2647
|
+
for (let i = 0; i < cpLen; i++) {
|
|
2648
|
+
const idxNext = (i + 1) % cpLen;
|
|
2649
|
+
const pt = centerPoints[i];
|
|
2650
|
+
const ptNext = centerPoints[idxNext];
|
|
2651
|
+
if (i === 0) {
|
|
2652
|
+
const ptPrev = centerPoints[cpLen - 1];
|
|
2653
|
+
epAnglePrev = Math.atan2(pt.y - ptPrev.y, pt.x - ptPrev.x);
|
|
2654
|
+
epAlphaPrev = computeParamsEllipse(ptPrev, pt, cloudRadius, curlAdvance);
|
|
2655
|
+
}
|
|
2656
|
+
const angleCur = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
|
|
2657
|
+
const alpha = computeParamsEllipse(pt, ptNext, cloudRadius, curlAdvance);
|
|
2658
|
+
addCornerCurl(epAnglePrev, angleCur, cloudRadius, pt.x, pt.y, alpha, epAlphaPrev, out, i === 0);
|
|
2659
|
+
epAnglePrev = angleCur;
|
|
2660
|
+
epAlphaPrev = alpha;
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
function computeParamsEllipse(pt, ptNext, r2, curlAdv) {
|
|
2664
|
+
const len = distance(pt, ptNext);
|
|
2665
|
+
if (len === 0) return ANGLE_34;
|
|
2666
|
+
const e = len - curlAdv;
|
|
2667
|
+
const arg = (curlAdv / 2 + e / 2) / r2;
|
|
2668
|
+
return arg < -1 || arg > 1 ? 0 : Math.acos(arg);
|
|
2669
|
+
}
|
|
2670
|
+
function getCloudyBorderExtent(intensity, lineWidth, isEllipse) {
|
|
2671
|
+
const cloudRadius = isEllipse ? getEllipseCloudRadius(intensity, lineWidth) : getPolygonCloudRadius(intensity, lineWidth);
|
|
2672
|
+
return cloudRadius + lineWidth / 2;
|
|
2673
|
+
}
|
|
2674
|
+
function generateCloudyRectanglePath(rect, rd, intensity, lineWidth) {
|
|
2675
|
+
const out = new PathBuilder();
|
|
2676
|
+
let left = 0;
|
|
2677
|
+
let top = 0;
|
|
2678
|
+
let right = rect.width;
|
|
2679
|
+
let bottom = rect.height;
|
|
2680
|
+
if (rd) {
|
|
2681
|
+
left += rd.left;
|
|
2682
|
+
top += rd.top;
|
|
2683
|
+
right -= rd.right;
|
|
2684
|
+
bottom -= rd.bottom;
|
|
2685
|
+
} else {
|
|
2686
|
+
left += lineWidth / 2;
|
|
2687
|
+
top += lineWidth / 2;
|
|
2688
|
+
right -= lineWidth / 2;
|
|
2689
|
+
bottom -= lineWidth / 2;
|
|
2690
|
+
}
|
|
2691
|
+
const polygon = [
|
|
2692
|
+
{ x: left, y: -top },
|
|
2693
|
+
{ x: right, y: -top },
|
|
2694
|
+
{ x: right, y: -bottom },
|
|
2695
|
+
{ x: left, y: -bottom },
|
|
2696
|
+
{ x: left, y: -top }
|
|
2697
|
+
];
|
|
2698
|
+
cloudyPolygonImpl(polygon, false, intensity, lineWidth, out);
|
|
2699
|
+
out.close();
|
|
2700
|
+
return out.build(lineWidth);
|
|
2701
|
+
}
|
|
2702
|
+
function generateCloudyEllipsePath(rect, rd, intensity, lineWidth) {
|
|
2703
|
+
const out = new PathBuilder();
|
|
2704
|
+
let left = 0;
|
|
2705
|
+
let top = 0;
|
|
2706
|
+
let right = rect.width;
|
|
2707
|
+
let bottom = rect.height;
|
|
2708
|
+
if (rd) {
|
|
2709
|
+
left += rd.left;
|
|
2710
|
+
top += rd.top;
|
|
2711
|
+
right -= rd.right;
|
|
2712
|
+
bottom -= rd.bottom;
|
|
2713
|
+
}
|
|
2714
|
+
cloudyEllipseImpl(left, -bottom, right, -top, intensity, lineWidth, out);
|
|
2715
|
+
out.close();
|
|
2716
|
+
return out.build(lineWidth);
|
|
2717
|
+
}
|
|
2718
|
+
function generateCloudyPolygonPath(vertices, rectOrigin, intensity, lineWidth) {
|
|
2719
|
+
const out = new PathBuilder();
|
|
2720
|
+
if (vertices.length < 3) {
|
|
2721
|
+
return out.build(lineWidth);
|
|
2722
|
+
}
|
|
2723
|
+
const localPts = vertices.map((v) => ({
|
|
2724
|
+
x: v.x - rectOrigin.x,
|
|
2725
|
+
y: -(v.y - rectOrigin.y)
|
|
2726
|
+
}));
|
|
2727
|
+
const first = localPts[0];
|
|
2728
|
+
const last = localPts[localPts.length - 1];
|
|
2729
|
+
if (first.x !== last.x || first.y !== last.y) {
|
|
2730
|
+
localPts.push({ x: first.x, y: first.y });
|
|
2731
|
+
}
|
|
2732
|
+
cloudyPolygonImpl(localPts, false, intensity, lineWidth, out);
|
|
2733
|
+
out.close();
|
|
2734
|
+
return out.build(lineWidth);
|
|
2735
|
+
}
|
|
2130
2736
|
const HANDLE_SIZE_PX = 14;
|
|
2131
2737
|
const polygonHandlerFactory = {
|
|
2132
2738
|
annotationType: PdfAnnotationSubtype.POLYGON,
|
|
@@ -2165,7 +2771,9 @@ const polygonHandlerFactory = {
|
|
|
2165
2771
|
if (vertices.length < 3) return;
|
|
2166
2772
|
const defaults = getDefaults();
|
|
2167
2773
|
if (!defaults) return;
|
|
2168
|
-
const
|
|
2774
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
2775
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
|
|
2776
|
+
const rect = expandRect(rectFromPoints(vertices), pad);
|
|
2169
2777
|
const anno = {
|
|
2170
2778
|
...defaults,
|
|
2171
2779
|
vertices,
|
|
@@ -2173,7 +2781,10 @@ const polygonHandlerFactory = {
|
|
|
2173
2781
|
type: PdfAnnotationSubtype.POLYGON,
|
|
2174
2782
|
pageIndex: context.pageIndex,
|
|
2175
2783
|
id: uuidV4(),
|
|
2176
|
-
created: /* @__PURE__ */ new Date()
|
|
2784
|
+
created: /* @__PURE__ */ new Date(),
|
|
2785
|
+
...intensity > 0 && {
|
|
2786
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
2787
|
+
}
|
|
2177
2788
|
};
|
|
2178
2789
|
onCommit(anno);
|
|
2179
2790
|
setVertices([]);
|
|
@@ -2186,8 +2797,10 @@ const polygonHandlerFactory = {
|
|
|
2186
2797
|
if (vertices.length === 0 || !currentPos) return null;
|
|
2187
2798
|
const defaults = getDefaults();
|
|
2188
2799
|
if (!defaults) return null;
|
|
2800
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
2801
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
|
|
2189
2802
|
const allPoints = [...vertices, currentPos];
|
|
2190
|
-
const bounds = expandRect(rectFromPoints(allPoints),
|
|
2803
|
+
const bounds = expandRect(rectFromPoints(allPoints), pad);
|
|
2191
2804
|
return {
|
|
2192
2805
|
type: PdfAnnotationSubtype.POLYGON,
|
|
2193
2806
|
bounds,
|
|
@@ -2273,10 +2886,11 @@ const squareHandlerFactory = {
|
|
|
2273
2886
|
const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
|
|
2274
2887
|
const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
|
|
2275
2888
|
const strokeWidth = defaults.strokeWidth;
|
|
2276
|
-
const
|
|
2889
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
2890
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
|
|
2277
2891
|
const rect = {
|
|
2278
|
-
origin: { x: x -
|
|
2279
|
-
size: { width: width +
|
|
2892
|
+
origin: { x: x - pad, y: y - pad },
|
|
2893
|
+
size: { width: width + 2 * pad, height: height + 2 * pad }
|
|
2280
2894
|
};
|
|
2281
2895
|
const anno = {
|
|
2282
2896
|
...defaults,
|
|
@@ -2284,7 +2898,10 @@ const squareHandlerFactory = {
|
|
|
2284
2898
|
created: /* @__PURE__ */ new Date(),
|
|
2285
2899
|
id: uuidV4(),
|
|
2286
2900
|
pageIndex,
|
|
2287
|
-
rect
|
|
2901
|
+
rect,
|
|
2902
|
+
...intensity > 0 && {
|
|
2903
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
2904
|
+
}
|
|
2288
2905
|
};
|
|
2289
2906
|
onCommit(anno);
|
|
2290
2907
|
}
|
|
@@ -2299,17 +2916,21 @@ const squareHandlerFactory = {
|
|
|
2299
2916
|
const defaults = getDefaults();
|
|
2300
2917
|
if (!defaults) return null;
|
|
2301
2918
|
const strokeWidth = defaults.strokeWidth;
|
|
2302
|
-
const
|
|
2919
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
2920
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
|
|
2303
2921
|
const rect = {
|
|
2304
|
-
origin: { x: minX -
|
|
2305
|
-
size: { width: width +
|
|
2922
|
+
origin: { x: minX - pad, y: minY - pad },
|
|
2923
|
+
size: { width: width + 2 * pad, height: height + 2 * pad }
|
|
2306
2924
|
};
|
|
2307
2925
|
return {
|
|
2308
2926
|
type: PdfAnnotationSubtype.SQUARE,
|
|
2309
2927
|
bounds: rect,
|
|
2310
2928
|
data: {
|
|
2311
2929
|
rect,
|
|
2312
|
-
...defaults
|
|
2930
|
+
...defaults,
|
|
2931
|
+
...intensity > 0 && {
|
|
2932
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
2933
|
+
}
|
|
2313
2934
|
}
|
|
2314
2935
|
};
|
|
2315
2936
|
};
|
|
@@ -2343,13 +2964,18 @@ const squareHandlerFactory = {
|
|
|
2343
2964
|
if (!defaults2) return;
|
|
2344
2965
|
const preview = getPreview(clampedPos);
|
|
2345
2966
|
if (preview) {
|
|
2967
|
+
const intensity = defaults2.cloudyBorderIntensity ?? 0;
|
|
2968
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, false) : void 0;
|
|
2346
2969
|
const anno = {
|
|
2347
2970
|
...defaults2,
|
|
2348
2971
|
type: PdfAnnotationSubtype.SQUARE,
|
|
2349
2972
|
created: /* @__PURE__ */ new Date(),
|
|
2350
2973
|
id: uuidV4(),
|
|
2351
2974
|
pageIndex,
|
|
2352
|
-
rect: preview.data.rect
|
|
2975
|
+
rect: preview.data.rect,
|
|
2976
|
+
...pad !== void 0 && {
|
|
2977
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
2978
|
+
}
|
|
2353
2979
|
};
|
|
2354
2980
|
onCommit(anno);
|
|
2355
2981
|
}
|
|
@@ -2473,10 +3099,11 @@ const circleHandlerFactory = {
|
|
|
2473
3099
|
const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
|
|
2474
3100
|
const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
|
|
2475
3101
|
const strokeWidth = defaults.strokeWidth;
|
|
2476
|
-
const
|
|
3102
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
3103
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
|
|
2477
3104
|
const rect = {
|
|
2478
|
-
origin: { x: x -
|
|
2479
|
-
size: { width: width +
|
|
3105
|
+
origin: { x: x - pad, y: y - pad },
|
|
3106
|
+
size: { width: width + 2 * pad, height: height + 2 * pad }
|
|
2480
3107
|
};
|
|
2481
3108
|
const anno = {
|
|
2482
3109
|
...defaults,
|
|
@@ -2484,7 +3111,10 @@ const circleHandlerFactory = {
|
|
|
2484
3111
|
created: /* @__PURE__ */ new Date(),
|
|
2485
3112
|
id: uuidV4(),
|
|
2486
3113
|
pageIndex,
|
|
2487
|
-
rect
|
|
3114
|
+
rect,
|
|
3115
|
+
...intensity > 0 && {
|
|
3116
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
3117
|
+
}
|
|
2488
3118
|
};
|
|
2489
3119
|
onCommit(anno);
|
|
2490
3120
|
}
|
|
@@ -2499,17 +3129,21 @@ const circleHandlerFactory = {
|
|
|
2499
3129
|
const defaults = getDefaults();
|
|
2500
3130
|
if (!defaults) return null;
|
|
2501
3131
|
const strokeWidth = defaults.strokeWidth;
|
|
2502
|
-
const
|
|
3132
|
+
const intensity = defaults.cloudyBorderIntensity ?? 0;
|
|
3133
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
|
|
2503
3134
|
const rect = {
|
|
2504
|
-
origin: { x: minX -
|
|
2505
|
-
size: { width: width +
|
|
3135
|
+
origin: { x: minX - pad, y: minY - pad },
|
|
3136
|
+
size: { width: width + 2 * pad, height: height + 2 * pad }
|
|
2506
3137
|
};
|
|
2507
3138
|
return {
|
|
2508
3139
|
type: PdfAnnotationSubtype.CIRCLE,
|
|
2509
3140
|
bounds: rect,
|
|
2510
3141
|
data: {
|
|
2511
3142
|
rect,
|
|
2512
|
-
...defaults
|
|
3143
|
+
...defaults,
|
|
3144
|
+
...intensity > 0 && {
|
|
3145
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
3146
|
+
}
|
|
2513
3147
|
}
|
|
2514
3148
|
};
|
|
2515
3149
|
};
|
|
@@ -2543,6 +3177,8 @@ const circleHandlerFactory = {
|
|
|
2543
3177
|
if (!defaults2) return;
|
|
2544
3178
|
const preview = getPreview(clampedPos);
|
|
2545
3179
|
if (preview) {
|
|
3180
|
+
const intensity = defaults2.cloudyBorderIntensity ?? 0;
|
|
3181
|
+
const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, true) : void 0;
|
|
2546
3182
|
const anno = {
|
|
2547
3183
|
...defaults2,
|
|
2548
3184
|
type: PdfAnnotationSubtype.CIRCLE,
|
|
@@ -2550,7 +3186,10 @@ const circleHandlerFactory = {
|
|
|
2550
3186
|
created: /* @__PURE__ */ new Date(),
|
|
2551
3187
|
id: uuidV4(),
|
|
2552
3188
|
pageIndex,
|
|
2553
|
-
rect: preview.data.rect
|
|
3189
|
+
rect: preview.data.rect,
|
|
3190
|
+
...pad !== void 0 && {
|
|
3191
|
+
rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
|
|
3192
|
+
}
|
|
2554
3193
|
};
|
|
2555
3194
|
onCommit(anno);
|
|
2556
3195
|
}
|
|
@@ -2722,23 +3361,29 @@ const patchInk = (original, ctx) => {
|
|
|
2722
3361
|
}
|
|
2723
3362
|
case "resize": {
|
|
2724
3363
|
if (!ctx.changes.rect) return ctx.changes;
|
|
2725
|
-
const { oldRect, resolvedRect, rects } = baseResizeScaling(
|
|
3364
|
+
const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
|
|
2726
3365
|
original,
|
|
2727
3366
|
ctx.changes.rect,
|
|
2728
3367
|
ctx.metadata
|
|
2729
3368
|
);
|
|
2730
|
-
const inset = (
|
|
2731
|
-
origin: { x:
|
|
3369
|
+
const inset = (r2, pad) => ({
|
|
3370
|
+
origin: { x: r2.origin.x + pad, y: r2.origin.y + pad },
|
|
2732
3371
|
size: {
|
|
2733
|
-
width: Math.max(1,
|
|
2734
|
-
height: Math.max(1,
|
|
3372
|
+
width: Math.max(1, r2.size.width - pad * 2),
|
|
3373
|
+
height: Math.max(1, r2.size.height - pad * 2)
|
|
2735
3374
|
}
|
|
2736
3375
|
});
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
3376
|
+
const resizeEpsilon = 1e-3;
|
|
3377
|
+
const widthChanged = Math.abs(scaleX - 1) > resizeEpsilon;
|
|
3378
|
+
const heightChanged = Math.abs(scaleY - 1) > resizeEpsilon;
|
|
3379
|
+
const strokeScale = widthChanged && !heightChanged ? scaleX : !widthChanged && heightChanged ? scaleY : Math.min(scaleX, scaleY);
|
|
3380
|
+
const rawStrokeWidth = Math.max(1, original.strokeWidth * strokeScale);
|
|
3381
|
+
const maxStrokeWidth = Math.max(
|
|
3382
|
+
1,
|
|
3383
|
+
Math.min(resolvedRect.size.width, resolvedRect.size.height)
|
|
2740
3384
|
);
|
|
2741
|
-
const
|
|
3385
|
+
const clampedStrokeWidth = Math.min(rawStrokeWidth, maxStrokeWidth);
|
|
3386
|
+
const newStrokeWidth = Number(clampedStrokeWidth.toFixed(1));
|
|
2742
3387
|
const innerOld = inset(oldRect, original.strokeWidth / 2);
|
|
2743
3388
|
const innerNew = inset(resolvedRect, newStrokeWidth / 2);
|
|
2744
3389
|
const sx = innerNew.size.width / Math.max(innerOld.size.width, 1e-6);
|
|
@@ -2948,11 +3593,17 @@ const patchPolyline = (orig, ctx) => {
|
|
|
2948
3593
|
return ctx.changes;
|
|
2949
3594
|
}
|
|
2950
3595
|
};
|
|
3596
|
+
function getPolygonPad(intensity, strokeWidth) {
|
|
3597
|
+
if ((intensity ?? 0) > 0) {
|
|
3598
|
+
return getCloudyBorderExtent(intensity, strokeWidth, false);
|
|
3599
|
+
}
|
|
3600
|
+
return strokeWidth / 2;
|
|
3601
|
+
}
|
|
2951
3602
|
const patchPolygon = (orig, ctx) => {
|
|
2952
3603
|
switch (ctx.type) {
|
|
2953
3604
|
case "vertex-edit":
|
|
2954
3605
|
if (ctx.changes.vertices && ctx.changes.vertices.length) {
|
|
2955
|
-
const pad = orig.strokeWidth
|
|
3606
|
+
const pad = getPolygonPad(orig.cloudyBorderIntensity, orig.strokeWidth);
|
|
2956
3607
|
const rawVertices = ctx.changes.vertices;
|
|
2957
3608
|
const rawRect = expandRect(rectFromPoints(rawVertices), pad);
|
|
2958
3609
|
const compensated = compensateRotatedVertexEdit(orig, rawVertices, rawRect);
|
|
@@ -2996,15 +3647,30 @@ const patchPolygon = (orig, ctx) => {
|
|
|
2996
3647
|
};
|
|
2997
3648
|
}
|
|
2998
3649
|
case "property-update": {
|
|
2999
|
-
const
|
|
3650
|
+
const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
|
|
3651
|
+
const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.rotation !== void 0 || cloudyChanged;
|
|
3000
3652
|
if (!needsRectUpdate) return ctx.changes;
|
|
3001
3653
|
const merged = { ...orig, ...ctx.changes };
|
|
3002
|
-
const pad = merged.strokeWidth
|
|
3654
|
+
const pad = getPolygonPad(merged.cloudyBorderIntensity, merged.strokeWidth);
|
|
3003
3655
|
const tightRect = expandRect(rectFromPoints(merged.vertices), pad);
|
|
3656
|
+
let patch = ctx.changes;
|
|
3657
|
+
const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
|
|
3658
|
+
if (cloudyChanged || ctx.changes.strokeWidth !== void 0 && hasCloudy) {
|
|
3659
|
+
const intensity = merged.cloudyBorderIntensity ?? 0;
|
|
3660
|
+
if (intensity > 0) {
|
|
3661
|
+
const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
|
|
3662
|
+
patch = {
|
|
3663
|
+
...patch,
|
|
3664
|
+
rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
|
|
3665
|
+
};
|
|
3666
|
+
} else {
|
|
3667
|
+
patch = { ...patch, rectangleDifferences: void 0 };
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3004
3670
|
const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
|
|
3005
3671
|
if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
|
|
3006
3672
|
return {
|
|
3007
|
-
...
|
|
3673
|
+
...patch,
|
|
3008
3674
|
unrotatedRect: tightRect,
|
|
3009
3675
|
rect: calculateRotatedRectAABBAroundPoint(
|
|
3010
3676
|
tightRect,
|
|
@@ -3013,7 +3679,7 @@ const patchPolygon = (orig, ctx) => {
|
|
|
3013
3679
|
)
|
|
3014
3680
|
};
|
|
3015
3681
|
}
|
|
3016
|
-
return { ...
|
|
3682
|
+
return { ...patch, rect: tightRect };
|
|
3017
3683
|
}
|
|
3018
3684
|
default:
|
|
3019
3685
|
return ctx.changes;
|
|
@@ -3029,11 +3695,29 @@ const patchCircle = (orig, ctx) => {
|
|
|
3029
3695
|
return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
|
|
3030
3696
|
case "rotate":
|
|
3031
3697
|
return baseRotateChanges(orig, ctx) ?? ctx.changes;
|
|
3032
|
-
case "property-update":
|
|
3698
|
+
case "property-update": {
|
|
3699
|
+
let patch = ctx.changes;
|
|
3700
|
+
const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
|
|
3701
|
+
const strokeChanged = ctx.changes.strokeWidth !== void 0;
|
|
3702
|
+
const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
|
|
3703
|
+
if (cloudyChanged || strokeChanged && hasCloudy) {
|
|
3704
|
+
const merged = { ...orig, ...ctx.changes };
|
|
3705
|
+
const intensity = merged.cloudyBorderIntensity ?? 0;
|
|
3706
|
+
if (intensity > 0) {
|
|
3707
|
+
const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, true);
|
|
3708
|
+
patch = {
|
|
3709
|
+
...patch,
|
|
3710
|
+
rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
|
|
3711
|
+
};
|
|
3712
|
+
} else {
|
|
3713
|
+
patch = { ...patch, rectangleDifferences: void 0 };
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3033
3716
|
if (ctx.changes.rotation !== void 0) {
|
|
3034
|
-
|
|
3717
|
+
patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
|
|
3035
3718
|
}
|
|
3036
|
-
return
|
|
3719
|
+
return patch;
|
|
3720
|
+
}
|
|
3037
3721
|
default:
|
|
3038
3722
|
return ctx.changes;
|
|
3039
3723
|
}
|
|
@@ -3048,11 +3732,29 @@ const patchSquare = (orig, ctx) => {
|
|
|
3048
3732
|
return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
|
|
3049
3733
|
case "rotate":
|
|
3050
3734
|
return baseRotateChanges(orig, ctx) ?? ctx.changes;
|
|
3051
|
-
case "property-update":
|
|
3735
|
+
case "property-update": {
|
|
3736
|
+
let patch = ctx.changes;
|
|
3737
|
+
const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
|
|
3738
|
+
const strokeChanged = ctx.changes.strokeWidth !== void 0;
|
|
3739
|
+
const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
|
|
3740
|
+
if (cloudyChanged || strokeChanged && hasCloudy) {
|
|
3741
|
+
const merged = { ...orig, ...ctx.changes };
|
|
3742
|
+
const intensity = merged.cloudyBorderIntensity ?? 0;
|
|
3743
|
+
if (intensity > 0) {
|
|
3744
|
+
const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
|
|
3745
|
+
patch = {
|
|
3746
|
+
...patch,
|
|
3747
|
+
rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
|
|
3748
|
+
};
|
|
3749
|
+
} else {
|
|
3750
|
+
patch = { ...patch, rectangleDifferences: void 0 };
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3052
3753
|
if (ctx.changes.rotation !== void 0) {
|
|
3053
|
-
|
|
3754
|
+
patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
|
|
3054
3755
|
}
|
|
3055
|
-
return
|
|
3756
|
+
return patch;
|
|
3757
|
+
}
|
|
3056
3758
|
default:
|
|
3057
3759
|
return ctx.changes;
|
|
3058
3760
|
}
|
|
@@ -3095,34 +3797,6 @@ const patchStamp = (orig, ctx) => {
|
|
|
3095
3797
|
return ctx.changes;
|
|
3096
3798
|
}
|
|
3097
3799
|
};
|
|
3098
|
-
function convertAABBRectToUnrotatedSpace(newAABBRect, originalAABBRect, originalUnrotatedRect, rotationDegrees) {
|
|
3099
|
-
const theta = rotationDegrees * Math.PI / 180;
|
|
3100
|
-
const A = Math.abs(Math.cos(theta));
|
|
3101
|
-
const B = Math.abs(Math.sin(theta));
|
|
3102
|
-
const det = A * A - B * B;
|
|
3103
|
-
const newAABBw = newAABBRect.size.width;
|
|
3104
|
-
const newAABBh = newAABBRect.size.height;
|
|
3105
|
-
let newWidth;
|
|
3106
|
-
let newHeight;
|
|
3107
|
-
if (Math.abs(det) > 1e-6) {
|
|
3108
|
-
newWidth = (A * newAABBw - B * newAABBh) / det;
|
|
3109
|
-
newHeight = (A * newAABBh - B * newAABBw) / det;
|
|
3110
|
-
newWidth = Math.max(newWidth, 1);
|
|
3111
|
-
newHeight = Math.max(newHeight, 1);
|
|
3112
|
-
} else {
|
|
3113
|
-
const origArea = originalAABBRect.size.width * originalAABBRect.size.height;
|
|
3114
|
-
const newArea = newAABBw * newAABBh;
|
|
3115
|
-
const uniformScale = origArea > 0 ? Math.sqrt(newArea / origArea) : 1;
|
|
3116
|
-
newWidth = originalUnrotatedRect.size.width * uniformScale;
|
|
3117
|
-
newHeight = originalUnrotatedRect.size.height * uniformScale;
|
|
3118
|
-
}
|
|
3119
|
-
const newCenterX = newAABBRect.origin.x + newAABBw / 2;
|
|
3120
|
-
const newCenterY = newAABBRect.origin.y + newAABBh / 2;
|
|
3121
|
-
return {
|
|
3122
|
-
origin: { x: newCenterX - newWidth / 2, y: newCenterY - newHeight / 2 },
|
|
3123
|
-
size: { width: newWidth, height: newHeight }
|
|
3124
|
-
};
|
|
3125
|
-
}
|
|
3126
3800
|
const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
|
|
3127
3801
|
constructor(id, registry, config) {
|
|
3128
3802
|
var _a, _b, _c;
|
|
@@ -3476,12 +4150,13 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
|
|
|
3476
4150
|
// Pass through services
|
|
3477
4151
|
onPreview: (state) => callbacks.onPreview(tool.id, state),
|
|
3478
4152
|
onCommit: (annotation, ctx) => {
|
|
3479
|
-
var _a2, _b;
|
|
3480
|
-
|
|
3481
|
-
|
|
4153
|
+
var _a2, _b, _c;
|
|
4154
|
+
const editAfterCreate = ((_a2 = tool.behavior) == null ? void 0 : _a2.editAfterCreate) ?? false;
|
|
4155
|
+
this.createAnnotation(pageIndex, annotation, ctx, documentId, { editAfterCreate });
|
|
4156
|
+
if ((_b = tool.behavior) == null ? void 0 : _b.deactivateToolAfterCreate) {
|
|
3482
4157
|
this.setActiveTool(null, documentId);
|
|
3483
4158
|
}
|
|
3484
|
-
if ((
|
|
4159
|
+
if (((_c = tool.behavior) == null ? void 0 : _c.selectAfterCreate) || editAfterCreate) {
|
|
3485
4160
|
this.selectAnnotation(pageIndex, annotation.id, documentId);
|
|
3486
4161
|
}
|
|
3487
4162
|
},
|
|
@@ -3651,7 +4326,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
|
|
|
3651
4326
|
}
|
|
3652
4327
|
if (this.config.autoCommit !== false) this.commit(documentId);
|
|
3653
4328
|
}
|
|
3654
|
-
createAnnotation(pageIndex, annotation, ctx, documentId) {
|
|
4329
|
+
createAnnotation(pageIndex, annotation, ctx, documentId, options) {
|
|
3655
4330
|
const docId = documentId ?? this.getActiveDocumentId();
|
|
3656
4331
|
if (!this.checkPermission(docId, PdfPermissionFlag.ModifyAnnotations)) {
|
|
3657
4332
|
this.logger.debug(
|
|
@@ -3668,6 +4343,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
|
|
|
3668
4343
|
...annotation,
|
|
3669
4344
|
author: annotation.author ?? this.config.annotationAuthor
|
|
3670
4345
|
};
|
|
4346
|
+
const editAfterCreate = options == null ? void 0 : options.editAfterCreate;
|
|
3671
4347
|
const execute = () => {
|
|
3672
4348
|
this.dispatch(createAnnotation(docId, pageIndex, newAnnotation));
|
|
3673
4349
|
if (ctx) contexts.set(id, ctx);
|
|
@@ -3677,7 +4353,8 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
|
|
|
3677
4353
|
annotation: newAnnotation,
|
|
3678
4354
|
pageIndex,
|
|
3679
4355
|
ctx,
|
|
3680
|
-
committed: false
|
|
4356
|
+
committed: false,
|
|
4357
|
+
editAfterCreate
|
|
3681
4358
|
});
|
|
3682
4359
|
};
|
|
3683
4360
|
if (!this.history) {
|
|
@@ -5131,10 +5808,14 @@ export {
|
|
|
5131
5808
|
calculateRotatedRectAABBAroundPoint2 as calculateRotatedRectAABBAroundPoint,
|
|
5132
5809
|
convertAABBRectToUnrotatedSpace,
|
|
5133
5810
|
createToolPredicate,
|
|
5811
|
+
generateCloudyEllipsePath,
|
|
5812
|
+
generateCloudyPolygonPath,
|
|
5813
|
+
generateCloudyRectanglePath,
|
|
5134
5814
|
getAnnotationByUid,
|
|
5135
5815
|
getAnnotations,
|
|
5136
5816
|
getAnnotationsByPageIndex,
|
|
5137
5817
|
getAttachedLinks,
|
|
5818
|
+
getCloudyBorderExtent,
|
|
5138
5819
|
getGroupLeaderId,
|
|
5139
5820
|
getGroupMembers,
|
|
5140
5821
|
getIRTChildIds,
|