Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
159 changes: 159 additions & 0 deletions docs/fragments.md
Original file line number Diff line number Diff line change
@@ -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 `<g>` whose id names its family.**

| family | directory | content root |
| --- | --- | --- |
| main icon | `Appendices/<set>/` | `<g id="main">` |
| sector one modifier | `Appendices/<set>/mod1/` | `<g id="mod1">` |
| sector two modifier | `Appendices/<set>/mod2/` | `<g id="mod2">` |
| frame | `Frames/` | `<g id="frame">` |
| frame overlay | `Frames/Overlay/` | `<g id="frame_overlay">` |
| echelon amplifier | `Echelon/` | `<g id="echelon">` |
| other amplifier | `Amplifier/` | `<g id="amplifier">` |
| headquarters / task force / dummy | `HQTFFD/` | `<g id="hqtffd">` |
| operational condition | `OCA/` | `<g id="oca">` |
| engagement bar | `Engagement/` | `<g id="eng_bar">` |

All lowercase, so there is no exception to remember. It is always a `<g>`, even where the content is a
single `<rect>` 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:

- **`<g id="octagon">`** - 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.
- **`<rect id="greyBox">`** - 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 |
| --- | --- | --- |
| `<g id="main">` | exactly one | the content - what a consumer renders |
| `<g id="template">` | zero or one | the construction guide: the `T` / `AS` labels and the `PT 1` / `PT 2` arrows that place them |
| `<g id="example">` | zero or more | a worked sample, normally `display="none"`. Numbered `example1`, `example2` and so on where there is more than one |
| `<defs>` | 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 `<g>` 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 `<defs>`, 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 `<defs>` 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.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* </ul>
* 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}.
* <p>
* <strong>Bound to {@code verify}</strong>, 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,
Expand Down Expand Up @@ -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)));
}
}
Expand Down
56 changes: 13 additions & 43 deletions jmsfx-standard/src/main/resources/svg/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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 |
| --- | --- | --- |
| `<g id="main">` | exactly one | the content - what a consumer renders |
| `<g id="template">` | zero or one | the construction guide: anchor point labels (`T`, `AS`) and the `PT 1`/`PT 2` arrows that place them |
| `<g id="example">` | zero or more | a worked sample, normally `display="none"`. Where there is more than one they are numbered `example1`, `example2` and so on |
| `<defs>` | 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)
Expand Down
Loading