dsh-figmark 1.1.0 → 1.1.2
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/lib/client.js +535 -169
- package/lib/client.pdf.js +20 -15
- package/market/index.json +2 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -26,6 +26,7 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
clear: "清空",
|
|
27
27
|
detail: "这个元素的详细说明",
|
|
28
28
|
addComment: "添加评论…",
|
|
29
|
+
pinSize: "标记大小",
|
|
29
30
|
weight: "线条粗细",
|
|
30
31
|
detailPlaceholder: "这一处具体要改什么(例如「这个标号应指向滑套 34」)",
|
|
31
32
|
summary: "总体目标",
|
|
@@ -113,6 +114,7 @@ window.__ModuleLoader__.load({
|
|
|
113
114
|
clear: "Clear",
|
|
114
115
|
detail: "What this element needs",
|
|
115
116
|
addComment: "Add a comment…",
|
|
117
|
+
pinSize: "Marker size",
|
|
116
118
|
weight: "Line weight",
|
|
117
119
|
detailPlaceholder: "What should change here (for example \"this numeral should point at the sleeve 34\")",
|
|
118
120
|
summary: "Overall goal",
|
|
@@ -1013,7 +1015,7 @@ window.__ModuleLoader__.load({
|
|
|
1013
1015
|
}
|
|
1014
1016
|
//#endregion
|
|
1015
1017
|
//#region src/client/render.ts
|
|
1016
|
-
/** Stroke width of
|
|
1018
|
+
/** Stroke width of a mark drawn at weight 1 on a reference-sized figure, in pixels. */
|
|
1017
1019
|
const STROKE_WIDTH = 2.5;
|
|
1018
1020
|
/**
|
|
1019
1021
|
* The stroke width one mark is drawn at.
|
|
@@ -1023,17 +1025,36 @@ window.__ModuleLoader__.load({
|
|
|
1023
1025
|
* thicker pen. The arrow's barbs are weighed by the same multiple, because an
|
|
1024
1026
|
* arrow drawn with a thick pen has a thick head.
|
|
1025
1027
|
*
|
|
1028
|
+
* The figure's own unit multiplies everything drawn on it, so the same mark is a
|
|
1029
|
+
* bold line on a thumbnail and a hairline on a scan — see `./scale`.
|
|
1030
|
+
*
|
|
1026
1031
|
* @param mark - the mark to weigh.
|
|
1032
|
+
* @param unit - the figure's size multiple, 1 for a reference-sized figure.
|
|
1027
1033
|
* @returns its stroke width in figure pixels.
|
|
1028
1034
|
*/
|
|
1029
|
-
function strokeWidthOf(mark) {
|
|
1030
|
-
return STROKE_WIDTH * (mark.weight ?? 1);
|
|
1035
|
+
function strokeWidthOf(mark, unit = 1) {
|
|
1036
|
+
return STROKE_WIDTH * (mark.weight ?? 1) * unit;
|
|
1031
1037
|
}
|
|
1032
1038
|
/** Arrow head length, in figure pixels. */
|
|
1033
1039
|
const ARROW_HEAD = 14;
|
|
1034
1040
|
/** Half-angle of the arrow head, in radians. */
|
|
1035
1041
|
const ARROW_SPREAD = Math.PI / 7;
|
|
1036
1042
|
/**
|
|
1043
|
+
* Half-width of one pin's head, in figure pixels.
|
|
1044
|
+
*
|
|
1045
|
+
* A pin is a marker the reader compares against the line work, so it is sized like a
|
|
1046
|
+
* stroke: by the figure's unit and by the mark's own weight. The weight is reused
|
|
1047
|
+
* rather than given its own field because it already means "how big this mark is" to
|
|
1048
|
+
* the reader, and one rail on the left is enough — a stroke and a pin are never the
|
|
1049
|
+
* same mark, so the one number never has to mean two things at once.
|
|
1050
|
+
* @param mark - the point mark.
|
|
1051
|
+
* @param unit - the figure's size multiple.
|
|
1052
|
+
* @returns its head's half-width in figure pixels.
|
|
1053
|
+
*/
|
|
1054
|
+
function pinRadiusOf(mark, unit = 1) {
|
|
1055
|
+
return 10 * (mark.weight ?? 1) * unit;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1037
1058
|
* How far a pin's head sits above its coordinate, as a multiple of its half-width.
|
|
1038
1059
|
*
|
|
1039
1060
|
* A map pin puts its *tip* on the coordinate it names, so the head stands clear of
|
|
@@ -1062,6 +1083,20 @@ window.__ModuleLoader__.load({
|
|
|
1062
1083
|
* every colour the palette offers.
|
|
1063
1084
|
*/
|
|
1064
1085
|
const PIN_NUMBER_COLOR = "#ffffff";
|
|
1086
|
+
/**
|
|
1087
|
+
* Font size of the number inside a pin, in figure pixels.
|
|
1088
|
+
*
|
|
1089
|
+
* Scaled by the mark's weight as well as the figure: the rail that makes a pin bigger
|
|
1090
|
+
* makes its head bigger, and a head that grows around a fixed-size numeral reads as a
|
|
1091
|
+
* circle with a speck in it. The number is the pin's whole payload, so it keeps the
|
|
1092
|
+
* same share of the head at every size.
|
|
1093
|
+
* @param mark - the point mark whose pin is being drawn.
|
|
1094
|
+
* @param unit - the figure's size multiple.
|
|
1095
|
+
* @returns the font size to draw the number at.
|
|
1096
|
+
*/
|
|
1097
|
+
function pinFontSizeOf(mark, unit = 1) {
|
|
1098
|
+
return 11 * (mark.weight ?? 1) * unit;
|
|
1099
|
+
}
|
|
1065
1100
|
/** Normalize two opposite corners into a top-left origin and positive extents. */
|
|
1066
1101
|
function bounds(first, second) {
|
|
1067
1102
|
return {
|
|
@@ -1111,11 +1146,11 @@ window.__ModuleLoader__.load({
|
|
|
1111
1146
|
* @param points - the arrow's tail and tip.
|
|
1112
1147
|
* @returns tail, tip and both barbs; the stored points alone when it is half drawn.
|
|
1113
1148
|
*/
|
|
1114
|
-
function arrowBarbs(points, weight = 1) {
|
|
1149
|
+
function arrowBarbs(points, weight = 1, unit = 1) {
|
|
1115
1150
|
const [first, second] = points;
|
|
1116
1151
|
if (first === void 0 || second === void 0) return points;
|
|
1117
1152
|
const angle = Math.atan2(second[1] - first[1], second[0] - first[0]);
|
|
1118
|
-
const head = ARROW_HEAD * weight;
|
|
1153
|
+
const head = ARROW_HEAD * weight * unit;
|
|
1119
1154
|
const left = [second[0] - head * Math.cos(angle - ARROW_SPREAD), second[1] - head * Math.sin(angle - ARROW_SPREAD)];
|
|
1120
1155
|
const right = [second[0] - head * Math.cos(angle + ARROW_SPREAD), second[1] - head * Math.sin(angle + ARROW_SPREAD)];
|
|
1121
1156
|
return [
|
|
@@ -1170,10 +1205,10 @@ window.__ModuleLoader__.load({
|
|
|
1170
1205
|
* @param mark - the point mark.
|
|
1171
1206
|
* @returns the marker's path, number centre and box, or undefined with no coordinate.
|
|
1172
1207
|
*/
|
|
1173
|
-
function pinGeometry(mark) {
|
|
1208
|
+
function pinGeometry(mark, unit = 1) {
|
|
1174
1209
|
const tip = mark.points[0];
|
|
1175
1210
|
if (tip === void 0) return void 0;
|
|
1176
|
-
const radius =
|
|
1211
|
+
const radius = pinRadiusOf(mark, unit);
|
|
1177
1212
|
const lift = radius * PIN_LIFT;
|
|
1178
1213
|
const [tipX, tipY] = tip;
|
|
1179
1214
|
const up = tipY - lift - radius >= 0;
|
|
@@ -1207,16 +1242,16 @@ window.__ModuleLoader__.load({
|
|
|
1207
1242
|
* @param mark - the point mark.
|
|
1208
1243
|
* @returns path data, or undefined when the mark carries no coordinate.
|
|
1209
1244
|
*/
|
|
1210
|
-
function pinPathData(mark) {
|
|
1211
|
-
return pinGeometry(mark)?.path;
|
|
1245
|
+
function pinPathData(mark, unit = 1) {
|
|
1246
|
+
return pinGeometry(mark, unit)?.path;
|
|
1212
1247
|
}
|
|
1213
1248
|
/**
|
|
1214
1249
|
* Where the number inside a `point` mark's marker sits.
|
|
1215
1250
|
* @param mark - the point mark.
|
|
1216
1251
|
* @returns the number's centre, or undefined when the mark carries no coordinate.
|
|
1217
1252
|
*/
|
|
1218
|
-
function pinNumberCentre(mark) {
|
|
1219
|
-
return pinGeometry(mark)?.number;
|
|
1253
|
+
function pinNumberCentre(mark, unit = 1) {
|
|
1254
|
+
return pinGeometry(mark, unit)?.number;
|
|
1220
1255
|
}
|
|
1221
1256
|
/**
|
|
1222
1257
|
* The box a `point` mark's pin covers.
|
|
@@ -1229,8 +1264,8 @@ window.__ModuleLoader__.load({
|
|
|
1229
1264
|
* @param mark - the point mark.
|
|
1230
1265
|
* @returns the pin's box, or undefined when the mark carries no point.
|
|
1231
1266
|
*/
|
|
1232
|
-
function pointMarkBox(mark) {
|
|
1233
|
-
return pinGeometry(mark)?.box;
|
|
1267
|
+
function pointMarkBox(mark, unit = 1) {
|
|
1268
|
+
return pinGeometry(mark, unit)?.box;
|
|
1234
1269
|
}
|
|
1235
1270
|
/**
|
|
1236
1271
|
* The box a mark covers once it is drawn, rotation included.
|
|
@@ -1243,14 +1278,14 @@ window.__ModuleLoader__.load({
|
|
|
1243
1278
|
* @param mark - the mark to measure.
|
|
1244
1279
|
* @returns the box, or undefined when the mark carries no coordinate.
|
|
1245
1280
|
*/
|
|
1246
|
-
function markOutline(mark) {
|
|
1247
|
-
if (mark.kind === "point") return pointMarkBox(mark);
|
|
1248
|
-
const geometry = mark.kind === "arrow" ? arrowBarbs(mark.points, mark.weight ?? 1) : mark.points;
|
|
1281
|
+
function markOutline(mark, unit = 1) {
|
|
1282
|
+
if (mark.kind === "point") return pointMarkBox(mark, unit);
|
|
1283
|
+
const geometry = mark.kind === "arrow" ? arrowBarbs(mark.points, mark.weight ?? 1, unit) : mark.points;
|
|
1249
1284
|
const straight = pointsBox(geometry);
|
|
1250
1285
|
if (straight === void 0) return void 0;
|
|
1251
1286
|
const radians = markRadians(mark);
|
|
1252
1287
|
const box = radians === 0 ? straight : turnedBox(geometry, straight, radians);
|
|
1253
|
-
const grow = strokeWidthOf(mark) / 2;
|
|
1288
|
+
const grow = strokeWidthOf(mark, unit) / 2;
|
|
1254
1289
|
return {
|
|
1255
1290
|
x: box.x - grow,
|
|
1256
1291
|
y: box.y - grow,
|
|
@@ -1324,6 +1359,42 @@ window.__ModuleLoader__.load({
|
|
|
1324
1359
|
return `rotate(${n(radians * 180 / Math.PI)} ${n(cx)} ${n(cy)})`;
|
|
1325
1360
|
}
|
|
1326
1361
|
//#endregion
|
|
1362
|
+
//#region src/client/scale.ts
|
|
1363
|
+
/**
|
|
1364
|
+
* How big the marks should be for the figure they are drawn on.
|
|
1365
|
+
*
|
|
1366
|
+
* A stroke is not a physical thing: 2.5 pixels is a bold line on a 300px thumbnail
|
|
1367
|
+
* and a hairline on a 4000px scan. Marks are comparisons the reader makes against
|
|
1368
|
+
* the line work underneath them, so they have to be sized in the same currency as
|
|
1369
|
+
* the figure — a fraction of it — rather than in the figure's pixels.
|
|
1370
|
+
*
|
|
1371
|
+
* The reference edge is the size a science figure is usually exported at. An
|
|
1372
|
+
* ordinary figure then gets the unit it always had, and only the outliers (a
|
|
1373
|
+
* thumbnail, a poster) are scaled. The range is clamped rather than proportional
|
|
1374
|
+
* because a 20000px figure would otherwise get strokes 40px wide, which reads as a
|
|
1375
|
+
* highlighter rather than a line.
|
|
1376
|
+
* @module dsh-figmark/client/scale
|
|
1377
|
+
*/
|
|
1378
|
+
/** The figure edge, in pixels, that gets the unit it always had. */
|
|
1379
|
+
const REFERENCE_EDGE = 1200;
|
|
1380
|
+
/** Smallest multiple of the base sizes, for figures far below the reference. */
|
|
1381
|
+
const MIN_UNIT = .75;
|
|
1382
|
+
/**
|
|
1383
|
+
* The size everything drawn on a figure is multiplied by.
|
|
1384
|
+
*
|
|
1385
|
+
* Measured against the figure's longer edge: that is the dimension a reader judges
|
|
1386
|
+
* a stroke against, and using the area would shrink a tall thin panel's marks for a
|
|
1387
|
+
* reason the eye does not see.
|
|
1388
|
+
* @param width - the figure's intrinsic width, in figure pixels.
|
|
1389
|
+
* @param height - the figure's intrinsic height, in figure pixels.
|
|
1390
|
+
* @returns the multiple of the base sizes, held inside {@link MIN_UNIT} and {@link MAX_UNIT}.
|
|
1391
|
+
*/
|
|
1392
|
+
function figureUnit(width, height) {
|
|
1393
|
+
const edge = Math.max(width, height);
|
|
1394
|
+
if (!Number.isFinite(edge) || edge <= 0) return 1;
|
|
1395
|
+
return Math.min(3, Math.max(MIN_UNIT, edge / REFERENCE_EDGE));
|
|
1396
|
+
}
|
|
1397
|
+
//#endregion
|
|
1327
1398
|
//#region src/client/Canvas.tsx
|
|
1328
1399
|
/**
|
|
1329
1400
|
* The drawing surface.
|
|
@@ -1334,6 +1405,19 @@ window.__ModuleLoader__.load({
|
|
|
1334
1405
|
* means the same thing at any zoom or pane width.
|
|
1335
1406
|
* @module dsh-figmark/client/Canvas
|
|
1336
1407
|
*/
|
|
1408
|
+
/**
|
|
1409
|
+
* The `weight` field of a new mark, or nothing when it is the default.
|
|
1410
|
+
*
|
|
1411
|
+
* Absent means the default throughout this plugin, so writing `weight: 1` would put
|
|
1412
|
+
* a field into every mark that says nothing — sidecars would grow a key for every
|
|
1413
|
+
* mark ever drawn, and every comparison of two documents would see a difference
|
|
1414
|
+
* where there is none.
|
|
1415
|
+
* @param weight - the weight the next stroke is drawn at.
|
|
1416
|
+
* @returns the field, or an empty object for the default.
|
|
1417
|
+
*/
|
|
1418
|
+
function weightField(weight) {
|
|
1419
|
+
return weight === 1 ? {} : { weight };
|
|
1420
|
+
}
|
|
1337
1421
|
/** Hit width of an invisible selection stroke, in figure pixels. */
|
|
1338
1422
|
const HIT_WIDTH = 14;
|
|
1339
1423
|
/** Minimum distance between recorded freehand samples, in figure pixels. */
|
|
@@ -1357,6 +1441,7 @@ window.__ModuleLoader__.load({
|
|
|
1357
1441
|
*/
|
|
1358
1442
|
function Canvas(props) {
|
|
1359
1443
|
const { width, height, marks, tool, color, containerRef, anchoring } = props;
|
|
1444
|
+
const [originX, originY] = props.origin ?? [0, 0];
|
|
1360
1445
|
const weight = props.weight ?? 1;
|
|
1361
1446
|
/**
|
|
1362
1447
|
* Each mark's number, by id.
|
|
@@ -1366,6 +1451,14 @@ window.__ModuleLoader__.load({
|
|
|
1366
1451
|
* otherwise.
|
|
1367
1452
|
*/
|
|
1368
1453
|
const numbers = new Map(marks.map((mark, index) => [mark.id, props.numbering?.get(mark.id) ?? index + 1]));
|
|
1454
|
+
/**
|
|
1455
|
+
* How big everything is drawn here, as a multiple of the base sizes.
|
|
1456
|
+
*
|
|
1457
|
+
* Derived from the surface rather than passed in: every mark on this canvas shares
|
|
1458
|
+
* one figure, so one number decides the pen for all of them, and a caller that
|
|
1459
|
+
* forgot to thread it through could not make a mark disagree with its hit area.
|
|
1460
|
+
*/
|
|
1461
|
+
const unit = figureUnit(width, height);
|
|
1369
1462
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
1370
1463
|
const [press, setPress] = (0, react.useState)(null);
|
|
1371
1464
|
const drawing = (0, react.useRef)(false);
|
|
@@ -1410,7 +1503,7 @@ window.__ModuleLoader__.load({
|
|
|
1410
1503
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1411
1504
|
const scaleX = rect.width === 0 ? 1 : width / rect.width;
|
|
1412
1505
|
const scaleY = rect.height === 0 ? 1 : height / rect.height;
|
|
1413
|
-
return [(event.clientX - rect.left) * scaleX, (event.clientY - rect.top) * scaleY];
|
|
1506
|
+
return [(event.clientX - rect.left) * scaleX - originX, (event.clientY - rect.top) * scaleY - originY];
|
|
1414
1507
|
};
|
|
1415
1508
|
/**
|
|
1416
1509
|
* Describe what the mark landed on.
|
|
@@ -1592,6 +1685,7 @@ window.__ModuleLoader__.load({
|
|
|
1592
1685
|
kind: "point",
|
|
1593
1686
|
color,
|
|
1594
1687
|
points: [point],
|
|
1688
|
+
...weightField(weight),
|
|
1595
1689
|
...anchor === void 0 ? {} : { anchor }
|
|
1596
1690
|
};
|
|
1597
1691
|
props.onAdd(mark);
|
|
@@ -1608,7 +1702,7 @@ window.__ModuleLoader__.load({
|
|
|
1608
1702
|
kind: tool,
|
|
1609
1703
|
color,
|
|
1610
1704
|
points: seed,
|
|
1611
|
-
weight
|
|
1705
|
+
...weightField(weight)
|
|
1612
1706
|
});
|
|
1613
1707
|
};
|
|
1614
1708
|
const move = (event) => {
|
|
@@ -1634,10 +1728,18 @@ window.__ModuleLoader__.load({
|
|
|
1634
1728
|
...press,
|
|
1635
1729
|
snap: event.shiftKey
|
|
1636
1730
|
};
|
|
1731
|
+
const edit = editFor(held, point, dx, dy);
|
|
1637
1732
|
setPress({
|
|
1638
1733
|
...held,
|
|
1639
|
-
edit
|
|
1734
|
+
edit
|
|
1640
1735
|
});
|
|
1736
|
+
const base = props.marks.find((candidate) => candidate.id === press.id);
|
|
1737
|
+
/* v8 ignore next -- a press always begins on a mark that is still in the list. */
|
|
1738
|
+
if (base === void 0) return;
|
|
1739
|
+
const live = { ...base };
|
|
1740
|
+
if (edit.points !== void 0) live.points = edit.points;
|
|
1741
|
+
if (edit.rotation !== void 0) live.rotation = edit.rotation;
|
|
1742
|
+
props.onLiveEdit?.(live);
|
|
1641
1743
|
return;
|
|
1642
1744
|
}
|
|
1643
1745
|
if (!drawing.current || draft === null) return;
|
|
@@ -1665,6 +1767,7 @@ window.__ModuleLoader__.load({
|
|
|
1665
1767
|
if (press !== null) {
|
|
1666
1768
|
const done = press.edit;
|
|
1667
1769
|
setPress(null);
|
|
1770
|
+
props.onLiveEdit?.(null);
|
|
1668
1771
|
if (done === null) return;
|
|
1669
1772
|
if (press.handle === void 0) {
|
|
1670
1773
|
/* v8 ignore next -- a move always carries the geometry it moved to. */
|
|
@@ -1754,8 +1857,8 @@ window.__ModuleLoader__.load({
|
|
|
1754
1857
|
* compared to a washed-out sticker.
|
|
1755
1858
|
*/
|
|
1756
1859
|
const renderPoint = (mark) => {
|
|
1757
|
-
const pin = pinPathData(mark);
|
|
1758
|
-
const centre = pinNumberCentre(mark);
|
|
1860
|
+
const pin = pinPathData(mark, unit);
|
|
1861
|
+
const centre = pinNumberCentre(mark, unit);
|
|
1759
1862
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1760
1863
|
if (pin === void 0 || centre === void 0) return null;
|
|
1761
1864
|
const number = numbers.get(mark.id);
|
|
@@ -1764,21 +1867,21 @@ window.__ModuleLoader__.load({
|
|
|
1764
1867
|
d: pin,
|
|
1765
1868
|
fill: "#ffffff",
|
|
1766
1869
|
stroke: "#ffffff",
|
|
1767
|
-
strokeWidth: 4.5,
|
|
1870
|
+
strokeWidth: 4.5 * unit,
|
|
1768
1871
|
strokeLinejoin: "round"
|
|
1769
1872
|
}),
|
|
1770
1873
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1771
1874
|
d: pin,
|
|
1772
1875
|
fill: mark.color,
|
|
1773
1876
|
stroke: "#ffffff",
|
|
1774
|
-
strokeWidth: 1.5,
|
|
1877
|
+
strokeWidth: 1.5 * unit,
|
|
1775
1878
|
strokeLinejoin: "round"
|
|
1776
1879
|
}),
|
|
1777
1880
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1778
1881
|
x: centre[0],
|
|
1779
1882
|
y: centre[1],
|
|
1780
1883
|
fontFamily: PIN_FONT_FAMILY,
|
|
1781
|
-
fontSize:
|
|
1884
|
+
fontSize: pinFontSizeOf(mark, unit),
|
|
1782
1885
|
fontWeight: 700,
|
|
1783
1886
|
fill: PIN_NUMBER_COLOR,
|
|
1784
1887
|
textAnchor: "middle",
|
|
@@ -1796,19 +1899,19 @@ window.__ModuleLoader__.load({
|
|
|
1796
1899
|
* handles that would do nothing.
|
|
1797
1900
|
*/
|
|
1798
1901
|
const renderPointFrame = (shown) => {
|
|
1799
|
-
const box = pointMarkBox(shown);
|
|
1902
|
+
const box = pointMarkBox(shown, unit);
|
|
1800
1903
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1801
1904
|
if (box === void 0) return null;
|
|
1802
1905
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1803
|
-
x: box.x - 3,
|
|
1804
|
-
y: box.y - 3,
|
|
1805
|
-
width: box.width + 6,
|
|
1806
|
-
height: box.height + 6,
|
|
1807
|
-
rx: 3,
|
|
1906
|
+
x: box.x - 3 * unit,
|
|
1907
|
+
y: box.y - 3 * unit,
|
|
1908
|
+
width: box.width + 6 * unit,
|
|
1909
|
+
height: box.height + 6 * unit,
|
|
1910
|
+
rx: 3 * unit,
|
|
1808
1911
|
fill: "none",
|
|
1809
1912
|
stroke: "#4c8dff",
|
|
1810
|
-
strokeWidth:
|
|
1811
|
-
strokeDasharray:
|
|
1913
|
+
strokeWidth: unit,
|
|
1914
|
+
strokeDasharray: `${3 * unit} ${2 * unit}`,
|
|
1812
1915
|
style: { pointerEvents: "none" }
|
|
1813
1916
|
});
|
|
1814
1917
|
};
|
|
@@ -1818,7 +1921,7 @@ window.__ModuleLoader__.load({
|
|
|
1818
1921
|
const selected = props.selectedId === mark.id;
|
|
1819
1922
|
const transform = markTransform(shown);
|
|
1820
1923
|
const isPoint = mark.kind === "point";
|
|
1821
|
-
const pin = isPoint ? pinPathData(shown) : void 0;
|
|
1924
|
+
const pin = isPoint ? pinPathData(shown, unit) : void 0;
|
|
1822
1925
|
/**
|
|
1823
1926
|
* What a press on the drawn shape starts.
|
|
1824
1927
|
*
|
|
@@ -1830,7 +1933,7 @@ window.__ModuleLoader__.load({
|
|
|
1830
1933
|
d: pin,
|
|
1831
1934
|
fill: "transparent",
|
|
1832
1935
|
stroke: "transparent",
|
|
1833
|
-
strokeWidth: HIT_WIDTH,
|
|
1936
|
+
strokeWidth: HIT_WIDTH * unit,
|
|
1834
1937
|
style: {
|
|
1835
1938
|
pointerEvents: "all",
|
|
1836
1939
|
cursor: "pointer"
|
|
@@ -1855,7 +1958,7 @@ window.__ModuleLoader__.load({
|
|
|
1855
1958
|
d: path,
|
|
1856
1959
|
fill: "none",
|
|
1857
1960
|
stroke: "#ffffff",
|
|
1858
|
-
strokeWidth: strokeWidthOf(shown) + 2.5,
|
|
1961
|
+
strokeWidth: strokeWidthOf(shown, unit) + 2.5 * unit,
|
|
1859
1962
|
strokeLinecap: "round",
|
|
1860
1963
|
strokeLinejoin: "round"
|
|
1861
1964
|
}),
|
|
@@ -1863,7 +1966,7 @@ window.__ModuleLoader__.load({
|
|
|
1863
1966
|
d: path,
|
|
1864
1967
|
fill: "none",
|
|
1865
1968
|
stroke: shown.color,
|
|
1866
|
-
strokeWidth: strokeWidthOf(shown),
|
|
1969
|
+
strokeWidth: strokeWidthOf(shown, unit),
|
|
1867
1970
|
strokeLinecap: "round",
|
|
1868
1971
|
strokeLinejoin: "round"
|
|
1869
1972
|
}),
|
|
@@ -1871,7 +1974,7 @@ window.__ModuleLoader__.load({
|
|
|
1871
1974
|
d: path,
|
|
1872
1975
|
fill: "none",
|
|
1873
1976
|
stroke: "transparent",
|
|
1874
|
-
strokeWidth: HIT_WIDTH,
|
|
1977
|
+
strokeWidth: HIT_WIDTH * unit,
|
|
1875
1978
|
style: {
|
|
1876
1979
|
pointerEvents: "stroke",
|
|
1877
1980
|
cursor: "pointer"
|
|
@@ -1892,9 +1995,13 @@ window.__ModuleLoader__.load({
|
|
|
1892
1995
|
]
|
|
1893
1996
|
}, mark.id);
|
|
1894
1997
|
};
|
|
1895
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1998
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1896
1999
|
className: "da-overlay",
|
|
1897
2000
|
"data-da-overlay": "",
|
|
2001
|
+
style: {
|
|
2002
|
+
left: originX,
|
|
2003
|
+
top: originY
|
|
2004
|
+
},
|
|
1898
2005
|
width,
|
|
1899
2006
|
height,
|
|
1900
2007
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -1916,7 +2023,10 @@ window.__ModuleLoader__.load({
|
|
|
1916
2023
|
}
|
|
1917
2024
|
props.onSelect(null);
|
|
1918
2025
|
},
|
|
1919
|
-
children:
|
|
2026
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
2027
|
+
transform: `translate(${originX} ${originY})`,
|
|
2028
|
+
children: [marks.map((mark) => renderMark(mark, false)), draft === null ? null : renderMark(draft, true)]
|
|
2029
|
+
})
|
|
1920
2030
|
});
|
|
1921
2031
|
}
|
|
1922
2032
|
/** A difference that never changes, for a body that has no edit yet. */
|
|
@@ -2478,6 +2588,44 @@ window.__ModuleLoader__.load({
|
|
|
2478
2588
|
const existing = shell.snapshot.draft.trim();
|
|
2479
2589
|
shell.setDraft(existing === "" ? message : `${existing}\n\n${message}`);
|
|
2480
2590
|
}
|
|
2591
|
+
/**
|
|
2592
|
+
* The box the whole drawing occupies: the figure, plus wherever the marks reach.
|
|
2593
|
+
*
|
|
2594
|
+
* A figure with every mark inside it gets exactly its own box back, which is the
|
|
2595
|
+
* case the overlay has always drawn; the growth only happens for marks that leave
|
|
2596
|
+
* it. The margin is applied only on sides that actually grew, so an ordinary figure
|
|
2597
|
+
* is not padded on all four sides for nothing.
|
|
2598
|
+
* @param width - the figure's width, in figure pixels.
|
|
2599
|
+
* @param height - the figure's height, in figure pixels.
|
|
2600
|
+
* @param marks - the marks drawn on it.
|
|
2601
|
+
* @param unit - the figure's size multiple, so outlines are measured as drawn.
|
|
2602
|
+
* @returns the box the drawing occupies, in figure coordinates.
|
|
2603
|
+
*/
|
|
2604
|
+
function plotBounds(width, height, marks, unit = 1) {
|
|
2605
|
+
let left = 0;
|
|
2606
|
+
let top = 0;
|
|
2607
|
+
let right = width;
|
|
2608
|
+
let bottom = height;
|
|
2609
|
+
for (const mark of marks) {
|
|
2610
|
+
const box = markOutline(mark, unit);
|
|
2611
|
+
/* v8 ignore next -- a mark with no coordinate covers nothing to grow for. */
|
|
2612
|
+
if (box === void 0) continue;
|
|
2613
|
+
left = Math.min(left, box.x);
|
|
2614
|
+
top = Math.min(top, box.y);
|
|
2615
|
+
right = Math.max(right, box.x + box.width);
|
|
2616
|
+
bottom = Math.max(bottom, box.y + box.height);
|
|
2617
|
+
}
|
|
2618
|
+
const padLeft = left < 0 ? 12 : 0;
|
|
2619
|
+
const padTop = top < 0 ? 12 : 0;
|
|
2620
|
+
const padRight = right > width ? 12 : 0;
|
|
2621
|
+
const padBottom = bottom > height ? 12 : 0;
|
|
2622
|
+
return {
|
|
2623
|
+
x: left - padLeft,
|
|
2624
|
+
y: top - padTop,
|
|
2625
|
+
width: right + padRight - (left - padLeft),
|
|
2626
|
+
height: bottom + padBottom - (top - padTop)
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2481
2629
|
//#endregion
|
|
2482
2630
|
//#region src/client/export.ts
|
|
2483
2631
|
/**
|
|
@@ -2485,17 +2633,19 @@ window.__ModuleLoader__.load({
|
|
|
2485
2633
|
*
|
|
2486
2634
|
* @param mark - the mark to draw.
|
|
2487
2635
|
* @param number - the mark's number in the list, when it carries one.
|
|
2636
|
+
* @param unit - the figure's size multiple, so the export is drawn at the same
|
|
2637
|
+
* weight the reader saw.
|
|
2488
2638
|
* @returns the mark's elements.
|
|
2489
2639
|
*/
|
|
2490
|
-
function markSvg(mark, number) {
|
|
2640
|
+
function markSvg(mark, number, unit) {
|
|
2491
2641
|
const path = markPathData(mark);
|
|
2492
2642
|
const parts = [];
|
|
2493
|
-
if (path !== void 0) parts.push(`<path d="${path}" fill="none" stroke="#ffffff" stroke-width="${strokeWidthOf(mark) + 2.5}" stroke-linecap="round" stroke-linejoin="round"/>`, `<path d="${path}" fill="none" stroke="${mark.color}" stroke-width="${strokeWidthOf(mark)}" stroke-linecap="round" stroke-linejoin="round"/>`);
|
|
2494
|
-
const pin = mark.kind === "point" ? pinPathData(mark) : void 0;
|
|
2495
|
-
const centre = mark.kind === "point" ? pinNumberCentre(mark) : void 0;
|
|
2643
|
+
if (path !== void 0) parts.push(`<path d="${path}" fill="none" stroke="#ffffff" stroke-width="${strokeWidthOf(mark, unit) + 2.5 * unit}" stroke-linecap="round" stroke-linejoin="round"/>`, `<path d="${path}" fill="none" stroke="${mark.color}" stroke-width="${strokeWidthOf(mark, unit)}" stroke-linecap="round" stroke-linejoin="round"/>`);
|
|
2644
|
+
const pin = mark.kind === "point" ? pinPathData(mark, unit) : void 0;
|
|
2645
|
+
const centre = mark.kind === "point" ? pinNumberCentre(mark, unit) : void 0;
|
|
2496
2646
|
if (pin !== void 0 && centre !== void 0) {
|
|
2497
|
-
parts.push(`<path d="${pin}" fill="${mark.color}" stroke="#ffffff" stroke-width="2" stroke-linejoin="round"/>`);
|
|
2498
|
-
if (number !== void 0) parts.push(`<text x="${centre[0]}" y="${centre[1]}" font-family="${PIN_FONT_FAMILY}" font-size="
|
|
2647
|
+
parts.push(`<path d="${pin}" fill="${mark.color}" stroke="#ffffff" stroke-width="${2 * unit}" stroke-linejoin="round"/>`);
|
|
2648
|
+
if (number !== void 0) parts.push(`<text x="${centre[0]}" y="${centre[1]}" font-family="${PIN_FONT_FAMILY}" font-size="${pinFontSizeOf(mark, unit)}" font-weight="700" fill="${PIN_NUMBER_COLOR}" text-anchor="middle" dominant-baseline="central">${number}</text>`);
|
|
2499
2649
|
}
|
|
2500
2650
|
return parts.join("");
|
|
2501
2651
|
}
|
|
@@ -2531,7 +2681,27 @@ window.__ModuleLoader__.load({
|
|
|
2531
2681
|
*/
|
|
2532
2682
|
function composeReviewSvg(layer, marks, numbering) {
|
|
2533
2683
|
const numbers = new Map(marks.map((mark, index) => [mark.id, numbering?.get(mark.id) ?? index + 1]));
|
|
2534
|
-
|
|
2684
|
+
const unit = figureUnit(layer.width, layer.height);
|
|
2685
|
+
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2686
|
+
const shift = `translate(${-paper.x} ${-paper.y})`;
|
|
2687
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${paper.width}" height="${paper.height}" viewBox="0 0 ${paper.width} ${paper.height}"><rect x="0" y="0" width="${paper.width}" height="${paper.height}" fill="#ffffff"/><g transform="${shift}">` + figureSvg(layer) + marks.map((mark) => markSvg(mark, numbers.get(mark.id), unit)).join("") + "</g></svg>";
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* The size of the picture {@link composeReviewSvg} builds.
|
|
2691
|
+
*
|
|
2692
|
+
* A caller has to know it before rasterizing: an SVG rendered at the figure's size
|
|
2693
|
+
* would crop away exactly the marks that made the paper grow.
|
|
2694
|
+
* @param layer - the figure to place underneath.
|
|
2695
|
+
* @param marks - marks in draw order.
|
|
2696
|
+
* @param unit - the figure's size multiple.
|
|
2697
|
+
* @returns the paper's width and height, in figure pixels.
|
|
2698
|
+
*/
|
|
2699
|
+
function reviewSize(layer, marks, unit = figureUnit(layer.width, layer.height)) {
|
|
2700
|
+
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2701
|
+
return {
|
|
2702
|
+
width: paper.width,
|
|
2703
|
+
height: paper.height
|
|
2704
|
+
};
|
|
2535
2705
|
}
|
|
2536
2706
|
/**
|
|
2537
2707
|
* Rasterization scale for one review image.
|
|
@@ -3120,39 +3290,45 @@ window.__ModuleLoader__.load({
|
|
|
3120
3290
|
.da-palette .da-tool .da-tool-icon{height:auto}
|
|
3121
3291
|
.da-palette .da-tool .da-tool-icon svg{width:16px;height:16px;stroke-width:1.9}
|
|
3122
3292
|
|
|
3123
|
-
/* --- the
|
|
3124
|
-
/* A vertical rail along the left edge of the figure
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3293
|
+
/* --- the size rail ------------------------------------------------------- */
|
|
3294
|
+
/* A vertical rail along the left edge of the figure: how heavy the pen is, and how
|
|
3295
|
+
big a point mark's pin is. Idle it is a hairline, so it costs the figure nothing;
|
|
3296
|
+
crossing it opens it out to a track. The track is *tapered* — a thin line at the
|
|
3297
|
+
bottom and a broad one at the top — because the control's whole subject is
|
|
3298
|
+
thickness, and a wedge says which way is heavier without reading a number.
|
|
3299
|
+
A horizontal range input is rotated upright inside the pill: it keeps dragging,
|
|
3300
|
+
arrow keys, the step and the accessible name, while the rail above does the
|
|
3301
|
+
drawing. The drawn parts never take the pointer, or grabbing the knob would grab
|
|
3302
|
+
the knob instead of the slider. */
|
|
3130
3303
|
.da-weight{position:absolute;z-index:7;left:6px;top:50%;transform:translateY(-50%);
|
|
3131
|
-
display:flex;align-items:center;justify-content:center;width:
|
|
3304
|
+
display:flex;align-items:center;justify-content:center;width:7px;height:200px;
|
|
3132
3305
|
border-radius:999px;background:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
|
|
3133
3306
|
cursor:ns-resize;transition:width .16s ease,background .16s ease,box-shadow .16s ease}
|
|
3134
|
-
.da-weight:hover,.da-weight:focus-within{width:
|
|
3307
|
+
.da-weight:hover,.da-weight:focus-within{width:30px;background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3135
3308
|
box-shadow:0 5px 18px rgba(20,40,80,.16),0 0 0 0.5px var(--dsw-alias-border-l2,currentColor)}
|
|
3136
|
-
/* The track
|
|
3137
|
-
.
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
.
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
.
|
|
3146
|
-
|
|
3147
|
-
|
|
3309
|
+
/* The track is an svg in the body: a hairline at the bottom that opens into a rounded
|
|
3310
|
+
shoulder at the top. It is not a clip-path polygon because a polygon has no rounded
|
|
3311
|
+
ends — the corners were what made the wedge read as a cut-out blade rather than a
|
|
3312
|
+
part of the panel. preserveAspectRatio="none" stretches one 12x100 shape over the
|
|
3313
|
+
rail's height, so the shoulders stay smooth at any length. */
|
|
3314
|
+
.da-weight-svg{position:absolute;left:50%;top:12px;bottom:12px;width:18px;
|
|
3315
|
+
transform:translateX(-50%);opacity:0;pointer-events:none;transition:opacity .16s ease}
|
|
3316
|
+
.da-weight:hover .da-weight-svg,.da-weight:focus-within .da-weight-svg{opacity:1}
|
|
3317
|
+
/* The knob sits on the value: the only part that shows at rest, so the rail reads as
|
|
3318
|
+
a slider waiting to be touched rather than a bare rule. */
|
|
3319
|
+
.da-weight-knob{position:absolute;left:50%;width:var(--da-knob-size,11px);height:var(--da-knob-size,11px);
|
|
3320
|
+
border-radius:50%;transform:translateX(-50%);pointer-events:none;
|
|
3321
|
+
background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3322
|
+
box-shadow:0 1px 4px rgba(20,40,80,.28),0 0 0 1px var(--dsw-alias-border-l3,currentColor);
|
|
3148
3323
|
transition:width .16s ease,height .16s ease,background .16s ease}
|
|
3324
|
+
.da-weight:hover,.da-weight:focus-within{--da-knob-size:15px}
|
|
3149
3325
|
.da-weight:hover .da-weight-knob,.da-weight:focus-within .da-weight-knob{
|
|
3150
|
-
|
|
3151
|
-
box-shadow:0 2px 7px rgba(20,40,80,.
|
|
3152
|
-
/* The browser's own range, rotated upright and made transparent
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
.da-weight-input{position:absolute;left:50%;top:50%;width:
|
|
3326
|
+
background:var(--dsw-alias-brand-primary,#176df2);
|
|
3327
|
+
box-shadow:0 2px 7px rgba(20,40,80,.3)}
|
|
3328
|
+
/* The browser's own range, rotated upright and made transparent. It spans the whole
|
|
3329
|
+
rail so a press anywhere on it lands on the slider — including on the knob, which
|
|
3330
|
+
is why the knob is pointer-transparent. */
|
|
3331
|
+
.da-weight-input{position:absolute;left:50%;top:50%;width:200px;height:30px;margin:0;opacity:0;
|
|
3156
3332
|
transform:translate(-50%,-50%) rotate(-90deg);cursor:ns-resize;appearance:none;background:transparent}
|
|
3157
3333
|
|
|
3158
3334
|
/* --- the floating bar ---------------------------------------------------- */
|
|
@@ -3243,6 +3419,10 @@ window.__ModuleLoader__.load({
|
|
|
3243
3419
|
needs to see where the drawing stops. */
|
|
3244
3420
|
.da-stage{position:relative;display:flex;align-items:safe center;justify-content:safe center;flex:1 1 auto;overflow:auto;min-height:0;padding:14px;background:var(--dsw-alias-bg-document-preview,transparent)}
|
|
3245
3421
|
.da-surface{position:relative;flex:0 0 auto;margin:0;transform-origin:top left;background:#fff;border-radius:4px;box-shadow:var(--dsw-elevation-prominent,0 1px 3px rgba(0,0,0,.14),0 8px 24px rgba(0,0,0,.08))}
|
|
3422
|
+
/* The paper: the figure plus the margin a mark may have needed. It is the white sheet
|
|
3423
|
+
the drawing sits on, so a mark that reaches past the figure lands on paper rather
|
|
3424
|
+
than on the panel behind it. */
|
|
3425
|
+
.da-paper{background:#fff}
|
|
3246
3426
|
.da-figure{display:block;user-select:none;-webkit-user-drag:none}
|
|
3247
3427
|
/* The table is a thing you can push. The hand only shows under the tools that
|
|
3248
3428
|
leave a drag free to move the view: under a drawing tool the same drag draws. */
|
|
@@ -3253,7 +3433,7 @@ window.__ModuleLoader__.load({
|
|
|
3253
3433
|
/* The rendered document takes the figure's place; the overlay above it owns every
|
|
3254
3434
|
pointer event, which is also what keeps a link inside it from navigating. */
|
|
3255
3435
|
.da-html{display:block;border:0;background:#fff;pointer-events:none}
|
|
3256
|
-
.da-overlay{position:absolute;
|
|
3436
|
+
.da-overlay{position:absolute;touch-action:none}
|
|
3257
3437
|
.da-overlay[data-mode="view"]{pointer-events:none}
|
|
3258
3438
|
.da-overlay[data-tool="select"]{cursor:default}
|
|
3259
3439
|
.da-overlay:not([data-tool="select"]){cursor:crosshair}
|
|
@@ -3317,14 +3497,19 @@ window.__ModuleLoader__.load({
|
|
|
3317
3497
|
come from the mark it belongs to. It is a pill rather than a card — no head, no
|
|
3318
3498
|
border between the field and the panel — so writing about a mark costs one line
|
|
3319
3499
|
over the figure instead of a box that covers it. */
|
|
3320
|
-
.da-float-detail{width:300px;gap:0;padding:4px;border-radius:22px;
|
|
3500
|
+
.da-float-detail{width:300px;max-width:calc(100% - 16px);gap:0;padding:4px;border-radius:22px;
|
|
3321
3501
|
flex-direction:row;align-items:flex-end;
|
|
3322
3502
|
box-shadow:0 8px 26px rgba(20,40,80,.2),0 0 0 1px rgba(20,40,80,.04)}
|
|
3323
3503
|
.da-float .da-comment{flex:1 1 auto;min-width:0;box-sizing:border-box;border:0;background:transparent;
|
|
3324
3504
|
color:inherit;font:inherit;font-size:13px;line-height:1.55;padding:7px 10px;resize:none;
|
|
3325
|
-
/* One line until the sentence needs more
|
|
3326
|
-
|
|
3327
|
-
|
|
3505
|
+
/* One line until the sentence needs more, then as many as it takes: the pill is a
|
|
3506
|
+
place to type, and a sentence read through a slot two lines tall is a sentence
|
|
3507
|
+
the reader has to scroll to finish. The rule for the record cards' textareas sets
|
|
3508
|
+
a 74px floor, hence the explicit reset. The scrollbar is hidden because it is not
|
|
3509
|
+
a scroll view — wheel and keys still work once the sentence outgrows the pane. */
|
|
3510
|
+
min-height:0;height:auto;max-height:min(180px,42vh);overflow-y:auto;outline:none;
|
|
3511
|
+
field-sizing:content;scrollbar-width:none}
|
|
3512
|
+
.da-float .da-comment::-webkit-scrollbar{width:0;height:0}
|
|
3328
3513
|
.da-float .da-comment::placeholder{color:var(--dsw-alias-label-tertiary,currentColor);opacity:.85}
|
|
3329
3514
|
/* The hand-off appears only once the field says something: an empty pill is a
|
|
3330
3515
|
place to type, not a decision to make. */
|
|
@@ -3544,13 +3729,34 @@ window.__ModuleLoader__.load({
|
|
|
3544
3729
|
* @param size - the card's size, in CSS pixels.
|
|
3545
3730
|
* @returns the card's top-left corner, in the panel's coordinates.
|
|
3546
3731
|
*/
|
|
3732
|
+
/**
|
|
3733
|
+
* The size to place a card by: the card's own, or a stand-in before it is measured.
|
|
3734
|
+
*
|
|
3735
|
+
* A comment pill grows with the sentence in it, so a placement that assumed one line
|
|
3736
|
+
* would push a long comment off the bottom of the panel. The element is measured once
|
|
3737
|
+
* it has been laid out; before that — the first frame of a selection, and in any
|
|
3738
|
+
* environment that reports zero boxes — the caller's estimate is the only size there
|
|
3739
|
+
* is, and the next pass corrects it.
|
|
3740
|
+
* @param element - the laid-out card, when there is one.
|
|
3741
|
+
* @param fallback - the size to use when the card has not been measured.
|
|
3742
|
+
* @returns the width and height to place by.
|
|
3743
|
+
*/
|
|
3744
|
+
function measuredSize(element, fallback) {
|
|
3745
|
+
if (element === null || element.offsetHeight === 0) return fallback;
|
|
3746
|
+
return {
|
|
3747
|
+
width: element.offsetWidth,
|
|
3748
|
+
height: element.offsetHeight
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3547
3751
|
function placeCard(anchor, panelWidth, panelHeight, size = {
|
|
3548
|
-
width:
|
|
3549
|
-
height:
|
|
3752
|
+
width: 300,
|
|
3753
|
+
height: 48
|
|
3550
3754
|
}) {
|
|
3551
3755
|
const below = anchor.y + anchor.height + 10;
|
|
3552
3756
|
const above = anchor.y - size.height - 10;
|
|
3553
|
-
const
|
|
3757
|
+
const roomBelow = panelHeight - 8 - below;
|
|
3758
|
+
const roomAbove = anchor.y - 10 - 8;
|
|
3759
|
+
const top = roomBelow >= size.height ? below : roomAbove >= size.height ? above : Math.max(8, roomAbove > roomBelow ? above : below);
|
|
3554
3760
|
const widest = Math.max(8, panelWidth - size.width - 8);
|
|
3555
3761
|
return {
|
|
3556
3762
|
left: Math.min(Math.max(8, anchor.x), widest),
|
|
@@ -3614,6 +3820,27 @@ window.__ModuleLoader__.load({
|
|
|
3614
3820
|
"#2f9e44"
|
|
3615
3821
|
];
|
|
3616
3822
|
/** Tools in toolbar order. */
|
|
3823
|
+
/**
|
|
3824
|
+
* The rail's track, drawn on an 18x100 grid and stretched to whatever height it gets.
|
|
3825
|
+
*
|
|
3826
|
+
* A hairline at the bottom opening into a round shoulder at the top: the control's
|
|
3827
|
+
* subject is thickness, so the track itself says which way is heavier. Every edge is
|
|
3828
|
+
* curved — straight flanks make a paper dart, and a square cap makes a cut-off blade —
|
|
3829
|
+
* so the profile is eased and the top is closed with an arc a little wider than the
|
|
3830
|
+
* shoulders. Stretching it vertically (preserveAspectRatio="none") costs nothing
|
|
3831
|
+
* visually: the only vertical features are straight lines, and the horizontal ones are
|
|
3832
|
+
* what the gradient is for.
|
|
3833
|
+
*/
|
|
3834
|
+
const WEIGHT_TRACK = [
|
|
3835
|
+
"M 8.2 100",
|
|
3836
|
+
"C 8.2 78 7.9 58 6.4 38",
|
|
3837
|
+
"C 5.4 25 3.2 16 1.9 11",
|
|
3838
|
+
"A 7.1 7.1 0 0 1 16.1 11",
|
|
3839
|
+
"C 14.8 16 12.6 25 11.6 38",
|
|
3840
|
+
"C 10.1 58 9.8 78 9.8 100",
|
|
3841
|
+
"A 0.8 0.8 0 0 1 8.2 100",
|
|
3842
|
+
"Z"
|
|
3843
|
+
].join(" ");
|
|
3617
3844
|
const TOOLS = [
|
|
3618
3845
|
{
|
|
3619
3846
|
tool: "select",
|
|
@@ -3640,6 +3867,20 @@ window.__ModuleLoader__.load({
|
|
|
3640
3867
|
label: "toolPoint"
|
|
3641
3868
|
}
|
|
3642
3869
|
];
|
|
3870
|
+
/**
|
|
3871
|
+
* The tools that draw something with a size the rail can set.
|
|
3872
|
+
*
|
|
3873
|
+
* `select` is not among them: it draws nothing, so with it armed and no mark
|
|
3874
|
+
* selected the rail has no subject. `point` is, because a pin is sized by the same
|
|
3875
|
+
* number — the rail asks "how big is this mark" either way.
|
|
3876
|
+
*/
|
|
3877
|
+
const WEIGHT_TOOLS = /* @__PURE__ */ new Set([
|
|
3878
|
+
"arrow",
|
|
3879
|
+
"rect",
|
|
3880
|
+
"ellipse",
|
|
3881
|
+
"pen",
|
|
3882
|
+
"point"
|
|
3883
|
+
]);
|
|
3643
3884
|
/** The copy key naming the shape each kind of mark is, for the list's second line. */
|
|
3644
3885
|
const TOOL_COPY = {
|
|
3645
3886
|
arrow: "toolArrow",
|
|
@@ -3849,9 +4090,27 @@ window.__ModuleLoader__.load({
|
|
|
3849
4090
|
const isVector = mediaType === "image/svg+xml";
|
|
3850
4091
|
const t = (0, react.useCallback)((key) => resolveTranslate(props, key), [props.t, props.localeId]);
|
|
3851
4092
|
const sessionId = props.sessionId ?? parsedAddress?.sessionId;
|
|
4093
|
+
/**
|
|
4094
|
+
* A private prefix for the rail's gradient and clip ids.
|
|
4095
|
+
*
|
|
4096
|
+
* Two bodies can be mounted at once (a PDF lays out one per page), and an id is
|
|
4097
|
+
* document-wide: sharing one would make every rail's fill follow the first rail's
|
|
4098
|
+
* selection. Declared here, with the other hooks, because the ids are read far below
|
|
4099
|
+
* the early returns for a figure that failed to load.
|
|
4100
|
+
*/
|
|
4101
|
+
const trackId = `da-weight-${(0, react.useId)().replaceAll(":", "")}`;
|
|
3852
4102
|
const stageRef = (0, react.useRef)(null);
|
|
3853
4103
|
/** The box a double clicked mark's detail is written in. */
|
|
3854
4104
|
const detailRef = (0, react.useRef)(null);
|
|
4105
|
+
/**
|
|
4106
|
+
* The comment pill around that box.
|
|
4107
|
+
*
|
|
4108
|
+
* Measured rather than the textarea inside it: the field gives up width to the two
|
|
4109
|
+
* hand-off buttons as soon as there is something to send, so a placement computed
|
|
4110
|
+
* from the field's box would put the buttons past the edge of the panel — which is
|
|
4111
|
+
* exactly what a comment pill against the right edge used to do.
|
|
4112
|
+
*/
|
|
4113
|
+
const pillRef = (0, react.useRef)(null);
|
|
3855
4114
|
const barRef = (0, react.useRef)(null);
|
|
3856
4115
|
const figureHostRef = (0, react.useRef)(null);
|
|
3857
4116
|
const surfaceRef = (0, react.useRef)(null);
|
|
@@ -3988,6 +4247,14 @@ window.__ModuleLoader__.load({
|
|
|
3988
4247
|
*/
|
|
3989
4248
|
const [detailAt, setDetailAt] = (0, react.useState)(null);
|
|
3990
4249
|
/**
|
|
4250
|
+
* The selected mark as a drag is currently shaping it.
|
|
4251
|
+
*
|
|
4252
|
+
* Kept beside the stored marks rather than in them: the gesture is not committed
|
|
4253
|
+
* until it is released, so writing it into the list would make every frame an
|
|
4254
|
+
* undoable edit. It exists only so the pill can follow the hand.
|
|
4255
|
+
*/
|
|
4256
|
+
const [liveMark, setLiveMark] = (0, react.useState)(null);
|
|
4257
|
+
/**
|
|
3991
4258
|
* Shut the style panel when the reader picks up a tool.
|
|
3992
4259
|
*
|
|
3993
4260
|
* Arming a tool is the moment the panel has done its job; leaving it open over
|
|
@@ -4175,6 +4442,23 @@ window.__ModuleLoader__.load({
|
|
|
4175
4442
|
rasterSize
|
|
4176
4443
|
]);
|
|
4177
4444
|
/**
|
|
4445
|
+
* How big everything drawn is, as a multiple of the base sizes.
|
|
4446
|
+
*
|
|
4447
|
+
* One number for the whole surface: the pen, the pins and the review image all
|
|
4448
|
+
* have to agree, and a stroke is only bold or thin relative to the figure it sits
|
|
4449
|
+
* on. A 4000px scan and a 300px thumbnail get the same-looking mark this way.
|
|
4450
|
+
*/
|
|
4451
|
+
const unit = size === null ? 1 : figureUnit(size.width, size.height);
|
|
4452
|
+
/**
|
|
4453
|
+
* The paper the drawing needs: the figure, plus anywhere the marks reach past it.
|
|
4454
|
+
*
|
|
4455
|
+
* A reader boxing the margin a plot clips, or flagging a spot just outside the
|
|
4456
|
+
* axes, is drawing something the figure does not contain. The paper grows to hold
|
|
4457
|
+
* it rather than the mark being clipped — and because the surface is white, a mark
|
|
4458
|
+
* out there lands on paper instead of on nothing.
|
|
4459
|
+
*/
|
|
4460
|
+
const paper = size === null ? null : plotBounds(size.width, size.height, marks, unit);
|
|
4461
|
+
/**
|
|
4178
4462
|
* Everything a background write needs, read at write time.
|
|
4179
4463
|
*
|
|
4180
4464
|
* A write runs from a timer that outlives the render that scheduled it, so it has
|
|
@@ -4406,31 +4690,38 @@ window.__ModuleLoader__.load({
|
|
|
4406
4690
|
const root = rootRef.current;
|
|
4407
4691
|
const host = figureRef.current;
|
|
4408
4692
|
const mark = marks.find((candidate) => candidate.id === selectedId);
|
|
4409
|
-
const
|
|
4693
|
+
const drawn = liveMark !== null && liveMark.id === selectedId ? liveMark : mark;
|
|
4694
|
+
const box = drawn === void 0 ? void 0 : markOutline(drawn, unit);
|
|
4410
4695
|
if (root === null || host === null || box === void 0 || size === null) {
|
|
4411
4696
|
setDetailAt(null);
|
|
4412
4697
|
return;
|
|
4413
4698
|
}
|
|
4414
4699
|
const panel = root.getBoundingClientRect();
|
|
4415
|
-
const
|
|
4700
|
+
const measured = host.getBoundingClientRect();
|
|
4701
|
+
const pillSize = measuredSize(pillRef.current, {
|
|
4702
|
+
width: 300,
|
|
4703
|
+
height: 48
|
|
4704
|
+
});
|
|
4416
4705
|
const at = placeCard(scaledBox(box, {
|
|
4417
|
-
x:
|
|
4418
|
-
y:
|
|
4419
|
-
width:
|
|
4420
|
-
height:
|
|
4706
|
+
x: measured.left,
|
|
4707
|
+
y: measured.top,
|
|
4708
|
+
width: measured.width,
|
|
4709
|
+
height: measured.height
|
|
4421
4710
|
}, {
|
|
4422
4711
|
x: panel.left,
|
|
4423
4712
|
y: panel.top,
|
|
4424
4713
|
width: panel.width,
|
|
4425
4714
|
height: panel.height
|
|
4426
|
-
}, size.width), panel.width, panel.height);
|
|
4715
|
+
}, size.width), panel.width, panel.height, pillSize);
|
|
4427
4716
|
setDetailAt((previous) => previous !== null && previous.left === at.left && previous.top === at.top ? previous : at);
|
|
4428
4717
|
}, [
|
|
4718
|
+
htmlHeight,
|
|
4719
|
+
liveMark,
|
|
4429
4720
|
marks,
|
|
4430
|
-
selectedId,
|
|
4431
4721
|
scale,
|
|
4722
|
+
selectedId,
|
|
4432
4723
|
size,
|
|
4433
|
-
|
|
4724
|
+
unit
|
|
4434
4725
|
]);
|
|
4435
4726
|
/**
|
|
4436
4727
|
* The factor currently drawn, kept beside the state that will hold it.
|
|
@@ -4748,8 +5039,9 @@ window.__ModuleLoader__.load({
|
|
|
4748
5039
|
]);
|
|
4749
5040
|
const exportPng = (0, react.useCallback)(async (target) => {
|
|
4750
5041
|
const svg = composeReviewSvg(target, marks);
|
|
4751
|
-
const
|
|
4752
|
-
|
|
5042
|
+
const paper = reviewSize(target, marks);
|
|
5043
|
+
const exportScale = Math.min(2, Math.max(1, 2400 / Math.max(paper.width, paper.height)));
|
|
5044
|
+
return await rasterizePng(svg, paper.width, paper.height, exportScale);
|
|
4753
5045
|
}, [marks]);
|
|
4754
5046
|
const buildDocument = (0, react.useCallback)((target, scope = marks) => {
|
|
4755
5047
|
if (loaded === null) return null;
|
|
@@ -4973,7 +5265,23 @@ window.__ModuleLoader__.load({
|
|
|
4973
5265
|
* the next stroke, which is where the remembered preference comes in.
|
|
4974
5266
|
*/
|
|
4975
5267
|
const shownColor = selected === null ? color : selected.color;
|
|
4976
|
-
|
|
5268
|
+
/**
|
|
5269
|
+
* What the size rail currently means, or null when it means nothing.
|
|
5270
|
+
*
|
|
5271
|
+
* It sizes one thing: the stroke of the mark the reader is working on, or the pen
|
|
5272
|
+
* of the next stroke. With nothing selected and no drawing tool out there is no
|
|
5273
|
+
* such thing, so the rail stands down instead of offering a control for a decision
|
|
5274
|
+
* nobody is making. A point mark borrows the same number for its pin, so the rail
|
|
5275
|
+
* stays when one is selected — it is the mark's size, whichever way it is drawn.
|
|
5276
|
+
*/
|
|
5277
|
+
const shownWeight = selected === null ? WEIGHT_TOOLS.has(tool) ? weight : null : selected.weight ?? 1;
|
|
5278
|
+
/**
|
|
5279
|
+
* How far up the rail that value sits, from 0 at the lightest to 1 at the heaviest.
|
|
5280
|
+
*
|
|
5281
|
+
* One number for the fill, the knob and the input: computed three times the three
|
|
5282
|
+
* could round differently and the knob would sit a pixel off the fill it belongs to.
|
|
5283
|
+
*/
|
|
5284
|
+
const weightFraction = shownWeight === null ? 0 : (shownWeight - WEIGHT_MIN) / (4 - WEIGHT_MIN);
|
|
4977
5285
|
/**
|
|
4978
5286
|
* Change one attribute of the selected mark, or of the next stroke when nothing
|
|
4979
5287
|
* is selected.
|
|
@@ -5004,16 +5312,32 @@ window.__ModuleLoader__.load({
|
|
|
5004
5312
|
}
|
|
5005
5313
|
repaintSelected(selected, { weight: held });
|
|
5006
5314
|
};
|
|
5007
|
-
const surfaceStyle =
|
|
5008
|
-
width:
|
|
5009
|
-
height:
|
|
5315
|
+
const surfaceStyle = paper === null ? {} : {
|
|
5316
|
+
width: paper.width * scale,
|
|
5317
|
+
height: paper.height * scale
|
|
5010
5318
|
};
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5319
|
+
/**
|
|
5320
|
+
* The paper at its unscaled size, with the figure placed at its own offset on it.
|
|
5321
|
+
*
|
|
5322
|
+
* The whole paper is scaled by one transform rather than each part scaling itself,
|
|
5323
|
+
* so the figure and the overlay cannot drift apart at a fractional factor — which
|
|
5324
|
+
* at 60% is exactly what a mark drawn on a line work and a mark drawn on paper
|
|
5325
|
+
* would do.
|
|
5326
|
+
*/
|
|
5327
|
+
const paperStyle = paper === null ? {} : {
|
|
5328
|
+
position: "relative",
|
|
5329
|
+
width: paper.width,
|
|
5330
|
+
height: paper.height,
|
|
5014
5331
|
transform: `scale(${scale})`,
|
|
5015
5332
|
transformOrigin: "top left"
|
|
5016
5333
|
};
|
|
5334
|
+
const figureStyle = paper === null ? {} : {
|
|
5335
|
+
position: "absolute",
|
|
5336
|
+
left: -paper.x,
|
|
5337
|
+
top: -paper.y,
|
|
5338
|
+
width: size?.width,
|
|
5339
|
+
height: size?.height
|
|
5340
|
+
};
|
|
5017
5341
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5018
5342
|
className: "da-root",
|
|
5019
5343
|
ref: rootRef,
|
|
@@ -5042,64 +5366,67 @@ window.__ModuleLoader__.load({
|
|
|
5042
5366
|
className: "da-surface",
|
|
5043
5367
|
ref: surfaceRef,
|
|
5044
5368
|
style: surfaceStyle,
|
|
5045
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
5046
|
-
|
|
5047
|
-
style:
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5369
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5370
|
+
className: "da-paper",
|
|
5371
|
+
style: paperStyle,
|
|
5372
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5373
|
+
ref: figureRef,
|
|
5374
|
+
style: figureStyle,
|
|
5375
|
+
children: [figure?.kind === "svg" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5376
|
+
ref: figureHostRef,
|
|
5377
|
+
dangerouslySetInnerHTML: { __html: figure.markup }
|
|
5378
|
+
}) : figure?.kind === "raster" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
5379
|
+
className: "da-figure",
|
|
5380
|
+
src: figure.objectUrl,
|
|
5381
|
+
width: size?.width,
|
|
5382
|
+
height: size?.height,
|
|
5383
|
+
alt: "",
|
|
5384
|
+
onLoad: (event) => {
|
|
5385
|
+
const image = event.currentTarget;
|
|
5386
|
+
setFileRasterSize({
|
|
5387
|
+
width: image.naturalWidth,
|
|
5388
|
+
height: image.naturalHeight
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
}) : figure?.kind === "html" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
5392
|
+
ref: frameRef,
|
|
5393
|
+
className: "da-html",
|
|
5394
|
+
title: t("htmlFrame"),
|
|
5395
|
+
sandbox: "allow-same-origin",
|
|
5396
|
+
style: {
|
|
5397
|
+
width: figure.width,
|
|
5398
|
+
height: htmlHeight
|
|
5399
|
+
}
|
|
5400
|
+
}) : null, paper === null || size === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Canvas, {
|
|
5401
|
+
width: paper.width,
|
|
5402
|
+
height: paper.height,
|
|
5403
|
+
origin: [-paper.x, -paper.y],
|
|
5404
|
+
marks,
|
|
5405
|
+
tool,
|
|
5406
|
+
color,
|
|
5407
|
+
weight,
|
|
5408
|
+
containerRef: figureHostRef,
|
|
5409
|
+
anchoring: isVector,
|
|
5410
|
+
anchorAtFigurePoint: figure?.kind === "html" ? anchorAtFigurePoint : void 0,
|
|
5411
|
+
selectedId,
|
|
5412
|
+
onSelect: setSelectedId,
|
|
5413
|
+
onAdd: addMark,
|
|
5414
|
+
onMove: moveMark,
|
|
5415
|
+
onUpdate: updateMark,
|
|
5416
|
+
onRemove: removeMark,
|
|
5417
|
+
onPan: panBy,
|
|
5418
|
+
onPanStart: () => {
|
|
5419
|
+
setPanning(true);
|
|
5420
|
+
},
|
|
5421
|
+
onPanEnd: () => {
|
|
5422
|
+
setPanning(false);
|
|
5423
|
+
},
|
|
5424
|
+
onEdit: editMark,
|
|
5425
|
+
onDrawn: finishDrawing,
|
|
5426
|
+
onLiveEdit: setLiveMark,
|
|
5427
|
+
numbering: props.numbering
|
|
5428
|
+
})]
|
|
5429
|
+
})
|
|
5103
5430
|
})
|
|
5104
5431
|
})
|
|
5105
5432
|
}),
|
|
@@ -5243,6 +5570,7 @@ window.__ModuleLoader__.load({
|
|
|
5243
5570
|
className: "da-float da-float-detail",
|
|
5244
5571
|
role: "group",
|
|
5245
5572
|
"aria-label": t("detail"),
|
|
5573
|
+
ref: pillRef,
|
|
5246
5574
|
style: {
|
|
5247
5575
|
left: detailAt.left,
|
|
5248
5576
|
top: detailAt.top
|
|
@@ -5281,14 +5609,14 @@ window.__ModuleLoader__.load({
|
|
|
5281
5609
|
})]
|
|
5282
5610
|
})]
|
|
5283
5611
|
}),
|
|
5284
|
-
|
|
5612
|
+
shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5285
5613
|
className: "da-weight",
|
|
5286
|
-
title: t("weight"),
|
|
5614
|
+
title: t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5287
5615
|
children: [
|
|
5288
5616
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5289
5617
|
type: "range",
|
|
5290
5618
|
className: "da-weight-input",
|
|
5291
|
-
"aria-label": t("weight"),
|
|
5619
|
+
"aria-label": t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5292
5620
|
min: WEIGHT_MIN,
|
|
5293
5621
|
max: 4,
|
|
5294
5622
|
step: .1,
|
|
@@ -5297,15 +5625,49 @@ window.__ModuleLoader__.load({
|
|
|
5297
5625
|
chooseWeight(Number(event.target.value));
|
|
5298
5626
|
}
|
|
5299
5627
|
}),
|
|
5300
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5301
|
-
className: "da-weight-
|
|
5302
|
-
"
|
|
5303
|
-
|
|
5628
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
5629
|
+
className: "da-weight-svg",
|
|
5630
|
+
viewBox: "0 0 18 100",
|
|
5631
|
+
preserveAspectRatio: "none",
|
|
5632
|
+
"aria-hidden": "true",
|
|
5633
|
+
children: [
|
|
5634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("defs", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
|
|
5635
|
+
id: `${trackId}-shade`,
|
|
5636
|
+
x1: "0",
|
|
5637
|
+
y1: "1",
|
|
5638
|
+
x2: "0",
|
|
5639
|
+
y2: "0",
|
|
5640
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5641
|
+
offset: "0",
|
|
5642
|
+
stopColor: "rgba(20,40,80,.05)"
|
|
5643
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5644
|
+
offset: "1",
|
|
5645
|
+
stopColor: "rgba(20,40,80,.2)"
|
|
5646
|
+
})]
|
|
5647
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("clipPath", {
|
|
5648
|
+
id: `${trackId}-cut`,
|
|
5649
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
5650
|
+
x: "0",
|
|
5651
|
+
y: 100 - weightFraction * 100,
|
|
5652
|
+
width: "18",
|
|
5653
|
+
height: weightFraction * 100
|
|
5654
|
+
})
|
|
5655
|
+
})] }),
|
|
5656
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
5657
|
+
d: WEIGHT_TRACK,
|
|
5658
|
+
fill: `url(#${trackId}-shade)`
|
|
5659
|
+
}),
|
|
5660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
5661
|
+
d: WEIGHT_TRACK,
|
|
5662
|
+
fill: "var(--dsw-alias-brand-primary,#176df2)",
|
|
5663
|
+
clipPath: `url(#${trackId}-cut)`
|
|
5664
|
+
})
|
|
5665
|
+
]
|
|
5304
5666
|
}),
|
|
5305
5667
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5306
5668
|
className: "da-weight-knob",
|
|
5307
5669
|
"aria-hidden": true,
|
|
5308
|
-
style: { bottom: `calc(${
|
|
5670
|
+
style: { bottom: `calc(12px + ${weightFraction} * (100% - 24px - var(--da-knob-size, 11px)))` }
|
|
5309
5671
|
})
|
|
5310
5672
|
]
|
|
5311
5673
|
}),
|
|
@@ -5644,15 +6006,18 @@ window.__ModuleLoader__.load({
|
|
|
5644
6006
|
const images = [];
|
|
5645
6007
|
const numbering = new Map(document.marks.map((mark, index) => [mark.id, index + 1]));
|
|
5646
6008
|
for (const page of pages) {
|
|
5647
|
-
const
|
|
6009
|
+
const layer = {
|
|
5648
6010
|
kind: "raster",
|
|
5649
6011
|
dataUrl: page.dataUrl,
|
|
5650
6012
|
width: page.width,
|
|
5651
6013
|
height: page.height
|
|
5652
|
-
}
|
|
6014
|
+
};
|
|
6015
|
+
const marks = document.marks.filter((mark) => mark.page === page.page);
|
|
6016
|
+
const svg = composeReviewSvg(layer, marks, numbering);
|
|
6017
|
+
const paper = reviewSize(layer, marks);
|
|
5653
6018
|
images.push({
|
|
5654
6019
|
page: page.page,
|
|
5655
|
-
image: await rasterizePng(svg,
|
|
6020
|
+
image: await rasterizePng(svg, paper.width, paper.height, reviewScale(paper.width, paper.height))
|
|
5656
6021
|
});
|
|
5657
6022
|
}
|
|
5658
6023
|
return images;
|
|
@@ -6178,6 +6543,7 @@ window.__ModuleLoader__.load({
|
|
|
6178
6543
|
createAutosave,
|
|
6179
6544
|
annotationSignature,
|
|
6180
6545
|
autosaveDelayMs: 600,
|
|
6546
|
+
figureUnit,
|
|
6181
6547
|
seat: createDocumentSeat({
|
|
6182
6548
|
sessions: ctx.get("sessions"),
|
|
6183
6549
|
conversation: ctx.get("conversation"),
|
package/lib/client.pdf.js
CHANGED
|
@@ -25856,7 +25856,7 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
25856
25856
|
* @param transform - `[a b c d e f]`, user space → page units.
|
|
25857
25857
|
* @returns the placed geometry.
|
|
25858
25858
|
*/
|
|
25859
|
-
function place(mark, transform) {
|
|
25859
|
+
function place(mark, transform, unit) {
|
|
25860
25860
|
const scale = unitScale(transform);
|
|
25861
25861
|
const radians = (mark.rotation ?? 0) * Math.PI / 180;
|
|
25862
25862
|
const box = pointsBox(mark.points);
|
|
@@ -25867,7 +25867,7 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
25867
25867
|
if (mark.kind === "rect" || mark.kind === "ellipse") {
|
|
25868
25868
|
if (turned) points = turnedRing(mark.kind, mark.points, centre, radians);
|
|
25869
25869
|
} else if (spins) points = mark.points.map((point) => turnAround(point, centre, radians));
|
|
25870
|
-
const weight = mark.weight ?? 1;
|
|
25870
|
+
const weight = (mark.weight ?? 1) * unit;
|
|
25871
25871
|
return {
|
|
25872
25872
|
points: points.map((point) => toUserSpace(transform, point)),
|
|
25873
25873
|
width: STROKE_WIDTH * weight / scale,
|
|
@@ -25963,13 +25963,14 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
25963
25963
|
* @param rect - the box the appearance is drawn in.
|
|
25964
25964
|
* @returns the content stream operators.
|
|
25965
25965
|
*/
|
|
25966
|
-
function pointContent(mark, placed, rect) {
|
|
25966
|
+
function pointContent(mark, placed, rect, unit) {
|
|
25967
25967
|
const point = placed.points[0];
|
|
25968
25968
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
25969
25969
|
if (point === void 0) return "";
|
|
25970
25970
|
const [x, y] = [point[0] - rect.x, point[1] - rect.y];
|
|
25971
|
-
const
|
|
25972
|
-
const
|
|
25971
|
+
const base = Math.max(placed.width / (STROKE_WIDTH * unit), 1e-4);
|
|
25972
|
+
const radius = POINT_RADIUS / base;
|
|
25973
|
+
const dot = POINT_DOT_RADIUS / base;
|
|
25973
25974
|
const ring = ellipsePath(x, y, radius, radius);
|
|
25974
25975
|
const centre = ellipsePath(x, y, dot, dot);
|
|
25975
25976
|
return `${colorOperators(mark.color, "RG")} ${num(placed.width)} w 1 J ${ring} S ${colorOperators(mark.color, "rg")} ${centre} f`;
|
|
@@ -26014,8 +26015,8 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
26014
26015
|
* @returns the appearance stream reference, or undefined when the mark draws
|
|
26015
26016
|
* nothing a stream is needed for.
|
|
26016
26017
|
*/
|
|
26017
|
-
function appearance(context, mark, placed, rect) {
|
|
26018
|
-
const content = mark.kind === "point" ? pointContent(mark, placed, rect) : strokeContent(mark, placed, rect);
|
|
26018
|
+
function appearance(context, mark, placed, rect, unit) {
|
|
26019
|
+
const content = mark.kind === "point" ? pointContent(mark, placed, rect, unit) : strokeContent(mark, placed, rect);
|
|
26019
26020
|
if (content === "") return void 0;
|
|
26020
26021
|
const dictionary = import_cjs.PDFDict.withContext(context);
|
|
26021
26022
|
dictionary.set(import_cjs.PDFName.of("Type"), import_cjs.PDFName.of("XObject"));
|
|
@@ -26054,11 +26055,11 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
26054
26055
|
* @param transform - `[a b c d e f]`, user space → page units.
|
|
26055
26056
|
* @returns the annotation dictionary.
|
|
26056
26057
|
*/
|
|
26057
|
-
function annotation(context, mark, transform) {
|
|
26058
|
-
const placed = place(mark, transform);
|
|
26058
|
+
function annotation(context, mark, transform, unit) {
|
|
26059
|
+
const placed = place(mark, transform, unit);
|
|
26059
26060
|
if (mark.points[0] === void 0 || placed.points[0] === void 0) throw new Error("an annotation mark needs at least one coordinate pair");
|
|
26060
26061
|
const rect = boundsOf(drawnPoints(mark, placed), placed.width * 2);
|
|
26061
|
-
const box = mark.kind === "point" ? boundsOf(placed.points, POINT_RADIUS * 2) : rect;
|
|
26062
|
+
const box = mark.kind === "point" ? boundsOf(placed.points, POINT_RADIUS * unit * 2) : rect;
|
|
26062
26063
|
const dictionary = import_cjs.PDFDict.withContext(context);
|
|
26063
26064
|
dictionary.set(import_cjs.PDFName.of("Type"), import_cjs.PDFName.of("Annot"));
|
|
26064
26065
|
dictionary.set(import_cjs.PDFName.of("Subtype"), import_cjs.PDFName.of(annotationSubtype(mark)));
|
|
@@ -26102,7 +26103,7 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
26102
26103
|
dictionary.set(import_cjs.PDFName.of("LE"), endings);
|
|
26103
26104
|
}
|
|
26104
26105
|
}
|
|
26105
|
-
const stream = appearance(context, mark, placed, box);
|
|
26106
|
+
const stream = appearance(context, mark, placed, box, unit);
|
|
26106
26107
|
if (stream !== void 0) {
|
|
26107
26108
|
const streams = import_cjs.PDFDict.withContext(context);
|
|
26108
26109
|
streams.set(import_cjs.PDFName.of("N"), stream);
|
|
@@ -26150,7 +26151,10 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
|
|
|
26150
26151
|
const page = document.getPage(input.page - 1);
|
|
26151
26152
|
if (input.marks.length === 0) continue;
|
|
26152
26153
|
const annots = pageAnnots(document.context, page);
|
|
26153
|
-
for (const mark of input.marks)
|
|
26154
|
+
for (const mark of input.marks) {
|
|
26155
|
+
const unit = input.unit ?? 1;
|
|
26156
|
+
annots.push(document.context.register(annotation(document.context, mark, input.transform, unit)));
|
|
26157
|
+
}
|
|
26154
26158
|
}
|
|
26155
26159
|
return await document.save();
|
|
26156
26160
|
}
|
|
@@ -50274,7 +50278,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
50274
50278
|
});
|
|
50275
50279
|
try {
|
|
50276
50280
|
const pages = markedPages(marks);
|
|
50277
|
-
const annotatedPdf = await annotatePdf(bytes, await annotateInputs(pdf, pages));
|
|
50281
|
+
const annotatedPdf = await annotatePdf(bytes, await annotateInputs(pdf, pages, props.figureUnit));
|
|
50278
50282
|
const saved = await seats.save(address, input, annotatedPdf);
|
|
50279
50283
|
autosave.cancel();
|
|
50280
50284
|
writtenSignature.current = signature(input);
|
|
@@ -50456,14 +50460,15 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
50456
50460
|
* @param pages - the marked pages.
|
|
50457
50461
|
* @returns one input per page, each with its marks and its user-space transform.
|
|
50458
50462
|
*/
|
|
50459
|
-
async function annotateInputs(pdf, pages) {
|
|
50463
|
+
async function annotateInputs(pdf, pages, unitOf) {
|
|
50460
50464
|
const inputs = [];
|
|
50461
50465
|
for (const entry of pages) {
|
|
50462
50466
|
const size = await pdf.size(entry.page);
|
|
50463
50467
|
inputs.push({
|
|
50464
50468
|
page: entry.page,
|
|
50465
50469
|
transform: size.transform,
|
|
50466
|
-
marks: entry.marks
|
|
50470
|
+
marks: entry.marks,
|
|
50471
|
+
unit: unitOf(size.width, size.height)
|
|
50467
50472
|
});
|
|
50468
50473
|
}
|
|
50469
50474
|
return inputs;
|
package/market/index.json
CHANGED