dsh-figmark 1.1.0 → 1.1.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/lib/client.js +440 -161
- 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
|
|
@@ -1111,11 +1132,11 @@ window.__ModuleLoader__.load({
|
|
|
1111
1132
|
* @param points - the arrow's tail and tip.
|
|
1112
1133
|
* @returns tail, tip and both barbs; the stored points alone when it is half drawn.
|
|
1113
1134
|
*/
|
|
1114
|
-
function arrowBarbs(points, weight = 1) {
|
|
1135
|
+
function arrowBarbs(points, weight = 1, unit = 1) {
|
|
1115
1136
|
const [first, second] = points;
|
|
1116
1137
|
if (first === void 0 || second === void 0) return points;
|
|
1117
1138
|
const angle = Math.atan2(second[1] - first[1], second[0] - first[0]);
|
|
1118
|
-
const head = ARROW_HEAD * weight;
|
|
1139
|
+
const head = ARROW_HEAD * weight * unit;
|
|
1119
1140
|
const left = [second[0] - head * Math.cos(angle - ARROW_SPREAD), second[1] - head * Math.sin(angle - ARROW_SPREAD)];
|
|
1120
1141
|
const right = [second[0] - head * Math.cos(angle + ARROW_SPREAD), second[1] - head * Math.sin(angle + ARROW_SPREAD)];
|
|
1121
1142
|
return [
|
|
@@ -1170,10 +1191,10 @@ window.__ModuleLoader__.load({
|
|
|
1170
1191
|
* @param mark - the point mark.
|
|
1171
1192
|
* @returns the marker's path, number centre and box, or undefined with no coordinate.
|
|
1172
1193
|
*/
|
|
1173
|
-
function pinGeometry(mark) {
|
|
1194
|
+
function pinGeometry(mark, unit = 1) {
|
|
1174
1195
|
const tip = mark.points[0];
|
|
1175
1196
|
if (tip === void 0) return void 0;
|
|
1176
|
-
const radius =
|
|
1197
|
+
const radius = pinRadiusOf(mark, unit);
|
|
1177
1198
|
const lift = radius * PIN_LIFT;
|
|
1178
1199
|
const [tipX, tipY] = tip;
|
|
1179
1200
|
const up = tipY - lift - radius >= 0;
|
|
@@ -1207,16 +1228,16 @@ window.__ModuleLoader__.load({
|
|
|
1207
1228
|
* @param mark - the point mark.
|
|
1208
1229
|
* @returns path data, or undefined when the mark carries no coordinate.
|
|
1209
1230
|
*/
|
|
1210
|
-
function pinPathData(mark) {
|
|
1211
|
-
return pinGeometry(mark)?.path;
|
|
1231
|
+
function pinPathData(mark, unit = 1) {
|
|
1232
|
+
return pinGeometry(mark, unit)?.path;
|
|
1212
1233
|
}
|
|
1213
1234
|
/**
|
|
1214
1235
|
* Where the number inside a `point` mark's marker sits.
|
|
1215
1236
|
* @param mark - the point mark.
|
|
1216
1237
|
* @returns the number's centre, or undefined when the mark carries no coordinate.
|
|
1217
1238
|
*/
|
|
1218
|
-
function pinNumberCentre(mark) {
|
|
1219
|
-
return pinGeometry(mark)?.number;
|
|
1239
|
+
function pinNumberCentre(mark, unit = 1) {
|
|
1240
|
+
return pinGeometry(mark, unit)?.number;
|
|
1220
1241
|
}
|
|
1221
1242
|
/**
|
|
1222
1243
|
* The box a `point` mark's pin covers.
|
|
@@ -1229,8 +1250,8 @@ window.__ModuleLoader__.load({
|
|
|
1229
1250
|
* @param mark - the point mark.
|
|
1230
1251
|
* @returns the pin's box, or undefined when the mark carries no point.
|
|
1231
1252
|
*/
|
|
1232
|
-
function pointMarkBox(mark) {
|
|
1233
|
-
return pinGeometry(mark)?.box;
|
|
1253
|
+
function pointMarkBox(mark, unit = 1) {
|
|
1254
|
+
return pinGeometry(mark, unit)?.box;
|
|
1234
1255
|
}
|
|
1235
1256
|
/**
|
|
1236
1257
|
* The box a mark covers once it is drawn, rotation included.
|
|
@@ -1243,14 +1264,14 @@ window.__ModuleLoader__.load({
|
|
|
1243
1264
|
* @param mark - the mark to measure.
|
|
1244
1265
|
* @returns the box, or undefined when the mark carries no coordinate.
|
|
1245
1266
|
*/
|
|
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;
|
|
1267
|
+
function markOutline(mark, unit = 1) {
|
|
1268
|
+
if (mark.kind === "point") return pointMarkBox(mark, unit);
|
|
1269
|
+
const geometry = mark.kind === "arrow" ? arrowBarbs(mark.points, mark.weight ?? 1, unit) : mark.points;
|
|
1249
1270
|
const straight = pointsBox(geometry);
|
|
1250
1271
|
if (straight === void 0) return void 0;
|
|
1251
1272
|
const radians = markRadians(mark);
|
|
1252
1273
|
const box = radians === 0 ? straight : turnedBox(geometry, straight, radians);
|
|
1253
|
-
const grow = strokeWidthOf(mark) / 2;
|
|
1274
|
+
const grow = strokeWidthOf(mark, unit) / 2;
|
|
1254
1275
|
return {
|
|
1255
1276
|
x: box.x - grow,
|
|
1256
1277
|
y: box.y - grow,
|
|
@@ -1324,6 +1345,42 @@ window.__ModuleLoader__.load({
|
|
|
1324
1345
|
return `rotate(${n(radians * 180 / Math.PI)} ${n(cx)} ${n(cy)})`;
|
|
1325
1346
|
}
|
|
1326
1347
|
//#endregion
|
|
1348
|
+
//#region src/client/scale.ts
|
|
1349
|
+
/**
|
|
1350
|
+
* How big the marks should be for the figure they are drawn on.
|
|
1351
|
+
*
|
|
1352
|
+
* A stroke is not a physical thing: 2.5 pixels is a bold line on a 300px thumbnail
|
|
1353
|
+
* and a hairline on a 4000px scan. Marks are comparisons the reader makes against
|
|
1354
|
+
* the line work underneath them, so they have to be sized in the same currency as
|
|
1355
|
+
* the figure — a fraction of it — rather than in the figure's pixels.
|
|
1356
|
+
*
|
|
1357
|
+
* The reference edge is the size a science figure is usually exported at. An
|
|
1358
|
+
* ordinary figure then gets the unit it always had, and only the outliers (a
|
|
1359
|
+
* thumbnail, a poster) are scaled. The range is clamped rather than proportional
|
|
1360
|
+
* because a 20000px figure would otherwise get strokes 40px wide, which reads as a
|
|
1361
|
+
* highlighter rather than a line.
|
|
1362
|
+
* @module dsh-figmark/client/scale
|
|
1363
|
+
*/
|
|
1364
|
+
/** The figure edge, in pixels, that gets the unit it always had. */
|
|
1365
|
+
const REFERENCE_EDGE = 1200;
|
|
1366
|
+
/** Smallest multiple of the base sizes, for figures far below the reference. */
|
|
1367
|
+
const MIN_UNIT = .75;
|
|
1368
|
+
/**
|
|
1369
|
+
* The size everything drawn on a figure is multiplied by.
|
|
1370
|
+
*
|
|
1371
|
+
* Measured against the figure's longer edge: that is the dimension a reader judges
|
|
1372
|
+
* a stroke against, and using the area would shrink a tall thin panel's marks for a
|
|
1373
|
+
* reason the eye does not see.
|
|
1374
|
+
* @param width - the figure's intrinsic width, in figure pixels.
|
|
1375
|
+
* @param height - the figure's intrinsic height, in figure pixels.
|
|
1376
|
+
* @returns the multiple of the base sizes, held inside {@link MIN_UNIT} and {@link MAX_UNIT}.
|
|
1377
|
+
*/
|
|
1378
|
+
function figureUnit(width, height) {
|
|
1379
|
+
const edge = Math.max(width, height);
|
|
1380
|
+
if (!Number.isFinite(edge) || edge <= 0) return 1;
|
|
1381
|
+
return Math.min(3, Math.max(MIN_UNIT, edge / REFERENCE_EDGE));
|
|
1382
|
+
}
|
|
1383
|
+
//#endregion
|
|
1327
1384
|
//#region src/client/Canvas.tsx
|
|
1328
1385
|
/**
|
|
1329
1386
|
* The drawing surface.
|
|
@@ -1334,6 +1391,19 @@ window.__ModuleLoader__.load({
|
|
|
1334
1391
|
* means the same thing at any zoom or pane width.
|
|
1335
1392
|
* @module dsh-figmark/client/Canvas
|
|
1336
1393
|
*/
|
|
1394
|
+
/**
|
|
1395
|
+
* The `weight` field of a new mark, or nothing when it is the default.
|
|
1396
|
+
*
|
|
1397
|
+
* Absent means the default throughout this plugin, so writing `weight: 1` would put
|
|
1398
|
+
* a field into every mark that says nothing — sidecars would grow a key for every
|
|
1399
|
+
* mark ever drawn, and every comparison of two documents would see a difference
|
|
1400
|
+
* where there is none.
|
|
1401
|
+
* @param weight - the weight the next stroke is drawn at.
|
|
1402
|
+
* @returns the field, or an empty object for the default.
|
|
1403
|
+
*/
|
|
1404
|
+
function weightField(weight) {
|
|
1405
|
+
return weight === 1 ? {} : { weight };
|
|
1406
|
+
}
|
|
1337
1407
|
/** Hit width of an invisible selection stroke, in figure pixels. */
|
|
1338
1408
|
const HIT_WIDTH = 14;
|
|
1339
1409
|
/** Minimum distance between recorded freehand samples, in figure pixels. */
|
|
@@ -1357,6 +1427,7 @@ window.__ModuleLoader__.load({
|
|
|
1357
1427
|
*/
|
|
1358
1428
|
function Canvas(props) {
|
|
1359
1429
|
const { width, height, marks, tool, color, containerRef, anchoring } = props;
|
|
1430
|
+
const [originX, originY] = props.origin ?? [0, 0];
|
|
1360
1431
|
const weight = props.weight ?? 1;
|
|
1361
1432
|
/**
|
|
1362
1433
|
* Each mark's number, by id.
|
|
@@ -1366,6 +1437,14 @@ window.__ModuleLoader__.load({
|
|
|
1366
1437
|
* otherwise.
|
|
1367
1438
|
*/
|
|
1368
1439
|
const numbers = new Map(marks.map((mark, index) => [mark.id, props.numbering?.get(mark.id) ?? index + 1]));
|
|
1440
|
+
/**
|
|
1441
|
+
* How big everything is drawn here, as a multiple of the base sizes.
|
|
1442
|
+
*
|
|
1443
|
+
* Derived from the surface rather than passed in: every mark on this canvas shares
|
|
1444
|
+
* one figure, so one number decides the pen for all of them, and a caller that
|
|
1445
|
+
* forgot to thread it through could not make a mark disagree with its hit area.
|
|
1446
|
+
*/
|
|
1447
|
+
const unit = figureUnit(width, height);
|
|
1369
1448
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
1370
1449
|
const [press, setPress] = (0, react.useState)(null);
|
|
1371
1450
|
const drawing = (0, react.useRef)(false);
|
|
@@ -1410,7 +1489,7 @@ window.__ModuleLoader__.load({
|
|
|
1410
1489
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1411
1490
|
const scaleX = rect.width === 0 ? 1 : width / rect.width;
|
|
1412
1491
|
const scaleY = rect.height === 0 ? 1 : height / rect.height;
|
|
1413
|
-
return [(event.clientX - rect.left) * scaleX, (event.clientY - rect.top) * scaleY];
|
|
1492
|
+
return [(event.clientX - rect.left) * scaleX - originX, (event.clientY - rect.top) * scaleY - originY];
|
|
1414
1493
|
};
|
|
1415
1494
|
/**
|
|
1416
1495
|
* Describe what the mark landed on.
|
|
@@ -1592,6 +1671,7 @@ window.__ModuleLoader__.load({
|
|
|
1592
1671
|
kind: "point",
|
|
1593
1672
|
color,
|
|
1594
1673
|
points: [point],
|
|
1674
|
+
...weightField(weight),
|
|
1595
1675
|
...anchor === void 0 ? {} : { anchor }
|
|
1596
1676
|
};
|
|
1597
1677
|
props.onAdd(mark);
|
|
@@ -1608,7 +1688,7 @@ window.__ModuleLoader__.load({
|
|
|
1608
1688
|
kind: tool,
|
|
1609
1689
|
color,
|
|
1610
1690
|
points: seed,
|
|
1611
|
-
weight
|
|
1691
|
+
...weightField(weight)
|
|
1612
1692
|
});
|
|
1613
1693
|
};
|
|
1614
1694
|
const move = (event) => {
|
|
@@ -1634,10 +1714,18 @@ window.__ModuleLoader__.load({
|
|
|
1634
1714
|
...press,
|
|
1635
1715
|
snap: event.shiftKey
|
|
1636
1716
|
};
|
|
1717
|
+
const edit = editFor(held, point, dx, dy);
|
|
1637
1718
|
setPress({
|
|
1638
1719
|
...held,
|
|
1639
|
-
edit
|
|
1720
|
+
edit
|
|
1640
1721
|
});
|
|
1722
|
+
const base = props.marks.find((candidate) => candidate.id === press.id);
|
|
1723
|
+
/* v8 ignore next -- a press always begins on a mark that is still in the list. */
|
|
1724
|
+
if (base === void 0) return;
|
|
1725
|
+
const live = { ...base };
|
|
1726
|
+
if (edit.points !== void 0) live.points = edit.points;
|
|
1727
|
+
if (edit.rotation !== void 0) live.rotation = edit.rotation;
|
|
1728
|
+
props.onLiveEdit?.(live);
|
|
1641
1729
|
return;
|
|
1642
1730
|
}
|
|
1643
1731
|
if (!drawing.current || draft === null) return;
|
|
@@ -1665,6 +1753,7 @@ window.__ModuleLoader__.load({
|
|
|
1665
1753
|
if (press !== null) {
|
|
1666
1754
|
const done = press.edit;
|
|
1667
1755
|
setPress(null);
|
|
1756
|
+
props.onLiveEdit?.(null);
|
|
1668
1757
|
if (done === null) return;
|
|
1669
1758
|
if (press.handle === void 0) {
|
|
1670
1759
|
/* v8 ignore next -- a move always carries the geometry it moved to. */
|
|
@@ -1754,8 +1843,8 @@ window.__ModuleLoader__.load({
|
|
|
1754
1843
|
* compared to a washed-out sticker.
|
|
1755
1844
|
*/
|
|
1756
1845
|
const renderPoint = (mark) => {
|
|
1757
|
-
const pin = pinPathData(mark);
|
|
1758
|
-
const centre = pinNumberCentre(mark);
|
|
1846
|
+
const pin = pinPathData(mark, unit);
|
|
1847
|
+
const centre = pinNumberCentre(mark, unit);
|
|
1759
1848
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1760
1849
|
if (pin === void 0 || centre === void 0) return null;
|
|
1761
1850
|
const number = numbers.get(mark.id);
|
|
@@ -1764,21 +1853,21 @@ window.__ModuleLoader__.load({
|
|
|
1764
1853
|
d: pin,
|
|
1765
1854
|
fill: "#ffffff",
|
|
1766
1855
|
stroke: "#ffffff",
|
|
1767
|
-
strokeWidth: 4.5,
|
|
1856
|
+
strokeWidth: 4.5 * unit,
|
|
1768
1857
|
strokeLinejoin: "round"
|
|
1769
1858
|
}),
|
|
1770
1859
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1771
1860
|
d: pin,
|
|
1772
1861
|
fill: mark.color,
|
|
1773
1862
|
stroke: "#ffffff",
|
|
1774
|
-
strokeWidth: 1.5,
|
|
1863
|
+
strokeWidth: 1.5 * unit,
|
|
1775
1864
|
strokeLinejoin: "round"
|
|
1776
1865
|
}),
|
|
1777
1866
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1778
1867
|
x: centre[0],
|
|
1779
1868
|
y: centre[1],
|
|
1780
1869
|
fontFamily: PIN_FONT_FAMILY,
|
|
1781
|
-
fontSize: 11,
|
|
1870
|
+
fontSize: 11 * unit,
|
|
1782
1871
|
fontWeight: 700,
|
|
1783
1872
|
fill: PIN_NUMBER_COLOR,
|
|
1784
1873
|
textAnchor: "middle",
|
|
@@ -1796,19 +1885,19 @@ window.__ModuleLoader__.load({
|
|
|
1796
1885
|
* handles that would do nothing.
|
|
1797
1886
|
*/
|
|
1798
1887
|
const renderPointFrame = (shown) => {
|
|
1799
|
-
const box = pointMarkBox(shown);
|
|
1888
|
+
const box = pointMarkBox(shown, unit);
|
|
1800
1889
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1801
1890
|
if (box === void 0) return null;
|
|
1802
1891
|
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,
|
|
1892
|
+
x: box.x - 3 * unit,
|
|
1893
|
+
y: box.y - 3 * unit,
|
|
1894
|
+
width: box.width + 6 * unit,
|
|
1895
|
+
height: box.height + 6 * unit,
|
|
1896
|
+
rx: 3 * unit,
|
|
1808
1897
|
fill: "none",
|
|
1809
1898
|
stroke: "#4c8dff",
|
|
1810
|
-
strokeWidth:
|
|
1811
|
-
strokeDasharray:
|
|
1899
|
+
strokeWidth: unit,
|
|
1900
|
+
strokeDasharray: `${3 * unit} ${2 * unit}`,
|
|
1812
1901
|
style: { pointerEvents: "none" }
|
|
1813
1902
|
});
|
|
1814
1903
|
};
|
|
@@ -1818,7 +1907,7 @@ window.__ModuleLoader__.load({
|
|
|
1818
1907
|
const selected = props.selectedId === mark.id;
|
|
1819
1908
|
const transform = markTransform(shown);
|
|
1820
1909
|
const isPoint = mark.kind === "point";
|
|
1821
|
-
const pin = isPoint ? pinPathData(shown) : void 0;
|
|
1910
|
+
const pin = isPoint ? pinPathData(shown, unit) : void 0;
|
|
1822
1911
|
/**
|
|
1823
1912
|
* What a press on the drawn shape starts.
|
|
1824
1913
|
*
|
|
@@ -1830,7 +1919,7 @@ window.__ModuleLoader__.load({
|
|
|
1830
1919
|
d: pin,
|
|
1831
1920
|
fill: "transparent",
|
|
1832
1921
|
stroke: "transparent",
|
|
1833
|
-
strokeWidth: HIT_WIDTH,
|
|
1922
|
+
strokeWidth: HIT_WIDTH * unit,
|
|
1834
1923
|
style: {
|
|
1835
1924
|
pointerEvents: "all",
|
|
1836
1925
|
cursor: "pointer"
|
|
@@ -1855,7 +1944,7 @@ window.__ModuleLoader__.load({
|
|
|
1855
1944
|
d: path,
|
|
1856
1945
|
fill: "none",
|
|
1857
1946
|
stroke: "#ffffff",
|
|
1858
|
-
strokeWidth: strokeWidthOf(shown) + 2.5,
|
|
1947
|
+
strokeWidth: strokeWidthOf(shown, unit) + 2.5 * unit,
|
|
1859
1948
|
strokeLinecap: "round",
|
|
1860
1949
|
strokeLinejoin: "round"
|
|
1861
1950
|
}),
|
|
@@ -1863,7 +1952,7 @@ window.__ModuleLoader__.load({
|
|
|
1863
1952
|
d: path,
|
|
1864
1953
|
fill: "none",
|
|
1865
1954
|
stroke: shown.color,
|
|
1866
|
-
strokeWidth: strokeWidthOf(shown),
|
|
1955
|
+
strokeWidth: strokeWidthOf(shown, unit),
|
|
1867
1956
|
strokeLinecap: "round",
|
|
1868
1957
|
strokeLinejoin: "round"
|
|
1869
1958
|
}),
|
|
@@ -1871,7 +1960,7 @@ window.__ModuleLoader__.load({
|
|
|
1871
1960
|
d: path,
|
|
1872
1961
|
fill: "none",
|
|
1873
1962
|
stroke: "transparent",
|
|
1874
|
-
strokeWidth: HIT_WIDTH,
|
|
1963
|
+
strokeWidth: HIT_WIDTH * unit,
|
|
1875
1964
|
style: {
|
|
1876
1965
|
pointerEvents: "stroke",
|
|
1877
1966
|
cursor: "pointer"
|
|
@@ -1892,9 +1981,13 @@ window.__ModuleLoader__.load({
|
|
|
1892
1981
|
]
|
|
1893
1982
|
}, mark.id);
|
|
1894
1983
|
};
|
|
1895
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1984
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1896
1985
|
className: "da-overlay",
|
|
1897
1986
|
"data-da-overlay": "",
|
|
1987
|
+
style: {
|
|
1988
|
+
left: originX,
|
|
1989
|
+
top: originY
|
|
1990
|
+
},
|
|
1898
1991
|
width,
|
|
1899
1992
|
height,
|
|
1900
1993
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -1916,7 +2009,10 @@ window.__ModuleLoader__.load({
|
|
|
1916
2009
|
}
|
|
1917
2010
|
props.onSelect(null);
|
|
1918
2011
|
},
|
|
1919
|
-
children:
|
|
2012
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
2013
|
+
transform: `translate(${originX} ${originY})`,
|
|
2014
|
+
children: [marks.map((mark) => renderMark(mark, false)), draft === null ? null : renderMark(draft, true)]
|
|
2015
|
+
})
|
|
1920
2016
|
});
|
|
1921
2017
|
}
|
|
1922
2018
|
/** A difference that never changes, for a body that has no edit yet. */
|
|
@@ -2478,6 +2574,44 @@ window.__ModuleLoader__.load({
|
|
|
2478
2574
|
const existing = shell.snapshot.draft.trim();
|
|
2479
2575
|
shell.setDraft(existing === "" ? message : `${existing}\n\n${message}`);
|
|
2480
2576
|
}
|
|
2577
|
+
/**
|
|
2578
|
+
* The box the whole drawing occupies: the figure, plus wherever the marks reach.
|
|
2579
|
+
*
|
|
2580
|
+
* A figure with every mark inside it gets exactly its own box back, which is the
|
|
2581
|
+
* case the overlay has always drawn; the growth only happens for marks that leave
|
|
2582
|
+
* it. The margin is applied only on sides that actually grew, so an ordinary figure
|
|
2583
|
+
* is not padded on all four sides for nothing.
|
|
2584
|
+
* @param width - the figure's width, in figure pixels.
|
|
2585
|
+
* @param height - the figure's height, in figure pixels.
|
|
2586
|
+
* @param marks - the marks drawn on it.
|
|
2587
|
+
* @param unit - the figure's size multiple, so outlines are measured as drawn.
|
|
2588
|
+
* @returns the box the drawing occupies, in figure coordinates.
|
|
2589
|
+
*/
|
|
2590
|
+
function plotBounds(width, height, marks, unit = 1) {
|
|
2591
|
+
let left = 0;
|
|
2592
|
+
let top = 0;
|
|
2593
|
+
let right = width;
|
|
2594
|
+
let bottom = height;
|
|
2595
|
+
for (const mark of marks) {
|
|
2596
|
+
const box = markOutline(mark, unit);
|
|
2597
|
+
/* v8 ignore next -- a mark with no coordinate covers nothing to grow for. */
|
|
2598
|
+
if (box === void 0) continue;
|
|
2599
|
+
left = Math.min(left, box.x);
|
|
2600
|
+
top = Math.min(top, box.y);
|
|
2601
|
+
right = Math.max(right, box.x + box.width);
|
|
2602
|
+
bottom = Math.max(bottom, box.y + box.height);
|
|
2603
|
+
}
|
|
2604
|
+
const padLeft = left < 0 ? 12 : 0;
|
|
2605
|
+
const padTop = top < 0 ? 12 : 0;
|
|
2606
|
+
const padRight = right > width ? 12 : 0;
|
|
2607
|
+
const padBottom = bottom > height ? 12 : 0;
|
|
2608
|
+
return {
|
|
2609
|
+
x: left - padLeft,
|
|
2610
|
+
y: top - padTop,
|
|
2611
|
+
width: right + padRight - (left - padLeft),
|
|
2612
|
+
height: bottom + padBottom - (top - padTop)
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2481
2615
|
//#endregion
|
|
2482
2616
|
//#region src/client/export.ts
|
|
2483
2617
|
/**
|
|
@@ -2485,17 +2619,19 @@ window.__ModuleLoader__.load({
|
|
|
2485
2619
|
*
|
|
2486
2620
|
* @param mark - the mark to draw.
|
|
2487
2621
|
* @param number - the mark's number in the list, when it carries one.
|
|
2622
|
+
* @param unit - the figure's size multiple, so the export is drawn at the same
|
|
2623
|
+
* weight the reader saw.
|
|
2488
2624
|
* @returns the mark's elements.
|
|
2489
2625
|
*/
|
|
2490
|
-
function markSvg(mark, number) {
|
|
2626
|
+
function markSvg(mark, number, unit) {
|
|
2491
2627
|
const path = markPathData(mark);
|
|
2492
2628
|
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;
|
|
2629
|
+
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"/>`);
|
|
2630
|
+
const pin = mark.kind === "point" ? pinPathData(mark, unit) : void 0;
|
|
2631
|
+
const centre = mark.kind === "point" ? pinNumberCentre(mark, unit) : void 0;
|
|
2496
2632
|
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="11" font-weight="700" fill="${PIN_NUMBER_COLOR}" text-anchor="middle" dominant-baseline="central">${number}</text>`);
|
|
2633
|
+
parts.push(`<path d="${pin}" fill="${mark.color}" stroke="#ffffff" stroke-width="${2 * unit}" stroke-linejoin="round"/>`);
|
|
2634
|
+
if (number !== void 0) parts.push(`<text x="${centre[0]}" y="${centre[1]}" font-family="${PIN_FONT_FAMILY}" font-size="${11 * unit}" font-weight="700" fill="${PIN_NUMBER_COLOR}" text-anchor="middle" dominant-baseline="central">${number}</text>`);
|
|
2499
2635
|
}
|
|
2500
2636
|
return parts.join("");
|
|
2501
2637
|
}
|
|
@@ -2531,7 +2667,27 @@ window.__ModuleLoader__.load({
|
|
|
2531
2667
|
*/
|
|
2532
2668
|
function composeReviewSvg(layer, marks, numbering) {
|
|
2533
2669
|
const numbers = new Map(marks.map((mark, index) => [mark.id, numbering?.get(mark.id) ?? index + 1]));
|
|
2534
|
-
|
|
2670
|
+
const unit = figureUnit(layer.width, layer.height);
|
|
2671
|
+
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2672
|
+
const shift = `translate(${-paper.x} ${-paper.y})`;
|
|
2673
|
+
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>";
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* The size of the picture {@link composeReviewSvg} builds.
|
|
2677
|
+
*
|
|
2678
|
+
* A caller has to know it before rasterizing: an SVG rendered at the figure's size
|
|
2679
|
+
* would crop away exactly the marks that made the paper grow.
|
|
2680
|
+
* @param layer - the figure to place underneath.
|
|
2681
|
+
* @param marks - marks in draw order.
|
|
2682
|
+
* @param unit - the figure's size multiple.
|
|
2683
|
+
* @returns the paper's width and height, in figure pixels.
|
|
2684
|
+
*/
|
|
2685
|
+
function reviewSize(layer, marks, unit = figureUnit(layer.width, layer.height)) {
|
|
2686
|
+
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2687
|
+
return {
|
|
2688
|
+
width: paper.width,
|
|
2689
|
+
height: paper.height
|
|
2690
|
+
};
|
|
2535
2691
|
}
|
|
2536
2692
|
/**
|
|
2537
2693
|
* Rasterization scale for one review image.
|
|
@@ -3120,39 +3276,53 @@ window.__ModuleLoader__.load({
|
|
|
3120
3276
|
.da-palette .da-tool .da-tool-icon{height:auto}
|
|
3121
3277
|
.da-palette .da-tool .da-tool-icon svg{width:16px;height:16px;stroke-width:1.9}
|
|
3122
3278
|
|
|
3123
|
-
/* --- the
|
|
3124
|
-
/* A vertical rail along the left edge of the figure
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3279
|
+
/* --- the size rail ------------------------------------------------------- */
|
|
3280
|
+
/* A vertical rail along the left edge of the figure: how heavy the pen is, and how
|
|
3281
|
+
big a point mark's pin is. Idle it is a hairline, so it costs the figure nothing;
|
|
3282
|
+
crossing it opens it out to a track. The track is *tapered* — a thin line at the
|
|
3283
|
+
bottom and a broad one at the top — because the control's whole subject is
|
|
3284
|
+
thickness, and a wedge says which way is heavier without reading a number.
|
|
3285
|
+
A horizontal range input is rotated upright inside the pill: it keeps dragging,
|
|
3286
|
+
arrow keys, the step and the accessible name, while the rail above does the
|
|
3287
|
+
drawing. The drawn parts never take the pointer, or grabbing the knob would grab
|
|
3288
|
+
the knob instead of the slider. */
|
|
3130
3289
|
.da-weight{position:absolute;z-index:7;left:6px;top:50%;transform:translateY(-50%);
|
|
3131
|
-
display:flex;align-items:center;justify-content:center;width:
|
|
3290
|
+
display:flex;align-items:center;justify-content:center;width:7px;height:200px;
|
|
3132
3291
|
border-radius:999px;background:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
|
|
3133
3292
|
cursor:ns-resize;transition:width .16s ease,background .16s ease,box-shadow .16s ease}
|
|
3134
|
-
.da-weight:hover,.da-weight:focus-within{width:
|
|
3293
|
+
.da-weight:hover,.da-weight:focus-within{width:30px;background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3135
3294
|
box-shadow:0 5px 18px rgba(20,40,80,.16),0 0 0 0.5px var(--dsw-alias-border-l2,currentColor)}
|
|
3136
|
-
/* The
|
|
3137
|
-
.
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3295
|
+
/* The wedge: a hairline at the bottom, the heaviest width at the top. Drawn as a
|
|
3296
|
+
clipped gradient box so it costs one element and no SVG. */
|
|
3297
|
+
.da-weight::before{content:"";position:absolute;left:50%;top:14px;bottom:14px;width:11px;
|
|
3298
|
+
transform:translateX(-50%);opacity:0;
|
|
3299
|
+
background:linear-gradient(to top,var(--dsw-alias-border-l3,rgba(127,127,127,.34)),
|
|
3300
|
+
var(--dsw-alias-border-l4,rgba(127,127,127,.5)));
|
|
3301
|
+
clip-path:polygon(calc(50% - 0.5px) 100%,calc(50% + 0.5px) 100%,
|
|
3302
|
+
calc(50% + 5.5px) 0,calc(50% - 5.5px) 0);
|
|
3303
|
+
transition:opacity .16s ease}
|
|
3304
|
+
.da-weight:hover::before,.da-weight:focus-within::before{opacity:1}
|
|
3305
|
+
/* The part already chosen, drawn from the bottom up in the brand colour. */
|
|
3306
|
+
.da-weight-fill{position:absolute;left:50%;bottom:14px;width:11px;transform:translateX(-50%);
|
|
3307
|
+
opacity:0;pointer-events:none;transition:opacity .16s ease;
|
|
3308
|
+
background:var(--dsw-alias-brand-primary,#176df2);
|
|
3309
|
+
clip-path:polygon(calc(50% - 0.5px) 100%,calc(50% + 0.5px) 100%,
|
|
3310
|
+
calc(50% + 5.5px) 0,calc(50% - 5.5px) 0)}
|
|
3142
3311
|
.da-weight:hover .da-weight-fill,.da-weight:focus-within .da-weight-fill{opacity:1}
|
|
3143
|
-
/* The knob sits on the value
|
|
3144
|
-
|
|
3145
|
-
.da-weight-knob{position:absolute;left:50%;width:
|
|
3146
|
-
transform:translateX(-50%);
|
|
3312
|
+
/* The knob sits on the value: the only part that shows at rest, so the rail reads as
|
|
3313
|
+
a slider waiting to be touched rather than a bare rule. */
|
|
3314
|
+
.da-weight-knob{position:absolute;left:50%;width:11px;height:11px;border-radius:50%;
|
|
3315
|
+
transform:translateX(-50%);pointer-events:none;
|
|
3316
|
+
background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3147
3317
|
box-shadow:0 1px 4px rgba(20,40,80,.32),0 0 0 1px var(--dsw-alias-border-l3,currentColor);
|
|
3148
3318
|
transition:width .16s ease,height .16s ease,background .16s ease}
|
|
3149
3319
|
.da-weight:hover .da-weight-knob,.da-weight:focus-within .da-weight-knob{
|
|
3150
|
-
width:
|
|
3320
|
+
width:15px;height:15px;background:var(--dsw-alias-brand-primary,#176df2);
|
|
3151
3321
|
box-shadow:0 2px 7px rgba(20,40,80,.34)}
|
|
3152
|
-
/* The browser's own range, rotated upright and made transparent
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
.da-weight-input{position:absolute;left:50%;top:50%;width:
|
|
3322
|
+
/* The browser's own range, rotated upright and made transparent. It spans the whole
|
|
3323
|
+
rail so a press anywhere on it lands on the slider — including on the knob, which
|
|
3324
|
+
is why the knob is pointer-transparent. */
|
|
3325
|
+
.da-weight-input{position:absolute;left:50%;top:50%;width:200px;height:30px;margin:0;opacity:0;
|
|
3156
3326
|
transform:translate(-50%,-50%) rotate(-90deg);cursor:ns-resize;appearance:none;background:transparent}
|
|
3157
3327
|
|
|
3158
3328
|
/* --- the floating bar ---------------------------------------------------- */
|
|
@@ -3243,6 +3413,10 @@ window.__ModuleLoader__.load({
|
|
|
3243
3413
|
needs to see where the drawing stops. */
|
|
3244
3414
|
.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
3415
|
.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))}
|
|
3416
|
+
/* The paper: the figure plus the margin a mark may have needed. It is the white sheet
|
|
3417
|
+
the drawing sits on, so a mark that reaches past the figure lands on paper rather
|
|
3418
|
+
than on the panel behind it. */
|
|
3419
|
+
.da-paper{background:#fff}
|
|
3246
3420
|
.da-figure{display:block;user-select:none;-webkit-user-drag:none}
|
|
3247
3421
|
/* The table is a thing you can push. The hand only shows under the tools that
|
|
3248
3422
|
leave a drag free to move the view: under a drawing tool the same drag draws. */
|
|
@@ -3253,7 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
3253
3427
|
/* The rendered document takes the figure's place; the overlay above it owns every
|
|
3254
3428
|
pointer event, which is also what keeps a link inside it from navigating. */
|
|
3255
3429
|
.da-html{display:block;border:0;background:#fff;pointer-events:none}
|
|
3256
|
-
.da-overlay{position:absolute;
|
|
3430
|
+
.da-overlay{position:absolute;touch-action:none}
|
|
3257
3431
|
.da-overlay[data-mode="view"]{pointer-events:none}
|
|
3258
3432
|
.da-overlay[data-tool="select"]{cursor:default}
|
|
3259
3433
|
.da-overlay:not([data-tool="select"]){cursor:crosshair}
|
|
@@ -3322,9 +3496,14 @@ window.__ModuleLoader__.load({
|
|
|
3322
3496
|
box-shadow:0 8px 26px rgba(20,40,80,.2),0 0 0 1px rgba(20,40,80,.04)}
|
|
3323
3497
|
.da-float .da-comment{flex:1 1 auto;min-width:0;box-sizing:border-box;border:0;background:transparent;
|
|
3324
3498
|
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
|
-
|
|
3499
|
+
/* One line until the sentence needs more, then as many as it takes: the pill is a
|
|
3500
|
+
place to type, and a sentence read through a slot two lines tall is a sentence
|
|
3501
|
+
the reader has to scroll to finish. The rule for the record cards' textareas sets
|
|
3502
|
+
a 74px floor, hence the explicit reset. The scrollbar is hidden because it is not
|
|
3503
|
+
a scroll view — wheel and keys still work once the sentence outgrows the pane. */
|
|
3504
|
+
min-height:0;height:auto;max-height:min(180px,42vh);overflow-y:auto;outline:none;
|
|
3505
|
+
field-sizing:content;scrollbar-width:none}
|
|
3506
|
+
.da-float .da-comment::-webkit-scrollbar{width:0;height:0}
|
|
3328
3507
|
.da-float .da-comment::placeholder{color:var(--dsw-alias-label-tertiary,currentColor);opacity:.85}
|
|
3329
3508
|
/* The hand-off appears only once the field says something: an empty pill is a
|
|
3330
3509
|
place to type, not a decision to make. */
|
|
@@ -3544,13 +3723,34 @@ window.__ModuleLoader__.load({
|
|
|
3544
3723
|
* @param size - the card's size, in CSS pixels.
|
|
3545
3724
|
* @returns the card's top-left corner, in the panel's coordinates.
|
|
3546
3725
|
*/
|
|
3726
|
+
/**
|
|
3727
|
+
* The size to place a card by: the card's own, or a stand-in before it is measured.
|
|
3728
|
+
*
|
|
3729
|
+
* A comment pill grows with the sentence in it, so a placement that assumed one line
|
|
3730
|
+
* would push a long comment off the bottom of the panel. The element is measured once
|
|
3731
|
+
* it has been laid out; before that — the first frame of a selection, and in any
|
|
3732
|
+
* environment that reports zero boxes — the caller's estimate is the only size there
|
|
3733
|
+
* is, and the next pass corrects it.
|
|
3734
|
+
* @param element - the laid-out card, when there is one.
|
|
3735
|
+
* @param fallback - the size to use when the card has not been measured.
|
|
3736
|
+
* @returns the width and height to place by.
|
|
3737
|
+
*/
|
|
3738
|
+
function measuredSize(element, fallback) {
|
|
3739
|
+
if (element === null || element.offsetHeight === 0) return fallback;
|
|
3740
|
+
return {
|
|
3741
|
+
width: element.offsetWidth,
|
|
3742
|
+
height: element.offsetHeight
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3547
3745
|
function placeCard(anchor, panelWidth, panelHeight, size = {
|
|
3548
|
-
width:
|
|
3549
|
-
height:
|
|
3746
|
+
width: 300,
|
|
3747
|
+
height: 48
|
|
3550
3748
|
}) {
|
|
3551
3749
|
const below = anchor.y + anchor.height + 10;
|
|
3552
3750
|
const above = anchor.y - size.height - 10;
|
|
3553
|
-
const
|
|
3751
|
+
const roomBelow = panelHeight - 8 - below;
|
|
3752
|
+
const roomAbove = anchor.y - 10 - 8;
|
|
3753
|
+
const top = roomBelow >= size.height ? below : roomAbove >= size.height ? above : Math.max(8, roomAbove > roomBelow ? above : below);
|
|
3554
3754
|
const widest = Math.max(8, panelWidth - size.width - 8);
|
|
3555
3755
|
return {
|
|
3556
3756
|
left: Math.min(Math.max(8, anchor.x), widest),
|
|
@@ -3640,6 +3840,20 @@ window.__ModuleLoader__.load({
|
|
|
3640
3840
|
label: "toolPoint"
|
|
3641
3841
|
}
|
|
3642
3842
|
];
|
|
3843
|
+
/**
|
|
3844
|
+
* The tools that draw something with a size the rail can set.
|
|
3845
|
+
*
|
|
3846
|
+
* `select` is not among them: it draws nothing, so with it armed and no mark
|
|
3847
|
+
* selected the rail has no subject. `point` is, because a pin is sized by the same
|
|
3848
|
+
* number — the rail asks "how big is this mark" either way.
|
|
3849
|
+
*/
|
|
3850
|
+
const WEIGHT_TOOLS = /* @__PURE__ */ new Set([
|
|
3851
|
+
"arrow",
|
|
3852
|
+
"rect",
|
|
3853
|
+
"ellipse",
|
|
3854
|
+
"pen",
|
|
3855
|
+
"point"
|
|
3856
|
+
]);
|
|
3643
3857
|
/** The copy key naming the shape each kind of mark is, for the list's second line. */
|
|
3644
3858
|
const TOOL_COPY = {
|
|
3645
3859
|
arrow: "toolArrow",
|
|
@@ -3988,6 +4202,14 @@ window.__ModuleLoader__.load({
|
|
|
3988
4202
|
*/
|
|
3989
4203
|
const [detailAt, setDetailAt] = (0, react.useState)(null);
|
|
3990
4204
|
/**
|
|
4205
|
+
* The selected mark as a drag is currently shaping it.
|
|
4206
|
+
*
|
|
4207
|
+
* Kept beside the stored marks rather than in them: the gesture is not committed
|
|
4208
|
+
* until it is released, so writing it into the list would make every frame an
|
|
4209
|
+
* undoable edit. It exists only so the pill can follow the hand.
|
|
4210
|
+
*/
|
|
4211
|
+
const [liveMark, setLiveMark] = (0, react.useState)(null);
|
|
4212
|
+
/**
|
|
3991
4213
|
* Shut the style panel when the reader picks up a tool.
|
|
3992
4214
|
*
|
|
3993
4215
|
* Arming a tool is the moment the panel has done its job; leaving it open over
|
|
@@ -4175,6 +4397,23 @@ window.__ModuleLoader__.load({
|
|
|
4175
4397
|
rasterSize
|
|
4176
4398
|
]);
|
|
4177
4399
|
/**
|
|
4400
|
+
* How big everything drawn is, as a multiple of the base sizes.
|
|
4401
|
+
*
|
|
4402
|
+
* One number for the whole surface: the pen, the pins and the review image all
|
|
4403
|
+
* have to agree, and a stroke is only bold or thin relative to the figure it sits
|
|
4404
|
+
* on. A 4000px scan and a 300px thumbnail get the same-looking mark this way.
|
|
4405
|
+
*/
|
|
4406
|
+
const unit = size === null ? 1 : figureUnit(size.width, size.height);
|
|
4407
|
+
/**
|
|
4408
|
+
* The paper the drawing needs: the figure, plus anywhere the marks reach past it.
|
|
4409
|
+
*
|
|
4410
|
+
* A reader boxing the margin a plot clips, or flagging a spot just outside the
|
|
4411
|
+
* axes, is drawing something the figure does not contain. The paper grows to hold
|
|
4412
|
+
* it rather than the mark being clipped — and because the surface is white, a mark
|
|
4413
|
+
* out there lands on paper instead of on nothing.
|
|
4414
|
+
*/
|
|
4415
|
+
const paper = size === null ? null : plotBounds(size.width, size.height, marks, unit);
|
|
4416
|
+
/**
|
|
4178
4417
|
* Everything a background write needs, read at write time.
|
|
4179
4418
|
*
|
|
4180
4419
|
* A write runs from a timer that outlives the render that scheduled it, so it has
|
|
@@ -4406,31 +4645,38 @@ window.__ModuleLoader__.load({
|
|
|
4406
4645
|
const root = rootRef.current;
|
|
4407
4646
|
const host = figureRef.current;
|
|
4408
4647
|
const mark = marks.find((candidate) => candidate.id === selectedId);
|
|
4409
|
-
const
|
|
4648
|
+
const drawn = liveMark !== null && liveMark.id === selectedId ? liveMark : mark;
|
|
4649
|
+
const box = drawn === void 0 ? void 0 : markOutline(drawn, unit);
|
|
4410
4650
|
if (root === null || host === null || box === void 0 || size === null) {
|
|
4411
4651
|
setDetailAt(null);
|
|
4412
4652
|
return;
|
|
4413
4653
|
}
|
|
4414
4654
|
const panel = root.getBoundingClientRect();
|
|
4415
|
-
const
|
|
4655
|
+
const measured = host.getBoundingClientRect();
|
|
4656
|
+
const pillSize = measuredSize(detailRef.current, {
|
|
4657
|
+
width: 300,
|
|
4658
|
+
height: 48
|
|
4659
|
+
});
|
|
4416
4660
|
const at = placeCard(scaledBox(box, {
|
|
4417
|
-
x:
|
|
4418
|
-
y:
|
|
4419
|
-
width:
|
|
4420
|
-
height:
|
|
4661
|
+
x: measured.left,
|
|
4662
|
+
y: measured.top,
|
|
4663
|
+
width: measured.width,
|
|
4664
|
+
height: measured.height
|
|
4421
4665
|
}, {
|
|
4422
4666
|
x: panel.left,
|
|
4423
4667
|
y: panel.top,
|
|
4424
4668
|
width: panel.width,
|
|
4425
4669
|
height: panel.height
|
|
4426
|
-
}, size.width), panel.width, panel.height);
|
|
4670
|
+
}, size.width), panel.width, panel.height, pillSize);
|
|
4427
4671
|
setDetailAt((previous) => previous !== null && previous.left === at.left && previous.top === at.top ? previous : at);
|
|
4428
4672
|
}, [
|
|
4673
|
+
htmlHeight,
|
|
4674
|
+
liveMark,
|
|
4429
4675
|
marks,
|
|
4430
|
-
selectedId,
|
|
4431
4676
|
scale,
|
|
4677
|
+
selectedId,
|
|
4432
4678
|
size,
|
|
4433
|
-
|
|
4679
|
+
unit
|
|
4434
4680
|
]);
|
|
4435
4681
|
/**
|
|
4436
4682
|
* The factor currently drawn, kept beside the state that will hold it.
|
|
@@ -4748,8 +4994,9 @@ window.__ModuleLoader__.load({
|
|
|
4748
4994
|
]);
|
|
4749
4995
|
const exportPng = (0, react.useCallback)(async (target) => {
|
|
4750
4996
|
const svg = composeReviewSvg(target, marks);
|
|
4751
|
-
const
|
|
4752
|
-
|
|
4997
|
+
const paper = reviewSize(target, marks);
|
|
4998
|
+
const exportScale = Math.min(2, Math.max(1, 2400 / Math.max(paper.width, paper.height)));
|
|
4999
|
+
return await rasterizePng(svg, paper.width, paper.height, exportScale);
|
|
4753
5000
|
}, [marks]);
|
|
4754
5001
|
const buildDocument = (0, react.useCallback)((target, scope = marks) => {
|
|
4755
5002
|
if (loaded === null) return null;
|
|
@@ -4973,7 +5220,16 @@ window.__ModuleLoader__.load({
|
|
|
4973
5220
|
* the next stroke, which is where the remembered preference comes in.
|
|
4974
5221
|
*/
|
|
4975
5222
|
const shownColor = selected === null ? color : selected.color;
|
|
4976
|
-
|
|
5223
|
+
/**
|
|
5224
|
+
* What the size rail currently means, or null when it means nothing.
|
|
5225
|
+
*
|
|
5226
|
+
* It sizes one thing: the stroke of the mark the reader is working on, or the pen
|
|
5227
|
+
* of the next stroke. With nothing selected and no drawing tool out there is no
|
|
5228
|
+
* such thing, so the rail stands down instead of offering a control for a decision
|
|
5229
|
+
* nobody is making. A point mark borrows the same number for its pin, so the rail
|
|
5230
|
+
* stays when one is selected — it is the mark's size, whichever way it is drawn.
|
|
5231
|
+
*/
|
|
5232
|
+
const shownWeight = selected === null ? WEIGHT_TOOLS.has(tool) ? weight : null : selected.weight ?? 1;
|
|
4977
5233
|
/**
|
|
4978
5234
|
* Change one attribute of the selected mark, or of the next stroke when nothing
|
|
4979
5235
|
* is selected.
|
|
@@ -5004,16 +5260,32 @@ window.__ModuleLoader__.load({
|
|
|
5004
5260
|
}
|
|
5005
5261
|
repaintSelected(selected, { weight: held });
|
|
5006
5262
|
};
|
|
5007
|
-
const surfaceStyle =
|
|
5008
|
-
width:
|
|
5009
|
-
height:
|
|
5263
|
+
const surfaceStyle = paper === null ? {} : {
|
|
5264
|
+
width: paper.width * scale,
|
|
5265
|
+
height: paper.height * scale
|
|
5010
5266
|
};
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5267
|
+
/**
|
|
5268
|
+
* The paper at its unscaled size, with the figure placed at its own offset on it.
|
|
5269
|
+
*
|
|
5270
|
+
* The whole paper is scaled by one transform rather than each part scaling itself,
|
|
5271
|
+
* so the figure and the overlay cannot drift apart at a fractional factor — which
|
|
5272
|
+
* at 60% is exactly what a mark drawn on a line work and a mark drawn on paper
|
|
5273
|
+
* would do.
|
|
5274
|
+
*/
|
|
5275
|
+
const paperStyle = paper === null ? {} : {
|
|
5276
|
+
position: "relative",
|
|
5277
|
+
width: paper.width,
|
|
5278
|
+
height: paper.height,
|
|
5014
5279
|
transform: `scale(${scale})`,
|
|
5015
5280
|
transformOrigin: "top left"
|
|
5016
5281
|
};
|
|
5282
|
+
const figureStyle = paper === null ? {} : {
|
|
5283
|
+
position: "absolute",
|
|
5284
|
+
left: -paper.x,
|
|
5285
|
+
top: -paper.y,
|
|
5286
|
+
width: size?.width,
|
|
5287
|
+
height: size?.height
|
|
5288
|
+
};
|
|
5017
5289
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5018
5290
|
className: "da-root",
|
|
5019
5291
|
ref: rootRef,
|
|
@@ -5042,64 +5314,67 @@ window.__ModuleLoader__.load({
|
|
|
5042
5314
|
className: "da-surface",
|
|
5043
5315
|
ref: surfaceRef,
|
|
5044
5316
|
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
|
-
|
|
5317
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5318
|
+
className: "da-paper",
|
|
5319
|
+
style: paperStyle,
|
|
5320
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5321
|
+
ref: figureRef,
|
|
5322
|
+
style: figureStyle,
|
|
5323
|
+
children: [figure?.kind === "svg" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5324
|
+
ref: figureHostRef,
|
|
5325
|
+
dangerouslySetInnerHTML: { __html: figure.markup }
|
|
5326
|
+
}) : figure?.kind === "raster" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
5327
|
+
className: "da-figure",
|
|
5328
|
+
src: figure.objectUrl,
|
|
5329
|
+
width: size?.width,
|
|
5330
|
+
height: size?.height,
|
|
5331
|
+
alt: "",
|
|
5332
|
+
onLoad: (event) => {
|
|
5333
|
+
const image = event.currentTarget;
|
|
5334
|
+
setFileRasterSize({
|
|
5335
|
+
width: image.naturalWidth,
|
|
5336
|
+
height: image.naturalHeight
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
}) : figure?.kind === "html" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
5340
|
+
ref: frameRef,
|
|
5341
|
+
className: "da-html",
|
|
5342
|
+
title: t("htmlFrame"),
|
|
5343
|
+
sandbox: "allow-same-origin",
|
|
5344
|
+
style: {
|
|
5345
|
+
width: figure.width,
|
|
5346
|
+
height: htmlHeight
|
|
5347
|
+
}
|
|
5348
|
+
}) : null, paper === null || size === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Canvas, {
|
|
5349
|
+
width: paper.width,
|
|
5350
|
+
height: paper.height,
|
|
5351
|
+
origin: [-paper.x, -paper.y],
|
|
5352
|
+
marks,
|
|
5353
|
+
tool,
|
|
5354
|
+
color,
|
|
5355
|
+
weight,
|
|
5356
|
+
containerRef: figureHostRef,
|
|
5357
|
+
anchoring: isVector,
|
|
5358
|
+
anchorAtFigurePoint: figure?.kind === "html" ? anchorAtFigurePoint : void 0,
|
|
5359
|
+
selectedId,
|
|
5360
|
+
onSelect: setSelectedId,
|
|
5361
|
+
onAdd: addMark,
|
|
5362
|
+
onMove: moveMark,
|
|
5363
|
+
onUpdate: updateMark,
|
|
5364
|
+
onRemove: removeMark,
|
|
5365
|
+
onPan: panBy,
|
|
5366
|
+
onPanStart: () => {
|
|
5367
|
+
setPanning(true);
|
|
5368
|
+
},
|
|
5369
|
+
onPanEnd: () => {
|
|
5370
|
+
setPanning(false);
|
|
5371
|
+
},
|
|
5372
|
+
onEdit: editMark,
|
|
5373
|
+
onDrawn: finishDrawing,
|
|
5374
|
+
onLiveEdit: setLiveMark,
|
|
5375
|
+
numbering: props.numbering
|
|
5376
|
+
})]
|
|
5377
|
+
})
|
|
5103
5378
|
})
|
|
5104
5379
|
})
|
|
5105
5380
|
}),
|
|
@@ -5281,14 +5556,14 @@ window.__ModuleLoader__.load({
|
|
|
5281
5556
|
})]
|
|
5282
5557
|
})]
|
|
5283
5558
|
}),
|
|
5284
|
-
|
|
5559
|
+
shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5285
5560
|
className: "da-weight",
|
|
5286
|
-
title: t("weight"),
|
|
5561
|
+
title: t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5287
5562
|
children: [
|
|
5288
5563
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5289
5564
|
type: "range",
|
|
5290
5565
|
className: "da-weight-input",
|
|
5291
|
-
"aria-label": t("weight"),
|
|
5566
|
+
"aria-label": t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5292
5567
|
min: WEIGHT_MIN,
|
|
5293
5568
|
max: 4,
|
|
5294
5569
|
step: .1,
|
|
@@ -5305,7 +5580,7 @@ window.__ModuleLoader__.load({
|
|
|
5305
5580
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5306
5581
|
className: "da-weight-knob",
|
|
5307
5582
|
"aria-hidden": true,
|
|
5308
|
-
style: { bottom: `calc(${(shownWeight - WEIGHT_MIN) / (4 - WEIGHT_MIN) * 100}% - 5px)` }
|
|
5583
|
+
style: { bottom: `calc(${(shownWeight - WEIGHT_MIN) / (4 - WEIGHT_MIN) * 100}% - 5.5px)` }
|
|
5309
5584
|
})
|
|
5310
5585
|
]
|
|
5311
5586
|
}),
|
|
@@ -5644,15 +5919,18 @@ window.__ModuleLoader__.load({
|
|
|
5644
5919
|
const images = [];
|
|
5645
5920
|
const numbering = new Map(document.marks.map((mark, index) => [mark.id, index + 1]));
|
|
5646
5921
|
for (const page of pages) {
|
|
5647
|
-
const
|
|
5922
|
+
const layer = {
|
|
5648
5923
|
kind: "raster",
|
|
5649
5924
|
dataUrl: page.dataUrl,
|
|
5650
5925
|
width: page.width,
|
|
5651
5926
|
height: page.height
|
|
5652
|
-
}
|
|
5927
|
+
};
|
|
5928
|
+
const marks = document.marks.filter((mark) => mark.page === page.page);
|
|
5929
|
+
const svg = composeReviewSvg(layer, marks, numbering);
|
|
5930
|
+
const paper = reviewSize(layer, marks);
|
|
5653
5931
|
images.push({
|
|
5654
5932
|
page: page.page,
|
|
5655
|
-
image: await rasterizePng(svg,
|
|
5933
|
+
image: await rasterizePng(svg, paper.width, paper.height, reviewScale(paper.width, paper.height))
|
|
5656
5934
|
});
|
|
5657
5935
|
}
|
|
5658
5936
|
return images;
|
|
@@ -6178,6 +6456,7 @@ window.__ModuleLoader__.load({
|
|
|
6178
6456
|
createAutosave,
|
|
6179
6457
|
annotationSignature,
|
|
6180
6458
|
autosaveDelayMs: 600,
|
|
6459
|
+
figureUnit,
|
|
6181
6460
|
seat: createDocumentSeat({
|
|
6182
6461
|
sessions: ctx.get("sessions"),
|
|
6183
6462
|
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