diff --git a/README.md b/README.md index 711284fa..f2cd568a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ Every icon is drawn inside a standard `viewBox="0 0 612 792"`, and is composed o Rendering is therefore a matter of choosing the SVG for each part and layering them in a defined order - there is no per-icon layout. `IdentificationSymbol` models the chosen parts and their state; `IdentificationSymbolIcon` is the JavaFX `Node` that draws it. +They line up because that one coordinate space is shared: a main icon and its sector modifiers are built within the **bounding octagon**, x 183.5 to 426.5 and y 272.5 to 516.5. That is a rule rather than a measurement, which is what lets a symbol's extent be worked out without starting a JavaFX toolkit. + +The exception is the Control Measures, and a few Cyberspace path and terrain graphics. APP-6E 8.1.3 exempts them from the composition rules: they are map graphics rather than icons assembled within the octagon, they may use any part of the canvas, and their extent has to be measured. They are marked `FREE_CANVAS` in the model, and are the only main icons that carry measured bounds. + +Each part is one SVG fragment, and what those files have to look like - content roots, the free canvas shape, normalisation, and the two checks bound to the build - is [the fragment contract](docs/fragments.md). + ## The model is generated, not hand-written APP-6E is deliberately designed so that a consumer can extend the base symbology - adding or removing elements for its own domain. Hand-maintaining the resulting combinations is impractical, so the domain model is generated instead: `jmsfx-generator` reads a YAML model and emits the Java for `jmsfx-standard`. Revisions to the standard, and per-consumer extensions, are absorbed by regenerating rather than by editing thousands of classes. diff --git a/docs/fragments.md b/docs/fragments.md new file mode 100644 index 00000000..70f70fc9 --- /dev/null +++ b/docs/fragments.md @@ -0,0 +1,159 @@ +# The fragment contract + +Every part of a symbol - the frame, the main icon, each modifier and each amplifier - is one SVG file, +and rendering a symbol means picking the right files and layering them. This describes what those files +have to look like. It applies to every icon library, `jmsfx-standard` and `hallux` alike. + +Two of these rules are checked at `verify` and will fail the build. Both report by name, so a failure +tells you which file and what is wrong with it. + +## One canvas + +Every fragment is drawn against `viewBox="0 0 612 792"`. The frame, the icon, the modifiers and the +amplifiers all share that one coordinate space and each occupies its own region of it, which is what +lets the parts line up with no per-symbol layout. + +The **bounding octagon** - x 183.5 to 426.5, y 272.5 to 516.5, centred on (305, 394.5) - is the region a +main icon and its sector modifiers are built within. It is in `BoundingOctagon.svg`, and in code as +`IconGeometry.OCTAGON`. + +That an icon keeps within the octagon is a *rule*, not a measurement, and it is what lets a symbol's +extent be worked out without starting a JavaFX toolkit. The exception is `FREE_CANVAS`, below. + +## Content roots + +**A fragment holds exactly one content element: a `` whose id names its family.** + +| family | directory | content root | +| --- | --- | --- | +| main icon | `Appendices//` | `` | +| sector one modifier | `Appendices//mod1/` | `` | +| sector two modifier | `Appendices//mod2/` | `` | +| frame | `Frames/` | `` | +| frame overlay | `Frames/Overlay/` | `` | +| echelon amplifier | `Echelon/` | `` | +| other amplifier | `Amplifier/` | `` | +| headquarters / task force / dummy | `HQTFFD/` | `` | +| operational condition | `OCA/` | `` | +| engagement bar | `Engagement/` | `` | + +All lowercase, so there is no exception to remember. It is always a ``, even where the content is a +single `` that could have carried the id itself - one rule covers every family rather than one for +OCA and another for the rest. + +Two things may sit beside the content, and nothing else may: + +- **``** - the positioning guide: the octagon outline, the `outFrame` trace and the two + sector rules. Always `display="none"`. It belongs to main icons and modifiers, which are positioned + within the octagon; frames are drawn *around* the octagon and carry no guide. +- **``** - a backdrop behind an icon that needs one, also hidden. + +Nothing else at the root: no bare drawing elements, and no groups under any other name. + +### Free canvas fragments + +The Control Measures, and three Cyberspace path and terrain graphics, are `FREE_CANVAS`: GIS construction +samples showing how a measure is drawn on a map, rather than icons assembled within the octagon. APP-6E +8.1.3 exempts them from the composition rules. They carry illustrative material alongside the content, +so they hold: + +| element | how many | what it is | +| --- | --- | --- | +| `` | exactly one | the content - what a consumer renders | +| `` | zero or one | the construction guide: the `T` / `AS` labels and the `PT 1` / `PT 2` arrows that place them | +| `` | zero or more | a worked sample, normally `display="none"`. Numbered `example1`, `example2` and so on where there is more than one | +| `` | as needed | anything `main` references. Patterns and markers belong here, never loose in the document or inside `main` | + +The template is optional because two kinds of fragment have nothing to construct. An **area** is defined +by at least three control points the user places, so there is no fixed geometry a template could draw - +that covers every area measure without one, Airhead Line included, which is an area despite the name. +And the six **Space Debris** fragments are whole symbols in the way an ordinary icon is, so `main` holds +all of it. + +### No groups that group nothing + +A `` with no attributes and no siblings is a container around exactly one thing, styling nothing and +transforming nothing. There are none left, and new ones are worth taking out: they are what an editor +leaves behind after a copy or an ungroup. + +## Graphic types + +A main element's `graphicType` in the model says how its graphic relates to the octagon, and therefore +whether its extent follows from a rule or has to be measured. + +| type | what it means | +| --- | --- | +| `NA` | no graphic at all - the entity exists only to carry a level of the hierarchy, and has no fragment | +| `MAIN`, `MAIN_1`, `MAIN_2` | built within the octagon; extent is the octagon by rule | +| `FULL_OCTAGON` | fills the octagon | +| `FULL_FRAME` | takes the frame's bounds, so the fragment is per standard identity group and its filename carries the group's suffix | +| `FREE_CANVAS` | free of the composition rules; may use any part of the canvas, so its extent is **measured** rather than derived | + +`FREE_CANVAS` is the reason `FragmentMeasurer` exists: those are the only fragments whose bounds cannot +be worked out from the rules, and the generated libraries carry the measurements. + +## Normalisation + +The fragments are edited in Inkscape, which rewrites a file wholesale on every save - its own namespace +declarations and metadata, an empty ``, a fresh crop of generated ids, re-serialised coordinates, +and the whole document either on one line or with every attribute on a line of its own. A file's diff +ends up dominated by changes that have nothing to do with the drawing. + +`FragmentNormaliser` puts it back: + +``` +mvn -q -pl jmsfx-generator exec:java \ + -Dexec.mainClass=io.github.ctgnz.jmsfx.generator.FragmentNormaliser -Dexec.args=--apply +``` + +It strips editor attributes, elements and namespace declarations, empty `` and generated ids, +shortens numbers that carry more precision than they mean, and tidies the whitespace inside coordinate +lists. Then it pretty-prints with real element nesting. + +**Every rewrite is checked against `SvgFingerprint`.** A file that would come back describing a different +drawing is reported and left alone, never written - so "this reformat changed nothing" is a property the +run verifies rather than one the author asserts. Structure, attributes and text must match exactly; +numbers are compared within a tolerance, because the precision step deliberately moves a few by a +fraction of a thousandth of a unit. + +An id that is an editor's serial number - `path2999`, `XMLID_1_` - is a generated id and goes. An id that +names part of the construction - `main`, `octagon`, `template`, `varT` - is a name and stays. The test is +an SVG element name followed by digits, and nothing else, which is deliberately conservative: it means +`frame_1_` reads as a name worth keeping, because `frame` is not an element name. It was a name. Just not +a useful one. + +## What the build checks + +| check | what it enforces | +| --- | --- | +| `FragmentNormaliser --check` | every fragment is still normalised. The fix is the same tool with `--apply`, and the failure message says so | +| `FragmentShapeChecker` | every free canvas fragment holds the shape above. It reports every fault on a file at once, so a file is named once rather than once per problem | + +Both are bound to `verify` rather than written as tests, so skipping tests cannot skip them, and both +report only - neither writes. + +`FragmentShapeChecker` covers free canvas fragments today. The other families' content roots are settled +now, so extending it is a matter of saying so. + +Editor metadata is passed over by the shape check on purpose: the normaliser's check is bound to the same +phase and already fails on it with a message that says what to do, and reporting the same file twice for +something that is not about its shape would only be noise. + +## Changing a fragment + +1. Edit it, in Inkscape or by hand. +2. Run the normaliser with `--apply`. If it **refuses** the file, it is telling you the rewrite would have + changed the drawing - that is a real difference, not a formatting one, and worth understanding before + going further. +3. If the change moved anything between groups, `SvgFingerprint` cannot help: it records structure, so a + new group or a moved element changes it by design. Verify another way - compare each drawing element + with its ancestors' attributes folded in, and re-run `FragmentMeasurer` to confirm no bounds moved. +4. Mirror it into the other tree. The shared fragments are byte-identical between `jmsfx-standard` and + `hallux`, and are meant to stay that way. +5. `mvn verify`. + +## Related + +`IconGeometry` holds the canvas, the octagon and the trim padding. `MainElement.getIconBounds()` is the +rule about extent. `FragmentMeasurer` measures what the rules cannot derive and writes it back into the +model; it needs a JavaFX toolkit, which is why it runs by hand rather than in the build. diff --git a/jmsfx-generator/src/main/java/io/github/ctgnz/jmsfx/generator/FragmentShapeChecker.java b/jmsfx-generator/src/main/java/io/github/ctgnz/jmsfx/generator/FragmentShapeChecker.java index ba012d43..acfe7f75 100644 --- a/jmsfx-generator/src/main/java/io/github/ctgnz/jmsfx/generator/FragmentShapeChecker.java +++ b/jmsfx-generator/src/main/java/io/github/ctgnz/jmsfx/generator/FragmentShapeChecker.java @@ -30,7 +30,7 @@ * * Nothing else at the root: no bare drawing elements, and no groups under any other name. The template is optional rather than required because two kinds of fragment have nothing * to construct - an area measure is defined by at least three control points the user places, so there is no fixed geometry a template could draw, and the Space Debris fragments - * are whole symbols in the way an ordinary icon is. See jmsfx#78 and {@code svg/README.md}. + * are whole symbols in the way an ordinary icon is. See jmsfx#78 and {@code docs/fragments.md}. *

* Bound to {@code verify}, because injecting fragments into the generated classes needs a rule for "which element is the content" and this is that rule. A * fragment that drifts off the shape - an editor leaving a group anonymous, a new icon arriving with its content loose at the root - fails the build where it is cheap to fix, @@ -97,7 +97,7 @@ public static void main(String[] args) throws Exception { if (!wrong.isEmpty()) { // Thrown rather than exited, because this runs in Maven's own JVM under exec:java - // System.exit would take the build down without a message worth reading. - throw new IllegalStateException(String.format("%d of %d free canvas fragment%s off the expected shape:%n %s%n%nSee svg/README.md for the shape they should hold.%n", + throw new IllegalStateException(String.format("%d of %d free canvas fragment%s off the expected shape:%n %s%n%nSee docs/fragments.md for the shape they should hold.%n", wrong.size(), checked, wrong.size() == 1 ? " is" : "s are", String.join(System.lineSeparator() + " ", wrong))); } } diff --git a/jmsfx-standard/src/main/resources/svg/README.md b/jmsfx-standard/src/main/resources/svg/README.md index 26f123f2..97878af9 100644 --- a/jmsfx-standard/src/main/resources/svg/README.md +++ b/jmsfx-standard/src/main/resources/svg/README.md @@ -1,18 +1,21 @@ -# joint-military-symbology-xml # +# The SVG fragments -## SVG Files +Every part of a symbol - frame, main icon, sector modifiers, amplifiers - is one file in here, and +rendering a symbol means picking the right files and layering them. -This folder contains a zip file with all of the SVG files supplied by DISA, for use in implementing MIL-STD-2525. +These originate with DISA, by way of Esri's `joint-military-symbology-xml` project, which modelled +MIL-STD-2525D. That project is no longer maintained and jmsfx is now the canonical fork, so the files +have moved on: they are APP-6E rather than 2525D, they have been normalised, and they hold to a +structure the build enforces. -When unzipping these files for use with the included image conversion utility, please refer to its instructions [here](../source/utilities/image-conversion-utilities/README.md). +**[The fragment contract](https://github.com/ctgnz/jmsfx/blob/master/docs/fragments.md)** is what a +fragment has to look like - content roots, the free canvas shape, what the normaliser strips, and the +two checks bound to `verify`. Read that before editing anything in here. -Known issues are documented and tracked [here](KNOWN_ISSUES.md). +Known drawing issues, inherited along with the files, are tracked in [KNOWN_ISSUES.md](KNOWN_ISSUES.md). -## Sections - -* [Naming Conventions](#naming) -* [File Details](#details) -* [Licensing](#licensing) +This file covers only how the fragments are **named**, which is unchanged from the original and is the +mapping from a SIDC to a filename. ## Naming @@ -45,39 +48,6 @@ For symbol assembly purposes, the following SIDC positions are used to determine - The default version (overlaid / or X) this uses SIDC position 7. - The optional version (colored bars) this Uses SIDC positions 3-7 along with an additional value of 2 at the end. -## Details - -This section describes the details of the internal composition of the SVG files, for those developers who -wish to make their own changes. - -### Free canvas icons - -The Control Measure fragments, and three of the Cyberspace ones, are `FREE_CANVAS`: GIS construction samples -showing how a measure is drawn on a map, rather than icons composed into a symbol. APP-6E 8.1.3 exempts them -from the composition rules. They carry illustrative material alongside the drawn content, so each one holds: - -| element | how many | what it is | -| --- | --- | --- | -| `` | exactly one | the content - what a consumer renders | -| `` | zero or one | the construction guide: anchor point labels (`T`, `AS`) and the `PT 1`/`PT 2` arrows that place them | -| `` | zero or more | a worked sample, normally `display="none"`. Where there is more than one they are numbered `example1`, `example2` and so on | -| `` | as needed | anything `main` references. Patterns and markers belong here, never loose in the document or inside `main` | - -The template is optional because two kinds of fragment have nothing to construct: - -* An **area** is defined by at least three control points the user places, so there is no fixed geometry a - template could draw. That covers every area measure without one, including Airhead Line, which is an area - despite the name. -* The **Space Debris** fragments are whole symbols in the way an ordinary icon is, so `main` holds all of it. - -Nothing outside that list appears at the root of a free canvas fragment: no bare drawing elements, and no -groups under any other name. - -### Everything else - -Every other fragment holds a single content group, named for what it is - `frame`, `main`, `echelon`, `mod1`, -`mod2`. Those names are not yet consistent across the tree; making them so is tracked separately. - ## Licensing Copyright 2014 DISA (SSMC Technical Working Group)