@orbat-mapper/tactical-map-sheet 0.1.0

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.
@@ -0,0 +1,25 @@
1
+ # Public compatibility audit
2
+
3
+ This audit records the publication gate for the product contract in
4
+ [tactical-graphics#394](https://github.com/orbat-mapper/tactical-graphics/issues/394).
5
+
6
+ | Completion criterion | Evidence |
7
+ | --- | --- |
8
+ | Package remains private until completion | Privacy is removed only by the #415 release change; `publication.spec.ts` verifies the public package metadata and entry points. |
9
+ | Deterministic SVG and PDF, independent of viewport/DPR | Canonical byte fixtures and repeated-render assertions live in `product-contract.spec.ts`, `normandy-conformance.spec.ts`, and `pdf.spec.ts`. Rendering accepts no viewport or DPR input. |
10
+ | Exact page dimensions, center scale, and reported scale factors | `create-tactical-map-sheet.spec.ts` verifies physical dimensions, center compensation, footprint, center point scale, and maximum deviation; `pdf.spec.ts` verifies the PDF MediaBox. |
11
+ | UTM, UPS, MGRS, exceptions, transitions, and polar cases | `projection.spec.ts` qualifies GeographicLib/GEOTRANS vectors, hemisphere behavior, Norway/Svalbard rules, explicit zones, transitions, poles, round trips, and MGRS precision; sheet tests cover UTM and UPS grids. |
12
+ | Cross-zone and polar behavior is documented and warning-driven | The README documents `cross-zone-footprint`, `cross-projection-footprint`, and `scale-deviation`; sheet and PDF tests assert compatible structured warnings. |
13
+ | Portable control-measure portrayal and ordering | Normandy and product-contract fixtures cover ground-scale portrayal, canonical defaults/order, patterns, clipping, label clearance, and injected Open Sans metrics. |
14
+ | Vector point and custom symbols preserve their contract | Sheet tests cover bounds, anchors, size, rotation, colors, opacity, amplifiers, fallback warnings, and rejected unsupported SVG; PDF tests cover vector composition. |
15
+ | Complete marginalia | Sheet and product-contract fixtures cover metadata, declared scale, scale bar, CRS/zone, grid and true north, convergence, registration marks, and printable-footprint information. |
16
+ | No rasterized requested graphics | Controlled vector import rejects raster/linked/scripted content; SVG/PDF structural tests assert vector paths, patterns, symbols, and text. |
17
+ | PDF vector parity and exact dimensions | `pdf.spec.ts` checks MediaBox, clipping, patterns, ordering, embedded searchable fonts, deterministic bytes, vector structure, and visual parity with sibling SVG fixtures. |
18
+ | Main SVG entry does not load PDF | `node-packaging.spec.ts` and `browser-packaging.spec.ts` build both exports and verify that the root module graph contains neither jsPDF nor the PDF renderer. |
19
+ | Invalid input throws; all usable loss is identified | Public-boundary tests cover invalid geometry, projection failure, missing capabilities, unsupported vectors, clipping, off-frame and hidden graphics, fallback symbols, zone/projection crossings, and scale deviation. |
20
+ | Independent application adaptation | Closed issue #414 proves TacTrace adapts representative UTM/UPS requests without leaking application, framework, or map-engine types or runtime dependencies into this package. The deferred ORBAT Mapper integration is explicitly excluded from this publication gate by the current #415 product scope. |
21
+ | Required semantic, byte, structural, packaging, and visual coverage | The package suite contains semantic contract tests, checked SVG/PDF bytes, PDF/vector structure assertions, Node/browser packaging qualification, and pinned rendered goldens with explicit tolerances. |
22
+
23
+ The public compatibility boundary is normalized authored layers in and deterministic vector document
24
+ results out. Application stores, engine-rendered features, export UI, authorization, preview, and file
25
+ saving remain application responsibilities.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orbat Mapper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # Tactical map sheet
2
+
3
+ Public package for deterministic, scale-accurate tactical map sheets.
4
+
5
+ The package creates transparent or explicitly backed, grid-north-up UTM/UPS SVG and PDF
6
+ sheets from physical page data, a geographic center, nominal true-ground scale, and normalized authored layers.
7
+ It supports automatic or explicit projection selection, deterministic UTM/UPS MGRS grids, control
8
+ measures, renderer-neutral point symbols, custom vector symbols, and complete configurable
9
+ marginalia. One center-selected CRS is retained across zone and polar transitions and any usable
10
+ distortion or fallback is reported through structured warnings.
11
+
12
+ ## Install and entry points
13
+
14
+ ```bash
15
+ pnpm add @orbat-mapper/tactical-map-sheet
16
+ ```
17
+
18
+ The root entry point exports `createTacticalMapSheet` and all request, result, warning, metadata,
19
+ layer, MGRS, marginalia, and vector-symbol capability types. The optional PDF implementation is
20
+ only exported from `@orbat-mapper/tactical-map-sheet/pdf`, keeping PDF code and dependencies out of
21
+ the main SVG module graph.
22
+
23
+ ```ts
24
+ import {
25
+ createTacticalMapSheet,
26
+ type TacticalMapSheetRequest,
27
+ } from "@orbat-mapper/tactical-map-sheet";
28
+
29
+ const request: TacticalMapSheetRequest = {
30
+ page: { widthMm: 297, heightMm: 210, marginMm: 10, orientation: "landscape" },
31
+ center: [-2.5, 49.2],
32
+ scaleDenominator: 25_000,
33
+ grid: { intervalMeters: 1_000 },
34
+ layers: [],
35
+ marginalia: {},
36
+ };
37
+
38
+ const { svg, footprint, warnings, metadata, resources } = createTacticalMapSheet(request);
39
+ ```
40
+
41
+ `TacticalMapSheetResult` contains deterministic SVG bytes, the densified geographic printable
42
+ footprint, structured warnings, resolved projection/page/grid metadata, and the font and pattern
43
+ resources used by the document. Identical requests produce identical results independently of a
44
+ browser viewport or device-pixel ratio.
45
+
46
+ ## Contract and failure behavior
47
+
48
+ The page and map frame use millimetres. The package derives one printable footprint from the page,
49
+ center, and scale; it does not accept a competing extent. `projection: "auto"` selects one UTM or
50
+ UPS CRS from the center and retains it for the whole sheet. Layers render bottom-to-top in request
51
+ order, and each layer preserves authored graphic order. Requested control measures and symbols stay
52
+ vector in both output formats.
53
+
54
+ Invalid coordinates, contradictory page geometry, unavailable point-symbol capabilities,
55
+ projection failures, and SVG content outside the controlled vector subset throw. A usable but lossy
56
+ result is returned only with an identifying warning; requested content is never silently omitted or
57
+ rasterized.
58
+
59
+ Warnings are deterministic records with these public codes and payloads:
60
+
61
+ - `cross-zone-footprint` reports the center-selected UTM `selectedZone` and all
62
+ `intersectedZones` when the printable footprint enters another UTM zone.
63
+ - `cross-projection-footprint` reports the `selectedProjection` and the ordered
64
+ `intersectedProjections` (`ups`, `utm`) when the footprint crosses the UTM/UPS boundary. A UTM
65
+ footprint crossing that boundary and another UTM zone reports both warnings.
66
+ - `scale-deviation` reports `maximumRelativeDeviation` when frame scale differs from center scale
67
+ by at least `0.001` (0.1%). Smaller usable distortion remains available in result metadata.
68
+ - `graphic-clipped`, `graphic-outside-frame`, and `graphic-hidden` identify the affected `layerId`
69
+ and `graphicId`.
70
+ - `point-symbol-fallback` identifies a `layerId` and `graphicId` whose point-symbol capability
71
+ returned visible fallback vector content.
72
+
73
+ Regular/Caps, Italic, and Light semantic text use complete package-owned Open Sans static TTFs and
74
+ one final-size metrics service. Their license, immutable provenance, and SHA-256 identities live
75
+ under `src/assets/`.
76
+
77
+ Projection and grid primitives remain private. The `geographiclib-mgrs` backend is qualified against
78
+ GeographicLib/GEOTRANS vectors, Norway/Svalbard zone rules, explicit-zone and round-trip behavior,
79
+ UTM/UPS transitions, poles, MGRS precision boundaries, TypeScript/ESM, Node, and browser bundling.
80
+
81
+ Point-symbol renderers enter through a capability that returns complete SVG, bounds, doctrinal and
82
+ octagon anchors. Point and custom symbols then use the same conservative vector subset. Raster,
83
+ linked, scripted, malformed, or otherwise unsupported SVG throws instead of being omitted or
84
+ rasterized.
85
+
86
+ The checked conformance assets live beside `normandy-conformance.spec.ts`. Update both the canonical
87
+ SVG bytes and the 1188-pixel-wide PNG rendered by the exactly pinned `@resvg/resvg-js` dependency
88
+ with:
89
+
90
+ ```bash
91
+ pnpm --filter @orbat-mapper/tactical-map-sheet fixtures:update
92
+ ```
93
+
94
+ Review the SVG and PNG visually before committing an update. The visual comparison permits at most
95
+ 0.01% of pixels to differ by more than 8 in any RGBA channel; semantic assertions remain exact.
96
+
97
+ The optional direct PDF backend is a separate entry point, so SVG consumers never load jsPDF or its
98
+ dependencies:
99
+
100
+ ```ts
101
+ import { renderMapSheetPdf } from "@orbat-mapper/tactical-map-sheet/pdf";
102
+
103
+ const result = renderMapSheetPdf(request);
104
+ const pdfBytes = result.pdf;
105
+ ```
106
+
107
+ PDF is serialized directly from the same private millimetre scene as SVG. It retains vector paths,
108
+ tiling patterns, map-frame clipping, opacity, symbol transforms, embedded searchable Open Sans text,
109
+ canonical ordering, warnings, resources, footprint, and resolved metadata. The serializer fixes the
110
+ document timestamp and identifier, disables variable compression, and normalizes geometry through
111
+ the shared six-decimal formatter so identical requests produce identical bytes. Update the ordinary,
112
+ patterned, cross-zone, and polar canonical PDF and rendered PNG fixtures with:
113
+
114
+ ```bash
115
+ pnpm --filter @orbat-mapper/tactical-map-sheet fixtures:update:pdf
116
+ ```
117
+
118
+ The PDF fixtures are rendered by pinned PDF.js and `@napi-rs/canvas`, then compared both byte-for-byte
119
+ with their checked PNGs and visually with the sibling SVG render. The cross-renderer comparison allows
120
+ at most 2% of pixels to differ by more than 24 in one RGBA channel, or 10% for one-pixel repeating
121
+ hatching where the two vector rasterizers cover opposite edge pixels.
122
+
123
+ Application code remains responsible for content selection, authorization, export preferences,
124
+ footprint preview, displaying warnings and validation failures, and saving the returned bytes.
125
+ Application stores and map-engine output are not part of the public request contract.
126
+
127
+ ## API reference
128
+
129
+ ### Request
130
+
131
+ `TacticalMapSheetRequest` has these fields:
132
+
133
+ - `page`: required physical `widthMm` and `heightMm`, with optional non-negative `marginMm`,
134
+ `orientation`, and CSS `background` (or `"transparent"`).
135
+ - `center`: required GeoJSON longitude/latitude position; `scaleDenominator`: required positive
136
+ nominal true-ground scale at that center.
137
+ - `mapFrame`: optional `{ xMm, yMm, widthMm, heightMm }` inside the page. It is mutually exclusive
138
+ with `page.marginMm`.
139
+ - `projection`: `"auto"` (default), `{ kind: "utm", zone, hemisphere }`, or
140
+ `{ kind: "ups", hemisphere }`.
141
+ - `grid`: `false` or `MgrsGridOptions` (`intervalMeters` of 100, 1,000, 10,000, or 100,000;
142
+ matching precision 3, 2, 1, or 0; plus color, line width, and label size).
143
+ - `layers`: ordered `TacticalMapSheetLayer[]`; `pointSymbols`: the optional renderer capability;
144
+ `marginalia`: `false` or display options; `metadata`: optional document text.
145
+
146
+ Each layer has a stable `id`, ordered `{ graphic }[]`, and optional shared portrayal defaults for
147
+ color, line/dash/cap/join, and label size/clamp. A graphic is a control-measure `ControlMeasure`, a
148
+ `MapSheetPointSymbol`, or a `MapSheetCustomSymbol`.
149
+
150
+ Point symbols carry `id`, `sidc`, position, rotation in radians, pixel- or metre-based size, optional
151
+ amplifiers/modifiers, and renderer style/options. `MapSheetPointSymbolCapability.render(symbol)`
152
+ must return a `ControlledVectorSymbol`: stable resource ID, controlled-subset SVG, complete width and
153
+ height, octagon size, placement anchor, octagon anchor, and optional validation state. Custom symbols
154
+ carry that same vector record directly. Metre sizes may specify pixel clamps.
155
+
156
+ `MapSheetMarginaliaOptions` controls color and visibility of declared scale, scale bar, grid/north,
157
+ registration marks, calibration marks, and footprint. `MapSheetMetadata` supplies optional title,
158
+ subtitle, classification, and preparer text.
159
+
160
+ ### Results and resolved metadata
161
+
162
+ `createTacticalMapSheet(request)` returns `TacticalMapSheetResult`:
163
+
164
+ - `svg`: complete deterministic SVG; `footprint`: densified GeoJSON polygon.
165
+ - `warnings`: ordered structured warnings described below.
166
+ - `metadata`: resolved center, scale denominator, physical page and map frame, optional source
167
+ document metadata, optional resolved MGRS interval/precision, and resolved CRS (`kind`, zone or
168
+ hemisphere, EPSG, center point scale, center convergence in degrees, and maximum frame scale
169
+ deviation).
170
+ - `resources`: identities for embedded Open Sans fonts and deterministic pattern resources.
171
+
172
+ `renderMapSheetPdf(request)` accepts the identical request and returns `TacticalMapSheetPdfResult`:
173
+ `pdf: Uint8Array` plus the same footprint, warnings, metadata, and resources as the SVG result.
174
+
175
+ ### Warning union
176
+
177
+ `TacticalMapSheetWarning` is a discriminated union on `code`. Zone, projection, and scale warnings
178
+ carry the payloads documented earlier. `graphic-clipped`, `graphic-outside-frame`, `graphic-hidden`,
179
+ and `point-symbol-fallback` carry both `layerId` and `graphicId`. Warning order is deterministic.