Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/spatialillusions/milsymbol/llms.txt

Use this file to discover all available pages before exploring further.

milsymbol was built on the principle that everything used internally is accessible externally. Every symbol part, icon fragment, label placement rule, and color lookup table is a modular piece you can inspect, override, and extend. This makes it possible to add new tactical symbols, replace built-in icons, reposition text fields for special SIDCs, and write reusable symbol features — all without forking the library.

addSymbolPart — Adding Symbol Features

Symbol parts are the core extension primitive in milsymbol. Each part is a function that runs during symbol rendering and can contribute draw instructions that appear either before (pre) or after (post) the main symbol parts. A part also contributes a bounding box that is merged into the symbol’s overall bounds to ensure the canvas is large enough to include the new elements. The function receives the ms namespace as its argument and is called with this bound to the symbol instance, giving access to this.options, this.style, this.metadata, this.colors, and this.bbox.
ms.addSymbolPart(function myExtension(ms) {
  const options = this.getOptions();

  const bbox = new ms.BBox();
  const preDrawArray = [];
  const postDrawArray = [];

  // some logic using this.options, this.metadata, etc.

  return {
    pre: preDrawArray,
    post: postDrawArray,
    bbox
  };
});

Pre vs Post draw arrays

  • pre — Draw instructions in this array are rendered before all built-in symbol parts. Use pre for background elements (e.g. a colored glow or shadow) that must appear underneath the frame and icon.
  • post — Draw instructions in this array are rendered after all built-in symbol parts. Use post for overlays, labels, or decorations that must appear on top.

The debug extension as a reference

The built-in debug extension in src/symbolfunctions/debug.js is the minimal reference implementation:
// Draws the icon octagon and axis lines for debugging
ms.addSymbolPart(function debug(ms) {
  const drawArray1 = [];
  const drawArray2 = [];
  const gbbox = new ms.BBox();

  drawArray2.push({
    type: "path",
    fill: false,
    stroke: "rgb(0,0,255)",
    strokewidth: 1,
    d:
      "m 120,60 0,80 m -40,-80 0,80 m -20,-20 80,0 m 0,-40 -80,0" +
      " M 100,50 135.35534,64.64466 150,100 135.35534,135.35534" +
      " 100,150.00002 64.644661,135.35534 50,100 64.644661,64.64466 z"
  });

  return { pre: drawArray1, post: drawArray2, bbox: gbbox };
});
By adding this part, every symbol renders with a blue octagon and crosshair for development purposes.

addIconParts — Building Icon Fragments

Icons in milsymbol are assembled from reusable icon parts — small draw instructions keyed by a name string. For example, the infantry icon and the armor icon are separate parts that are combined to form the armored infantry icon. By adding new icon parts you can define reusable drawing primitives for custom SIDCs. The callback you register has the signature (iconParts, metadata, colors) and is called with this bound to the symbol instance:
ms.addIconParts(
  function (iconParts, metadata, colors) {
    /*
    iconParts: Object — The existing icon parts registry
    metadata:  Object — Symbol metadata (affiliation, dimension, echelon, etc.)
    colors:    Object — Color object for this symbol
    */

    // Add a custom tactical graphic harbor icon
    iconParts["TP.HARBOR"] = {
      type: "path",
      fill: false,
      d: "M 80,140 50,60 150,60 120,140"
    };

    // Since we're modifying the existing object directly, no return needed.
  }
);
Icon part names can be any string. By convention, custom parts that are not part of the standard library are prefixed (e.g. "TP." for tactical points) to avoid collisions with built-in parts.

addSIDCicons — Registering Custom SIDCs

ms.addSIDCicons() maps SIDC codes to icon parts and their bounding boxes. You can register entirely new SIDCs or override existing ones. The function receives the current SIDC map and bounding box map, and the std2525 flag.
ms.addSIDCicons(
  function tacticalPoints(sidc, bbox, icn, std2525) {
    /*
    sidc:     Object  — Existing SIDC → draw instruction map
    bbox:     Object  — Existing SIDC → bounding box map
    icn:      Object  — Existing icon parts registry
    std2525:  Boolean — Is it 2525 (true) or APP-6 (false)
    */

    // Create a new SIDC using an existing icon part
    sidc["G-T-D-----"] = icn["TP.DESTROY"];
    bbox["G-T-D-----"] = { x1: 0, x2: 200, y1: 40, y2: 160 };

    // Without a bounding box it defaults to the icon octagon bounds
    sidc["G-T-I-----"] = icn["TP.INTERDICT"];

    // No return needed — modifying the objects directly
  },
  "letter" // 'letter' for letter-based SIDCs, 'number' for number-based
);
The type parameter ("letter" or "number") tells milsymbol which SIDC lookup table to update. Letter-based SIDCs use hyphenated codes like "G-T-D-----". Number-based SIDCs are 15+ digit numeric strings.

Overriding an existing icon

To replace an existing symbol icon, simply assign a new draw instruction to the same SIDC key:
ms.addSIDCicons(
  function (sidc, bbox, icn, std2525) {
    // Replace the infantry icon with a custom design
    sidc["G-U-GI----"] = {
      type: "path",
      fill: "black",
      stroke: false,
      d: "M 70,70 L 130,70 L 130,130 L 70,130 Z"
    };
  },
  "letter"
);

addLabelOverrides — Custom Label Placement

For symbols where the standard text field positions don’t work — especially tactical graphics — you can register custom label placement rules for specific SIDCs. Each rule maps a modifier option name to a text draw instruction override.
ms.addLabelOverrides(
  function tacticalPoints(sidc) {
    /*
    sidc: Object — The existing label override map
    */

    // Custom label placement for SIDC G-G-GPPK--
    sidc["G-G-GPPK--"] = {
      additionalInformation: {
        stroke: false,
        textanchor: "middle",
        x: 100,
        y: -70,
        fontsize: 40
      },
      hostile: {
        stroke: false,
        textanchor: "start",
        x: 150,
        y: 45,
        fontsize: 40
      },
      uniqueDesignation: {
        stroke: false,
        textanchor: "start",
        x: 150,
        y: 0,
        fontsize: 80
      },
      dtg: {
        stroke: false,
        textanchor: "end",
        x: 50,
        y: -30,
        fontsize: 40
      },
      dtg1: {
        stroke: false,
        textanchor: "end",
        x: 50,
        y: 10,
        fontsize: 40
      }
    };

    // No return needed — modifying the object directly
  },
  "letter" // 'letter' or 'number'
);
Each key in the override object matches a modifier option name (e.g. uniqueDesignation, dtg). The value is a partial text draw instruction — any property listed here overrides the default. The x, y, and fontsize values use the same coordinate space as the draw instructions (origin at 100, 100; octagon width = 100 px).
The dtg1 key is a special suffix variant: when two DTG values need to be displayed on separate lines, the second line can be targeted as dtg1. This pattern lets you position multi-line field content independently.

TypeScript Extension Pattern

When writing milsymbol extensions in TypeScript, you can add your custom options to the SymbolOptions interface via module augmentation so the type checker accepts your new properties.
// ./your-project/src/milsymbol.d.ts
import "milsymbol";
declare module "milsymbol" {
  export interface SymbolOptions {
    customOption?: number;
  }
}
Then in your extension:
import ms from "milsymbol";
import { type DrawInstruction } from "milsymbol";

ms.addSymbolPart(function (ms) {
  const options = this.getOptions();

  const bbox = new ms.BBox();
  const preDrawArray: DrawInstruction[] = [];
  const postDrawArray: DrawInstruction[] = [];

  const custom = options.customOption; // Type-safe: number | undefined

  // your extension logic...

  return {
    pre: preDrawArray,
    post: postDrawArray,
    bbox
  };
});

setSymbolParts / getSymbolParts

For advanced scenarios where you need to replace or reorder the entire pipeline of symbol parts, use getSymbolParts() and setSymbolParts().
// Inspect the current parts
var parts = ms.getSymbolParts();
console.log(parts.length); // number of registered parts

// Replace all parts (e.g. remove all built-in parts and start fresh)
ms.setSymbolParts([myCustomPart1, myCustomPart2]);

// Insert a part at a specific position in the pipeline
var existingParts = ms.getSymbolParts();
existingParts.splice(2, 0, myNewPart); // insert at index 2
ms.setSymbolParts(existingParts);
setSymbolParts() replaces the entire parts array. If you remove built-in parts (text fields, direction arrow, engagement bar, etc.) they will no longer render on any symbol. Use this API only when you intend a full pipeline replacement.

ms.outline() — Generating Outlines from Draw Instructions

The ms.outline() utility takes a set of draw instructions and converts them to outlined (stroke-only) versions. This is used internally by the text field and engagement bar parts to produce the halo effect around labels.
ms.outline(
  drawInstruction,  // drawInstruction or Array<drawInstruction>
  outline,          // outline width (Number)
  stroke,           // original stroke width (Number)
  color             // outline color (String)
)
// Returns: Array or Object of draw instructions
Example usage inside a custom symbol part:
ms.addSymbolPart(function myPart(ms) {
  const drawArray1 = [];
  const drawArray2 = [];
  const bbox = new ms.BBox();

  const myShape = {
    type: "path",
    fill: this.colors.fillColor[this.metadata.affiliation],
    stroke: this.colors.frameColor[this.metadata.affiliation],
    strokewidth: this.style.strokeWidth,
    d: "M 80,80 L 120,80 L 120,120 L 80,120 Z"
  };

  drawArray2.push(myShape);

  // Add an outline if the global outline is enabled
  if (this.style.outlineWidth > 0) {
    drawArray1.push(
      ms.outline(
        myShape,
        this.style.outlineWidth,
        this.style.strokeWidth,
        typeof this.style.outlineColor === "object"
          ? this.style.outlineColor[this.metadata.affiliation]
          : this.style.outlineColor
      )
    );
  }

  return { pre: drawArray1, post: drawArray2, bbox };
});

Draw Instructions Reference

All custom geometry is expressed as plain JSON draw instruction objects. The coordinate origin is at (100, 100) and the icon octagon is 100 px wide and 100 px tall. The available instruction types are:
{
  type: "path",
  d: String,           // SVG path data
  fill: String | false,
  fillopacity: Number, // optional
  stroke: String | false,
  strokedasharray: String, // optional
  strokewidth: Number  // optional
}
{
  type: "circle",
  cx: Number, cy: Number, r: Number,
  fill: String | false,
  fillopacity: Number, // optional
  stroke: String | false,
  strokedasharray: String, // optional
  strokewidth: Number  // optional
}
{
  type: "text",
  x: Number, y: Number,
  textanchor: String,   // "start" | "middle" | "end"
  fontsize: Number,
  fontfamily: String,
  fontweight: String,
  fill: String | false,
  stroke: String | false,
  text: String
}
// Translate
{ type: "translate", x: Number, y: Number, draw: drawInstruction }

// Rotate
{ type: "rotate", degree: Number, x: Number, y: Number, draw: drawInstruction }

// Scale
{ type: "scale", factor: Number, draw: drawInstruction }
{
  type: "svg",
  svg: String  // full SVG XML string to embed
}
Multiple draw instructions can be grouped in an Array; the renderer handles both single objects and arrays uniformly.
The source file src/symbolfunctions/debug.js is the simplest self-contained example of a symbol part extension. Reading it alongside this guide gives you a working template to copy from. All other symbol parts in src/symbolfunctions/ demonstrate progressively more advanced patterns (text fields, direction arrows, engagement bars, HQ staves, etc.).
The docs/ directory in the milsymbol repository contains HTML test documents that render every symbol in the library. Opening these locally after adding a custom extension lets you verify that your new SIDC or label override looks correct across all affiliations and conditions.

Build docs developers (and LLMs) love