@orbat-mapper/tactical-draw 0.6.0 → 0.6.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/dist/index.d.mts +1 -1
- package/dist/index.mjs +66 -153
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -551,7 +551,7 @@ type SizeAnchor = "ground" | "screen";
|
|
|
551
551
|
/**
|
|
552
552
|
* The [[SizeAnchor]] a measure currently encodes (ADR-0020): `screen` when it
|
|
553
553
|
* carries its kind's pixel-denominated size option, `ground` otherwise. Keys off
|
|
554
|
-
* the same
|
|
554
|
+
* the same metadata size pairs as the bake, so a consumer deciding how to
|
|
555
555
|
* re-edit a graphic never has to re-derive the per-kind pixel-option knowledge
|
|
556
556
|
* (e.g. via a fragile key-name heuristic). Accepts the minimal `{ kind, options }`
|
|
557
557
|
* shape, mirroring [[measureUsesAdapterResolution]].
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS, applyBoxTransformOptions, canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, dragControlMeasureOptionHandle, foldsBoxTransformOptions, freezeOrientationOptions, getControlMeasureMetadata, getControlMeasureOptionHandles, getDefaultOptions, normalizeTextAmplifiers, renderControlMeasure, resolveAmplifierPlacement, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
|
|
1
|
+
import { DEFAULT_LABEL_HEIGHT_CSS_PIXELS, FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS, applyBoxTransformOptions, canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, dragControlMeasureOptionHandle, foldsBoxTransformOptions, freezeOrientationOptions, getControlMeasureMetadata, getControlMeasureOptionHandles, getDefaultOptions, normalizeTextAmplifiers, renderControlMeasure, resolveAmplifierPlacement, resolveSizePair, resolveStyleHints, roundToFixed, switchSizePairUnit } from "@orbat-mapper/control-measures";
|
|
2
2
|
function pickPointerType(originalEvent) {
|
|
3
3
|
if (typeof PointerEvent !== "undefined" && originalEvent instanceof PointerEvent) {
|
|
4
4
|
if (originalEvent.pointerType === "touch") return "touch";
|
|
@@ -1046,114 +1046,23 @@ function combineWithHostSignal(hostSignal) {
|
|
|
1046
1046
|
}
|
|
1047
1047
|
//#endregion
|
|
1048
1048
|
//#region src/tactical-draw/measure-options.ts
|
|
1049
|
-
const DEFAULT_LABEL_SIZE_PIXELS =
|
|
1050
|
-
/**
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
* (ADR-0028) — see [[sizeOptionSpecs]]. Both the resolution gate
|
|
1056
|
-
* ([[measureUsesAdapterResolution]]) and the bake ([[bakePixelSizeToGround]])
|
|
1057
|
-
* derive from this table, so a new pixel-sized kind is one entry here
|
|
1058
|
-
* (ADR-0020).
|
|
1059
|
-
*/
|
|
1060
|
-
const PIXEL_SIZE_OPTIONS = {
|
|
1061
|
-
boundary: {
|
|
1062
|
-
pixels: "echelonSizePixels",
|
|
1063
|
-
meters: "echelonSize"
|
|
1064
|
-
},
|
|
1065
|
-
"battle-position": {
|
|
1066
|
-
pixels: "echelonSizePixels",
|
|
1067
|
-
meters: "echelonSize"
|
|
1068
|
-
},
|
|
1069
|
-
"strong-point": {
|
|
1070
|
-
pixels: "echelonSizePixels",
|
|
1071
|
-
meters: "echelonSize"
|
|
1072
|
-
},
|
|
1073
|
-
"base-camp": {
|
|
1074
|
-
pixels: "echelonSizePixels",
|
|
1075
|
-
meters: "echelonSize"
|
|
1076
|
-
},
|
|
1077
|
-
"guerrilla-base": {
|
|
1078
|
-
pixels: "echelonSizePixels",
|
|
1079
|
-
meters: "echelonSize"
|
|
1080
|
-
},
|
|
1081
|
-
flot: {
|
|
1082
|
-
pixels: "radiusPixels",
|
|
1083
|
-
meters: "radius"
|
|
1084
|
-
},
|
|
1085
|
-
"fortified-line": {
|
|
1086
|
-
pixels: "toothSizePixels",
|
|
1087
|
-
meters: "toothSize"
|
|
1088
|
-
},
|
|
1089
|
-
"fortified-area": {
|
|
1090
|
-
pixels: "toothSizePixels",
|
|
1091
|
-
meters: "toothSize"
|
|
1092
|
-
},
|
|
1093
|
-
"antitank-ditch-under-construction": {
|
|
1094
|
-
pixels: "toothHeightPixels",
|
|
1095
|
-
meters: "toothHeight"
|
|
1096
|
-
},
|
|
1097
|
-
"antitank-ditch-completed": {
|
|
1098
|
-
pixels: "toothHeightPixels",
|
|
1099
|
-
meters: "toothHeight"
|
|
1100
|
-
},
|
|
1101
|
-
"antitank-wall": {
|
|
1102
|
-
pixels: "toothHeightPixels",
|
|
1103
|
-
meters: "toothHeight"
|
|
1104
|
-
},
|
|
1105
|
-
destroy: {
|
|
1106
|
-
pixels: "sizePixels",
|
|
1107
|
-
meters: "sizeMeters"
|
|
1108
|
-
},
|
|
1109
|
-
defeat: {
|
|
1110
|
-
pixels: "sizePixels",
|
|
1111
|
-
meters: "sizeMeters"
|
|
1112
|
-
},
|
|
1113
|
-
neutralize: {
|
|
1114
|
-
pixels: "sizePixels",
|
|
1115
|
-
meters: "sizeMeters"
|
|
1116
|
-
},
|
|
1117
|
-
suppress: {
|
|
1118
|
-
pixels: "sizePixels",
|
|
1119
|
-
meters: "sizeMeters"
|
|
1120
|
-
},
|
|
1121
|
-
minefield: {
|
|
1122
|
-
pixels: "sizePixels",
|
|
1123
|
-
meters: "sizeMeters"
|
|
1124
|
-
},
|
|
1125
|
-
text: {
|
|
1126
|
-
pixels: "sizePixels",
|
|
1127
|
-
meters: "sizeMeters"
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
/** The shared label-size pair every `capturesLabelSize` kind carries (ADR-0028). */
|
|
1131
|
-
const LABEL_SIZE_SPEC = {
|
|
1132
|
-
pixels: "labelSizePixels",
|
|
1133
|
-
meters: "labelSize"
|
|
1134
|
-
};
|
|
1135
|
-
/**
|
|
1136
|
-
* Every pixel/meter size-option pair `kind` can carry: its geometry entry from
|
|
1137
|
-
* [[PIXEL_SIZE_OPTIONS]] (if any) plus the shared label pair when the kind
|
|
1138
|
-
* captures a label size — a kind like Boundary can carry both anchor-bearing
|
|
1139
|
-
* dimensions at once (echelon glyph + labels).
|
|
1140
|
-
*/
|
|
1049
|
+
const DEFAULT_LABEL_SIZE_PIXELS = DEFAULT_LABEL_HEIGHT_CSS_PIXELS;
|
|
1050
|
+
/** An adapter resolution usable for pixel<->meter conversion. */
|
|
1051
|
+
function hasUsableResolution(mpp) {
|
|
1052
|
+
return mpp !== void 0 && Number.isFinite(mpp) && mpp > 0;
|
|
1053
|
+
}
|
|
1054
|
+
/** Every supported dimension, including captured labels, comes from core metadata. */
|
|
1141
1055
|
function sizeOptionSpecs(kind) {
|
|
1142
|
-
|
|
1143
|
-
const geometry = PIXEL_SIZE_OPTIONS[kind];
|
|
1144
|
-
if (geometry) specs.push(geometry);
|
|
1145
|
-
if (getControlMeasureMetadata(kind).capturesLabelSize) specs.push(LABEL_SIZE_SPEC);
|
|
1146
|
-
return specs;
|
|
1056
|
+
return getControlMeasureMetadata(kind).sizePairs ?? [];
|
|
1147
1057
|
}
|
|
1148
1058
|
/**
|
|
1149
1059
|
* Whether `kind` has at least one size that can be restored to its normal
|
|
1150
1060
|
* pixel-authored appearance at the adapter's current resolution. Kept inside
|
|
1151
|
-
* tactical-draw
|
|
1061
|
+
* tactical-draw using the core metadata catalog so hosts never need to
|
|
1152
1062
|
* infer option names or special-case captured labels.
|
|
1153
1063
|
*/
|
|
1154
1064
|
function canResetGroundSizes(measure, adapter) {
|
|
1155
|
-
|
|
1156
|
-
return mpp !== void 0 && mpp > 0 && sizeOptionSpecs(measure.kind).length > 0;
|
|
1065
|
+
return hasUsableResolution(adapter.getResolution()) && sizeOptionSpecs(measure.kind).length > 0;
|
|
1157
1066
|
}
|
|
1158
1067
|
/**
|
|
1159
1068
|
* Return the complete option record produced by resetting every known size
|
|
@@ -1167,35 +1076,30 @@ function canResetGroundSizes(measure, adapter) {
|
|
|
1167
1076
|
function resetGroundSizeOptions(measure, adapter) {
|
|
1168
1077
|
const mpp = adapter.getResolution();
|
|
1169
1078
|
const specs = sizeOptionSpecs(measure.kind);
|
|
1170
|
-
if (mpp
|
|
1079
|
+
if (!hasUsableResolution(mpp) || specs.length === 0) return void 0;
|
|
1171
1080
|
const defaults = getDefaultOptions(measure.kind);
|
|
1172
1081
|
const next = { ...measure.options ?? {} };
|
|
1173
1082
|
for (const spec of specs) {
|
|
1174
|
-
const defaultPixels = spec ===
|
|
1083
|
+
const defaultPixels = spec.defaultUnit === "px" && spec.defaultValue !== void 0 ? spec.defaultValue : defaults[spec.pixels];
|
|
1175
1084
|
if (typeof defaultPixels !== "number") continue;
|
|
1085
|
+
const result = switchSizePairUnit({
|
|
1086
|
+
kind: measure.kind,
|
|
1087
|
+
dimension: spec.id,
|
|
1088
|
+
options: {
|
|
1089
|
+
...next,
|
|
1090
|
+
[spec.pixels]: defaultPixels
|
|
1091
|
+
},
|
|
1092
|
+
unit: "m",
|
|
1093
|
+
constructionMetersPerCssPixel: mpp
|
|
1094
|
+
});
|
|
1095
|
+
if (result.status !== "converted") return void 0;
|
|
1176
1096
|
delete next[spec.pixels];
|
|
1177
|
-
next[spec.meters] =
|
|
1097
|
+
next[spec.meters] = result.value;
|
|
1178
1098
|
}
|
|
1179
1099
|
delete next.metersPerPixel;
|
|
1180
1100
|
return next;
|
|
1181
1101
|
}
|
|
1182
1102
|
/**
|
|
1183
|
-
* Whether `kind` authors `spec` in pixels — its defaults declare the pixel key
|
|
1184
|
-
* with no meter counterpart. A static property of the definition, but
|
|
1185
|
-
* `getDefaultOptions` deep-clones on every call, so it is memoised: the gate
|
|
1186
|
-
* below runs per measure per view-change frame.
|
|
1187
|
-
*/
|
|
1188
|
-
const pixelAuthoredCache = /* @__PURE__ */ new Map();
|
|
1189
|
-
function isPixelAuthored(kind, spec) {
|
|
1190
|
-
const cacheKey = `${kind}:${spec.pixels}`;
|
|
1191
|
-
const cached = pixelAuthoredCache.get(cacheKey);
|
|
1192
|
-
if (cached !== void 0) return cached;
|
|
1193
|
-
const defaults = getDefaultOptions(kind);
|
|
1194
|
-
const authored = defaults[spec.pixels] !== void 0 && defaults[spec.meters] === void 0;
|
|
1195
|
-
pixelAuthoredCache.set(cacheKey, authored);
|
|
1196
|
-
return authored;
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
1103
|
* Whether `measure` carries a pixel-denominated size option whose resolution
|
|
1200
1104
|
* to meters depends on the live map (`metersPerPixel`) — i.e. it is
|
|
1201
1105
|
* *screen-anchored*. Used both to inject the override
|
|
@@ -1209,7 +1113,7 @@ function measureUsesAdapterResolution(measure) {
|
|
|
1209
1113
|
return sizeOptionSpecs(measure.kind).some((spec) => {
|
|
1210
1114
|
if (options?.[spec.pixels] !== void 0) return true;
|
|
1211
1115
|
if (options?.[spec.meters] !== void 0) return false;
|
|
1212
|
-
return
|
|
1116
|
+
return !spec.captureOnDraw && spec.defaultUnit === "px";
|
|
1213
1117
|
});
|
|
1214
1118
|
}
|
|
1215
1119
|
/**
|
|
@@ -1241,7 +1145,7 @@ function measureRendersFromViewState(measure) {
|
|
|
1241
1145
|
/**
|
|
1242
1146
|
* The [[SizeAnchor]] a measure currently encodes (ADR-0020): `screen` when it
|
|
1243
1147
|
* carries its kind's pixel-denominated size option, `ground` otherwise. Keys off
|
|
1244
|
-
* the same
|
|
1148
|
+
* the same metadata size pairs as the bake, so a consumer deciding how to
|
|
1245
1149
|
* re-edit a graphic never has to re-derive the per-kind pixel-option knowledge
|
|
1246
1150
|
* (e.g. via a fragile key-name heuristic). Accepts the minimal `{ kind, options }`
|
|
1247
1151
|
* shape, mirroring [[measureUsesAdapterResolution]].
|
|
@@ -1254,32 +1158,32 @@ function defaultSizeAnchor(measure) {
|
|
|
1254
1158
|
return getControlMeasureMetadata(measure.kind).uniformSizing ? getSizeAnchor(measure) : "ground";
|
|
1255
1159
|
}
|
|
1256
1160
|
/**
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
* options declare the pixel-size option but no meter counterpart (e.g. Text's
|
|
1261
|
-
* `sizePixels: 24` with no `sizeMeters`) — such a fresh draw is authored in
|
|
1262
|
-
* pixels and must bake to meters, but a default draw commits with no options,
|
|
1263
|
-
* so the pixel size would otherwise never land and the graphic would persist
|
|
1264
|
-
* screen-anchored forever. No-op when the measure already carries a size key,
|
|
1265
|
-
* the kind has no pixel-size option, or the kind's default is meter-denominated
|
|
1266
|
-
* (e.g. FLOT, whose meter default governs a default draw without any bake) —
|
|
1267
|
-
* so this never changes the committed size of the pre-existing pixel kinds.
|
|
1161
|
+
* Materialize uncaptured pixel authoring defaults before commit, so the ground
|
|
1162
|
+
* bake sees an explicit value. Meter-default dimensions and authored values
|
|
1163
|
+
* remain unchanged; captured labels have their separate render/commit seam.
|
|
1268
1164
|
*/
|
|
1269
1165
|
function withDefaultAuthoringSize(measure) {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1166
|
+
let options = measure.options ?? {};
|
|
1167
|
+
let changed = false;
|
|
1168
|
+
for (const spec of sizeOptionSpecs(measure.kind)) {
|
|
1169
|
+
if (spec.captureOnDraw || spec.defaultUnit !== "px") continue;
|
|
1170
|
+
if (options[spec.pixels] !== void 0 || options[spec.meters] !== void 0) continue;
|
|
1171
|
+
const resolved = resolveSizePair({
|
|
1172
|
+
kind: measure.kind,
|
|
1173
|
+
dimension: spec.id,
|
|
1174
|
+
options
|
|
1175
|
+
});
|
|
1176
|
+
if (resolved.status !== "resolved") continue;
|
|
1177
|
+
options = {
|
|
1279
1178
|
...options,
|
|
1280
|
-
[spec.pixels]:
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1179
|
+
[spec.pixels]: resolved.value
|
|
1180
|
+
};
|
|
1181
|
+
changed = true;
|
|
1182
|
+
}
|
|
1183
|
+
return changed ? {
|
|
1184
|
+
...measure,
|
|
1185
|
+
options
|
|
1186
|
+
} : measure;
|
|
1283
1187
|
}
|
|
1284
1188
|
/**
|
|
1285
1189
|
* Bake a screen-anchored (pixel) size to a ground-anchored (meter) size at the
|
|
@@ -1291,17 +1195,26 @@ function withDefaultAuthoringSize(measure) {
|
|
|
1291
1195
|
*/
|
|
1292
1196
|
function bakePixelSizeToGround(measure, adapter) {
|
|
1293
1197
|
const options = measure.options ?? {};
|
|
1294
|
-
const specs = sizeOptionSpecs(measure.kind).filter((spec) =>
|
|
1198
|
+
const specs = sizeOptionSpecs(measure.kind).filter((spec) => options[spec.pixels] !== void 0);
|
|
1295
1199
|
if (specs.length === 0) return measure;
|
|
1296
1200
|
const mpp = adapter.getResolution();
|
|
1297
|
-
if (mpp
|
|
1298
|
-
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1201
|
+
if (!hasUsableResolution(mpp)) return measure;
|
|
1202
|
+
let next = options;
|
|
1203
|
+
for (const spec of specs) {
|
|
1204
|
+
const result = switchSizePairUnit({
|
|
1205
|
+
kind: measure.kind,
|
|
1206
|
+
dimension: spec.id,
|
|
1207
|
+
options: next,
|
|
1208
|
+
unit: "m",
|
|
1209
|
+
constructionMetersPerCssPixel: mpp
|
|
1210
|
+
});
|
|
1211
|
+
if (result.status !== "converted") return measure;
|
|
1212
|
+
next = result.options;
|
|
1213
|
+
}
|
|
1214
|
+
delete next.metersPerPixel;
|
|
1302
1215
|
return {
|
|
1303
1216
|
...measure,
|
|
1304
|
-
options:
|
|
1217
|
+
options: next
|
|
1305
1218
|
};
|
|
1306
1219
|
}
|
|
1307
1220
|
/**
|
|
@@ -1322,7 +1235,7 @@ function withCommittedAdapterMeasureOptions(measure) {
|
|
|
1322
1235
|
...measure,
|
|
1323
1236
|
options: {
|
|
1324
1237
|
...measure.options,
|
|
1325
|
-
labelSizePixels:
|
|
1238
|
+
labelSizePixels: DEFAULT_LABEL_SIZE_PIXELS
|
|
1326
1239
|
}
|
|
1327
1240
|
};
|
|
1328
1241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Engine-agnostic draw/edit core for tactical control measures (MIL-STD-2525 / APP-6). The MapAdapter ABI and bring-your-own-engine entry point for the tactical-draw package family.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Orbat Mapper",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@orbat-mapper/control-measures": "^0.
|
|
42
|
+
"@orbat-mapper/control-measures": "^0.22.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"vitest": ">=4.0.0"
|