From e344acc57d0277f28acd745f15b9c7cc8b133a77 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Thu, 28 Sep 2023 22:45:06 -0400 Subject: [PATCH 1/7] Convert shield_text to typescript --- shieldlib/src/custom_shields.ts | 8 +- shieldlib/src/screen_gfx.ts | 2 +- shieldlib/src/shield.js | 2 +- shieldlib/src/shield_canvas_draw.ts | 17 +- shieldlib/src/shield_renderer.ts | 4 +- .../src/{shield_text.mjs => shield_text.ts} | 193 ++++++++++++++---- shieldlib/src/types.ts | 12 +- 7 files changed, 180 insertions(+), 58 deletions(-) rename shieldlib/src/{shield_text.mjs => shield_text.ts} (71%) diff --git a/shieldlib/src/custom_shields.ts b/shieldlib/src/custom_shields.ts index 8eccf845b..dcf7da765 100644 --- a/shieldlib/src/custom_shields.ts +++ b/shieldlib/src/custom_shields.ts @@ -9,7 +9,7 @@ export function paBelt( r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, params: ShapeBlankParams -) { +): number { ShieldDraw.roundedRectangle(r, ctx, { fillColor: "white", strokeColor: "black", @@ -38,7 +38,7 @@ export function paBelt( ctx.lineWidth = lineWidth; ctx.stroke(); - return ctx; + return 20; } // Special case for Branson color-coded routes @@ -46,7 +46,7 @@ export function bransonRoute( r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, params: ShapeBlankParams -) { +): number { ShieldDraw.roundedRectangle(r, ctx, { fillColor: "#006747", strokeColor: "white", @@ -73,7 +73,7 @@ export function bransonRoute( ctx.lineWidth = lineWidth; ctx.stroke(); - return ctx; + return 20; } export function loadCustomShields() { diff --git a/shieldlib/src/screen_gfx.ts b/shieldlib/src/screen_gfx.ts index 225b43420..e3fe2b55c 100644 --- a/shieldlib/src/screen_gfx.ts +++ b/shieldlib/src/screen_gfx.ts @@ -3,7 +3,7 @@ import { StyleImage } from "maplibre-gl"; import rgba from "color-rgba"; const defaultFontFamily = '"sans-serif-condensed", "Arial Narrow", sans-serif'; -export const shieldFont = (size: string, fontFamily: string) => +export const shieldFont = (size: number, fontFamily: string) => `bold ${size}px ${fontFamily || defaultFontFamily}`; export const fontSizeThreshold = 12; diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index 6594d4a95..6a17c72c8 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -1,6 +1,6 @@ "use strict"; -import * as ShieldText from "./shield_text.mjs"; +import * as ShieldText from "./shield_text.js"; import * as ShieldDraw from "./shield_canvas_draw"; import * as Gfx from "./screen_gfx.js"; diff --git a/shieldlib/src/shield_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 11057815c..db542f585 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -4,7 +4,7 @@ * Shield blanks which are drawn rather built from raster shield blanks */ -import * as ShieldText from "./shield_text.mjs"; +import * as ShieldText from "./shield_text.js"; import { loadCustomShields } from "./custom_shields"; import { ShapeDrawFunction, ShieldRenderingContext } from "./shield_renderer"; import { ShapeBlankParams } from "./types"; @@ -18,7 +18,7 @@ export function computeWidth( params: ShapeBlankParams, ref: string, shape?: string -) { +): number { if (fixedWidthDefinitions[shape] !== undefined) { return r.px(fixedWidthDefinitions[shape]); } @@ -65,7 +65,7 @@ function ellipse( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -97,7 +97,10 @@ function ellipse( return width; } -export function blank(r: ShieldRenderingContext, ref: string) { +export function blank( + r: ShieldRenderingContext, + ref: string +): CanvasRenderingContext2D { var shieldWidth = ShieldText.calculateTextWidth(r, ref, r.px(genericShieldFontSize)) + r.px(2); @@ -116,7 +119,7 @@ export function roundedRectangle( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref?: string -) { +): number { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -220,7 +223,7 @@ function fishhead( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let pointUp = params.pointUp == undefined ? false : params.pointUp; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -270,6 +273,8 @@ function fishhead( ctx.strokeStyle = outline; ctx.stroke(); } + + return width; } function triangle( diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 570dc3aa6..9194feb32 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -48,8 +48,8 @@ export type ShapeDrawFunction = ( r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, params: ShapeBlankParams, - ref: string -) => void; + ref?: string +) => number; class MaplibreGLSpriteRepository implements SpriteRepository { map: Map; diff --git a/shieldlib/src/shield_text.mjs b/shieldlib/src/shield_text.ts similarity index 71% rename from shieldlib/src/shield_text.mjs rename to shieldlib/src/shield_text.ts index 964146c2d..a7cd5ca86 100644 --- a/shieldlib/src/shield_text.mjs +++ b/shieldlib/src/shield_text.ts @@ -1,14 +1,57 @@ "use strict"; import * as Gfx from "./screen_gfx.js"; +import { ShieldRenderingContext } from "./shield_renderer.js"; +import { + BoxPadding, + ShieldDefinition, + TextLayout, + TextLayoutParameters, +} from "./types.js"; const VerticalAlignment = { Middle: "middle", Top: "top", Bottom: "bottom", +} as const; + +type VerticalAlignmentType = + (typeof VerticalAlignment)[keyof typeof VerticalAlignment]; + +interface Dimension { + width: number; + height: number; +} + +type TextLayoutScaler = ( + availSize: Dimension, + textSize: Dimension, + options: TextLayoutParameters +) => TextTransform; + +interface TextTransform { + scale: number; + valign: VerticalAlignmentType; +} + +interface TextPlacement { + xBaseline: number; + yBaseline: number; + fontPx: number; +} + +let noPadding: BoxPadding = { + top: 0, + bottom: 0, + left: 0, + right: 0, }; -function ellipseScale(spaceBounds, textBounds) { +let bannerLayout: TextLayout = { + constraintFunc: "rectangle", +}; + +function ellipseScale(spaceBounds: Dimension, textBounds: Dimension): number { //Math derived from https://mathworld.wolfram.com/Ellipse-LineIntersection.html var a = spaceBounds.width; var b = spaceBounds.height; @@ -19,14 +62,20 @@ function ellipseScale(spaceBounds, textBounds) { return (a * b) / Math.sqrt(a * a * y0 * y0 + b * b * x0 * x0); } -function ellipseTextConstraint(spaceBounds, textBounds) { +function ellipseTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { return { scale: ellipseScale(spaceBounds, textBounds), valign: VerticalAlignment.Middle, }; } -function southHalfEllipseTextConstraint(spaceBounds, textBounds) { +function southHalfEllipseTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { return { scale: ellipseScale(spaceBounds, { //Turn ellipse 90 degrees @@ -37,7 +86,10 @@ function southHalfEllipseTextConstraint(spaceBounds, textBounds) { }; } -function rectTextConstraint(spaceBounds, textBounds) { +function rectTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { var scaleHeight = spaceBounds.height / textBounds.height; var scaleWidth = spaceBounds.width / textBounds.width; @@ -47,7 +99,11 @@ function rectTextConstraint(spaceBounds, textBounds) { }; } -function roundedRectTextConstraint(spaceBounds, textBounds, options) { +function roundedRectTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension, + options +): TextTransform { //Shrink space bounds so that corners hit the arcs let constraintRadius = 2; if (options !== undefined && options.radius !== undefined) { @@ -63,7 +119,10 @@ function roundedRectTextConstraint(spaceBounds, textBounds, options) { ); } -function diamondTextConstraint(spaceBounds, textBounds) { +function diamondTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { let a = spaceBounds.width; let b = spaceBounds.height; @@ -76,7 +135,10 @@ function diamondTextConstraint(spaceBounds, textBounds) { }; } -function triangleDownTextConstraint(spaceBounds, textBounds) { +function triangleDownTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { return { scale: diamondTextConstraint(spaceBounds, textBounds).scale, valign: VerticalAlignment.Top, @@ -96,13 +158,13 @@ function triangleDownTextConstraint(spaceBounds, textBounds) { * @returns JOSN object containing (X,Y) draw position and font size */ function layoutShieldText( - r, - text, - padding, - bounds, - textLayoutDef, - maxFontSize -) { + r: ShieldRenderingContext, + text: string, + padding: BoxPadding, + bounds: Dimension, + textLayoutDef: TextLayout, + maxFontSize: number = 14 +): TextPlacement { var padTop = r.px(padding.top) || 0; var padBot = r.px(padding.bottom) || 0; var padLeft = r.px(padding.left) || 0; @@ -147,7 +209,7 @@ function layoutShieldText( metrics = ctx.measureText(text); textHeight = metrics.actualBoundingBoxDescent; - var yBaseline; + let yBaseline: number; switch (textConstraint.valign) { case VerticalAlignment.Top: @@ -163,19 +225,26 @@ function layoutShieldText( } return { - xBaseline: xBaseline, - yBaseline: yBaseline, + xBaseline, + yBaseline, fontPx: fontSize, }; } -const defaultDefForLayout = { +const defaultDefForLayout: ShieldDefinition = { padding: { top: 0, bottom: 0, left: 0, right: 0, }, + shapeBlank: { + drawFunc: "rectangle", + params: { + fillColor: "white", + strokeColor: "black", + }, + }, }; /** @@ -188,13 +257,18 @@ const defaultDefForLayout = { * @param {*} bounds - size of the overall graphics area * @returns JOSN object containing (X,Y) draw position and font size */ -export function layoutShieldTextFromDef(r, text, def, bounds) { +export function layoutShieldTextFromDef( + r: ShieldRenderingContext, + text: string, + def: ShieldDefinition, + bounds: Dimension +): TextPlacement { //FIX if (def == null) { def = defaultDefForLayout; } - var padding = def.padding || {}; + var padding = def.padding || noPadding; var textLayoutDef = { constraintFunc: "rect", @@ -221,7 +295,12 @@ export function layoutShieldTextFromDef(r, text, def, bounds) { * @param {*} text - text to draw * @param {*} textLayout - location to draw text */ -export function renderShieldText(r, ctx, text, textLayout) { +export function renderShieldText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + textLayout: TextPlacement +): void { //Text color is set by fillStyle configureShieldText(r, ctx, textLayout); @@ -236,11 +315,16 @@ export function renderShieldText(r, ctx, text, textLayout) { * @param {*} text - text to draw * @param {*} textLayout - location to draw text */ -export function drawShieldHaloText(r, ctx, text, textLayout) { +export function drawShieldHaloText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + textLayout: TextPlacement +): void { //Stroke color is set by strokeStyle configureShieldText(r, ctx, textLayout); - ctx.shadowColor = ctx.strokeStyle; + ctx.shadowColor = ctx.strokeStyle.toString(); ctx.shadowBlur = 0; ctx.lineWidth = r.px(2); @@ -249,7 +333,11 @@ export function drawShieldHaloText(r, ctx, text, textLayout) { ctx.shadowBlur = null; } -function configureShieldText(r, ctx, textLayout) { +function configureShieldText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + textLayout: TextPlacement +): void { ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.font = Gfx.shieldFont(textLayout.fontPx, r.options.shieldFont); @@ -263,7 +351,12 @@ function configureShieldText(r, ctx, textLayout) { * @param {*} text - text to draw * @param {*} bannerIndex - plate position to draw, 0=top, incrementing */ -export function drawBannerText(r, ctx, text, bannerIndex) { +export function drawBannerText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number +): void { drawBannerTextComponent(r, ctx, text, bannerIndex, true); } @@ -275,7 +368,12 @@ export function drawBannerText(r, ctx, text, bannerIndex) { * @param {*} text - text to draw * @param {*} bannerIndex - plate position to draw, 0=top, incrementing */ -export function drawBannerHaloText(r, ctx, text, bannerIndex) { +export function drawBannerHaloText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number +): void { drawBannerTextComponent(r, ctx, text, bannerIndex, false); } @@ -288,19 +386,32 @@ export function drawBannerHaloText(r, ctx, text, bannerIndex) { * @param {*} bannerIndex - plate position to draw, 0=top, incrementing * @param {*} textComponent - if true, draw the text. If false, draw the halo */ -function drawBannerTextComponent(r, ctx, text, bannerIndex, textComponent) { +function drawBannerTextComponent( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number, + textComponent: boolean +): void { const bannerPadding = { - padding: { - top: r.options.bannerPadding, - bottom: 0, - left: 0, - right: 0, - }, + top: r.options.bannerPadding, + bottom: 0, + left: 0, + right: 0, }; - var textLayout = layoutShieldTextFromDef(r, text, bannerPadding, { + + let bannerBounds: Dimension = { width: ctx.canvas.width, height: r.px(r.options.bannerHeight - r.options.bannerPadding), - }); + }; + + let textLayout: TextPlacement = layoutShieldText( + r, + text, + bannerPadding, + bannerBounds, + bannerLayout + ); ctx.font = Gfx.shieldFont(textLayout.fontPx, r.options.shieldFont); ctx.textBaseline = "top"; @@ -330,16 +441,16 @@ function drawBannerTextComponent(r, ctx, text, bannerIndex, textComponent) { } } -export function calculateTextWidth(r, text, fontSize) { +export function calculateTextWidth( + r: ShieldRenderingContext, + text: string, + fontSize: number +): number { var ctx = r.emptySprite(); //dummy canvas ctx.font = Gfx.shieldFont(fontSize, r.options.shieldFont); return Math.ceil(ctx.measureText(text).width); } -export function drawText(name, options, ref) { - return drawTextFunctions[name](options, ref); -} - //Register text draw functions const drawTextFunctions = {}; @@ -349,7 +460,7 @@ const drawTextFunctions = {}; * @param {*} name name of the function as referenced by the shield definition * @param {*} fxn callback to the implementing function. Takes two parameters, ref and options */ -function registerDrawTextFunction(name, fxn) { +function registerDrawTextFunction(name: string, fxn: TextLayoutScaler): void { drawTextFunctions[name] = fxn; } diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 5912be5de..f3ea868b5 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -27,6 +27,8 @@ export interface ShieldDefinitionBase { banners?: string[]; /** If true, no next should be drawn on this shield */ notext?: boolean; + /** Specifies the maximum font size of text on this shield */ + maxFontSize?: number; } /** @@ -98,12 +100,16 @@ export interface ShapeBlankParams { sideAngle?: number; } +/** Parameters for laying out text on a shield */ +export interface TextLayoutParameters { + /** Specify a corner radius to further constrain placement on rectangular shapes */ + radius: number; +} + /** Parameters for laying out text on a shield background */ export interface TextLayout { constraintFunc: string; - options?: { - radius: number; - }; + options?: TextLayoutParameters; } /** From 4a556d14f09d7cbc7fc0da0ee7b4fe64a9eab82c Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Tue, 3 Oct 2023 21:02:49 -0400 Subject: [PATCH 2/7] Resolve conflicts --- shieldlib/src/shield_canvas_draw.ts | 37 +++++++++++++---------------- shieldlib/src/shield_text.ts | 4 ---- shieldlib/src/types.ts | 4 ---- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/shieldlib/src/shield_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 03960659c..68bede46b 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -4,7 +4,7 @@ * Shield blanks which are drawn rather built from raster shield blanks */ -import * as ShieldText from "./shield_text.js"; +import * as ShieldText from "./shield_text.mjs"; import { loadCustomShields } from "./custom_shields"; import { ShapeDrawFunction, ShieldRenderingContext } from "./shield_renderer"; import { ShapeBlankParams } from "./types"; @@ -18,7 +18,7 @@ export function computeWidth( params: ShapeBlankParams, ref: string, shape?: string -): number { +) { if (fixedWidthDefinitions[shape] !== undefined) { return r.px(fixedWidthDefinitions[shape]); } @@ -97,10 +97,7 @@ function ellipse( return width; } -export function blank( - r: ShieldRenderingContext, - ref: string -): CanvasRenderingContext2D { +export function blank(r: ShieldRenderingContext, ref: string) { var shieldWidth = ShieldText.calculateTextWidth(r, ref, r.px(genericShieldFontSize)) + r.px(2); @@ -166,7 +163,7 @@ function escutcheon( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -282,7 +279,7 @@ function triangle( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let pointUp = params.pointUp == undefined ? false : params.pointUp; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -348,7 +345,7 @@ function trapezoid( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let shortSideUp = params.shortSideUp == undefined ? false : params.shortSideUp; let sideAngle = params.sideAngle == undefined ? 0 : params.sideAngle; @@ -410,7 +407,7 @@ function diamond( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -479,7 +476,7 @@ function pentagon( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let pointUp = params.pointUp == undefined ? true : params.pointUp; let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let sideAngle = params.sideAngle == undefined ? 0 : params.sideAngle; @@ -552,7 +549,7 @@ function hexagonVertical( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -606,17 +603,17 @@ function hexagonHorizontal( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { - let angle = params.sideAngle == undefined ? 0 : params.sideAngle; +): number { + let sideAngle = params.sideAngle == undefined ? 0 : params.sideAngle; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; let outlineWidth = params.outlineWidth == undefined ? 1 : params.outlineWidth; - let sine = Math.sin(angle); - let cosine = Math.cos(angle); - let tangent = Math.tan(angle); - let halfComplementTangent = Math.tan(Math.PI / 4 - angle / 2); + let sine = Math.sin(sideAngle); + let cosine = Math.cos(sideAngle); + let tangent = Math.tan(sideAngle); + let halfComplementTangent = Math.tan(Math.PI / 4 - sideAngle / 2); let width = computeWidth(r, params, ref, "hexagonHorizontal"); @@ -672,7 +669,7 @@ function octagonVertical( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let sideAngle = params.sideAngle == undefined ? 0 : params.sideAngle; let fill = params.fillColor == undefined ? "white" : params.fillColor; @@ -769,7 +766,7 @@ export function draw( ctx: CanvasRenderingContext2D, params: ShapeBlankParams, ref: string -) { +): number { return drawFunctions[name](r, ctx, params, ref); } diff --git a/shieldlib/src/shield_text.ts b/shieldlib/src/shield_text.ts index 197de1170..e9dc755ef 100644 --- a/shieldlib/src/shield_text.ts +++ b/shieldlib/src/shield_text.ts @@ -102,11 +102,7 @@ function rectTextConstraint( function roundedRectTextConstraint( spaceBounds: Dimension, textBounds: Dimension, -<<<<<<< HEAD - options -======= options: TextLayoutParameters ->>>>>>> main ): TextTransform { //Shrink space bounds so that corners hit the arcs let constraintRadius = 2; diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index c8ad67db6..2c1038dbb 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -27,11 +27,7 @@ export interface ShieldDefinitionBase { banners?: string[]; /** If true, no next should be drawn on this shield */ notext?: boolean; -<<<<<<< HEAD - /** Specifies the maximum font size of text on this shield */ -======= /** Maximum size of shield text */ ->>>>>>> main maxFontSize?: number; } From 8e0aba9c71473579c3364681ea1100a44304a124 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Tue, 3 Oct 2023 21:53:11 -0400 Subject: [PATCH 3/7] Consolidate shield banner code --- shieldlib/src/shield.js | 36 ++---- shieldlib/src/shield_banner.ts | 171 ++++++++++++++++++++++++++++ shieldlib/src/shield_canvas_draw.ts | 2 +- shieldlib/src/shield_text.ts | 114 +------------------ shieldlib/src/types.d.ts | 5 + shieldlib/src/types.ts | 5 + shieldlib/tsconfig.json | 2 +- 7 files changed, 195 insertions(+), 140 deletions(-) create mode 100644 shieldlib/src/shield_banner.ts diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index 6a17c72c8..dd15c53d0 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -1,20 +1,13 @@ "use strict"; import * as ShieldText from "./shield_text.js"; -import * as ShieldDraw from "./shield_canvas_draw"; +import * as ShieldDraw from "./shield_canvas_draw.js"; import * as Gfx from "./screen_gfx.js"; - -function drawBannerPart(r, ctx, shieldDef, drawFunc) { - if (shieldDef == null || typeof shieldDef.banners == "undefined") { - return ctx; //Unadorned shield - } - - for (var i = 0; i < shieldDef.banners.length; i++) { - drawFunc(r, ctx, shieldDef.banners[i], i); - } - - return ctx; -} +import { + drawBanners, + drawBannerHalos, + getBannerCount, +} from "./shield_banner.js"; function compoundShieldSize(r, dimension, bannerCount) { return { @@ -29,19 +22,6 @@ export function isValidRef(ref) { return ref !== null && ref.length !== 0 && ref.length <= 6; } -/** - * Get the number of banner placards associated with this shield - * - * @param {*} shield - Shield definition - * @returns the number of banner placards that need to be drawn - */ -function getBannerCount(shield) { - if (shield == null || typeof shield.banners == "undefined") { - return 0; //Unadorned shield - } - return shield.banners.length; -} - /** * Retrieve the shield blank that goes with a particular route. If there are * multiple shields for a route (different widths), it picks the best shield. @@ -359,7 +339,7 @@ export function generateShieldCtx(r, routeDef) { } // Add the halo around modifier plaque text - drawBannerPart(r, ctx, shieldDef, ShieldText.drawBannerHaloText); + drawBannerHalos(r, ctx, shieldDef); if (sourceSprite == null) { drawShield(r, ctx, shieldDef, routeDef); @@ -378,7 +358,7 @@ export function generateShieldCtx(r, routeDef) { drawShieldText(r, ctx, shieldDef, routeDef); // Add modifier plaque text - drawBannerPart(r, ctx, shieldDef, ShieldText.drawBannerText); + drawBanners(r, ctx, shieldDef); return ctx; } diff --git a/shieldlib/src/shield_banner.ts b/shieldlib/src/shield_banner.ts new file mode 100644 index 000000000..c7f76696e --- /dev/null +++ b/shieldlib/src/shield_banner.ts @@ -0,0 +1,171 @@ +import { shieldFont } from "./screen_gfx"; +import { ShieldRenderingContext } from "./shield_renderer"; +import { TextPlacement, layoutShieldText } from "./shield_text"; +import { Dimension, ShieldDefinition, TextLayout } from "./types"; + +let bannerLayout: TextLayout = { + constraintFunc: "rect", +}; + +/** + * Add modifier plaque text + * + * @param r - Shield rendering context + * @param ctx - Canvas drawing context + * @param shieldDef - Shield definition + */ +export function drawBanners( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + shieldDef: ShieldDefinition +) { + drawBannerPart(r, ctx, shieldDef, drawBannerText); +} + +/** + * Add the halo around modifier plaque text + * + * @param r - Shield rendering context + * @param ctx - Canvas drawing context + * @param shieldDef - Shield definition + */ +export function drawBannerHalos( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + shieldDef: ShieldDefinition +) { + drawBannerPart(r, ctx, shieldDef, drawBannerHaloText); +} + +type BannerDrawComponentFunction = ( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number +) => void; + +function drawBannerPart( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + shieldDef: ShieldDefinition, + drawFunc: BannerDrawComponentFunction +): void { + if (shieldDef == null || typeof shieldDef.banners == "undefined") { + return; //Unadorned shield + } + + for (var i = 0; i < shieldDef.banners.length; i++) { + drawFunc(r, ctx, shieldDef.banners[i], i); + } +} + +/** + * Get the number of banner placards associated with this shield + * + * @param shield - Shield definition + * @returns the number of banner placards that need to be drawn + */ +export function getBannerCount(shield: ShieldDefinition): number { + if (shield == null || typeof shield.banners == "undefined") { + return 0; //Unadorned shield + } + return shield.banners.length; +} + +/** + * Draw text on a modifier plate above a shield + * + * @param {*} r - rendering context + * @param {*} ctx - graphics context to draw to + * @param {*} text - text to draw + * @param {*} bannerIndex - plate position to draw, 0=top, incrementing + */ +export function drawBannerText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number +): void { + drawBannerTextComponent(r, ctx, text, bannerIndex, true); +} + +/** + * Draw drop shadow for text on a modifier plate above a shield + * + * @param {*} r - rendering context + * @param {*} ctx - graphics context to draw to + * @param {*} text - text to draw + * @param {*} bannerIndex - plate position to draw, 0=top, incrementing + */ +export function drawBannerHaloText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number +): void { + drawBannerTextComponent(r, ctx, text, bannerIndex, false); +} + +/** + * Banners are composed of two components: text on top, and a shadow beneath. + * + * @param {*} r - rendering context + * @param {*} ctx - graphics context to draw to + * @param {*} text - text to draw + * @param {*} bannerIndex - plate position to draw, 0=top, incrementing + * @param {*} textComponent - if true, draw the text. If false, draw the halo + */ +function drawBannerTextComponent( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + bannerIndex: number, + textComponent: boolean +): void { + const bannerPadding = { + top: r.options.bannerPadding, + bottom: 0, + left: 0, + right: 0, + }; + + let bannerBounds: Dimension = { + width: ctx.canvas.width, + height: r.px(r.options.bannerHeight - r.options.bannerPadding), + }; + + let textLayout: TextPlacement = layoutShieldText( + r, + text, + bannerPadding, + bannerBounds, + bannerLayout + ); + + ctx.font = shieldFont(textLayout.fontPx, r.options.shieldFont); + ctx.textBaseline = "top"; + ctx.textAlign = "center"; + + if (textComponent) { + ctx.fillStyle = r.options.bannerTextColor; + ctx.fillText( + text, + textLayout.xBaseline, + textLayout.yBaseline + + bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding) + ); + } else { + ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor; + ctx.shadowBlur = 0; + ctx.lineWidth = r.px(2); + ctx.strokeText( + text, + textLayout.xBaseline, + textLayout.yBaseline + + bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding) + ); + + ctx.shadowColor = null; + ctx.shadowBlur = null; + } +} diff --git a/shieldlib/src/shield_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 68bede46b..d79160fc5 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -4,7 +4,7 @@ * Shield blanks which are drawn rather built from raster shield blanks */ -import * as ShieldText from "./shield_text.mjs"; +import * as ShieldText from "./shield_text"; import { loadCustomShields } from "./custom_shields"; import { ShapeDrawFunction, ShieldRenderingContext } from "./shield_renderer"; import { ShapeBlankParams } from "./types"; diff --git a/shieldlib/src/shield_text.ts b/shieldlib/src/shield_text.ts index e9dc755ef..350f7f02c 100644 --- a/shieldlib/src/shield_text.ts +++ b/shieldlib/src/shield_text.ts @@ -4,6 +4,7 @@ import * as Gfx from "./screen_gfx.js"; import { ShieldRenderingContext } from "./shield_renderer.js"; import { BoxPadding, + Dimension, ShieldDefinition, TextLayout, TextLayoutParameters, @@ -18,11 +19,6 @@ const VerticalAlignment = { type VerticalAlignmentType = (typeof VerticalAlignment)[keyof typeof VerticalAlignment]; -interface Dimension { - width: number; - height: number; -} - type TextLayoutScaler = ( availSize: Dimension, textSize: Dimension, @@ -34,7 +30,7 @@ interface TextTransform { valign: VerticalAlignmentType; } -interface TextPlacement { +export interface TextPlacement { xBaseline: number; yBaseline: number; fontPx: number; @@ -47,10 +43,6 @@ let noPadding: BoxPadding = { right: 0, }; -let bannerLayout: TextLayout = { - constraintFunc: "rectangle", -}; - function ellipseScale(spaceBounds: Dimension, textBounds: Dimension): number { //Math derived from https://mathworld.wolfram.com/Ellipse-LineIntersection.html var a = spaceBounds.width; @@ -157,7 +149,7 @@ function triangleDownTextConstraint( * @param {*} maxFontSize - maximum font size * @returns JOSN object containing (X,Y) draw position and font size */ -function layoutShieldText( +export function layoutShieldText( r: ShieldRenderingContext, text: string, padding: BoxPadding, @@ -239,7 +231,7 @@ const defaultDefForLayout: ShieldDefinition = { right: 0, }, shapeBlank: { - drawFunc: "rectangle", + drawFunc: "rect", params: { fillColor: "white", strokeColor: "black", @@ -343,104 +335,6 @@ function configureShieldText( ctx.font = Gfx.shieldFont(textLayout.fontPx, r.options.shieldFont); } -/** - * Draw text on a modifier plate above a shield - * - * @param {*} r - rendering context - * @param {*} ctx - graphics context to draw to - * @param {*} text - text to draw - * @param {*} bannerIndex - plate position to draw, 0=top, incrementing - */ -export function drawBannerText( - r: ShieldRenderingContext, - ctx: CanvasRenderingContext2D, - text: string, - bannerIndex: number -): void { - drawBannerTextComponent(r, ctx, text, bannerIndex, true); -} - -/** - * Draw drop shadow for text on a modifier plate above a shield - * - * @param {*} r - rendering context - * @param {*} ctx - graphics context to draw to - * @param {*} text - text to draw - * @param {*} bannerIndex - plate position to draw, 0=top, incrementing - */ -export function drawBannerHaloText( - r: ShieldRenderingContext, - ctx: CanvasRenderingContext2D, - text: string, - bannerIndex: number -): void { - drawBannerTextComponent(r, ctx, text, bannerIndex, false); -} - -/** - * Banners are composed of two components: text on top, and a shadow beneath. - * - * @param {*} r - rendering context - * @param {*} ctx - graphics context to draw to - * @param {*} text - text to draw - * @param {*} bannerIndex - plate position to draw, 0=top, incrementing - * @param {*} textComponent - if true, draw the text. If false, draw the halo - */ -function drawBannerTextComponent( - r: ShieldRenderingContext, - ctx: CanvasRenderingContext2D, - text: string, - bannerIndex: number, - textComponent: boolean -): void { - const bannerPadding = { - top: r.options.bannerPadding, - bottom: 0, - left: 0, - right: 0, - }; - - let bannerBounds: Dimension = { - width: ctx.canvas.width, - height: r.px(r.options.bannerHeight - r.options.bannerPadding), - }; - - let textLayout: TextPlacement = layoutShieldText( - r, - text, - bannerPadding, - bannerBounds, - bannerLayout - ); - - ctx.font = Gfx.shieldFont(textLayout.fontPx, r.options.shieldFont); - ctx.textBaseline = "top"; - ctx.textAlign = "center"; - - if (textComponent) { - ctx.fillStyle = r.options.bannerTextColor; - ctx.fillText( - text, - textLayout.xBaseline, - textLayout.yBaseline + - bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding) - ); - } else { - ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor; - ctx.shadowBlur = 0; - ctx.lineWidth = r.px(2); - ctx.strokeText( - text, - textLayout.xBaseline, - textLayout.yBaseline + - bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding) - ); - - ctx.shadowColor = null; - ctx.shadowBlur = null; - } -} - export function calculateTextWidth( r: ShieldRenderingContext, text: string, diff --git a/shieldlib/src/types.d.ts b/shieldlib/src/types.d.ts index 363863df1..a81c725ad 100644 --- a/shieldlib/src/types.d.ts +++ b/shieldlib/src/types.d.ts @@ -56,3 +56,8 @@ export interface GraphicsFactory { */ pixelRatio(): number; } + +export interface Dimension { + width: number; + height: number; +} diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 2c1038dbb..20b4f2fed 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -218,3 +218,8 @@ export interface GraphicsFactory { */ pixelRatio(): number; } + +export interface Dimension { + width: number; + height: number; +} diff --git a/shieldlib/tsconfig.json b/shieldlib/tsconfig.json index db5212aa2..1204f7fe6 100644 --- a/shieldlib/tsconfig.json +++ b/shieldlib/tsconfig.json @@ -11,5 +11,5 @@ "experimentalSpecifierResolution": "node" }, "exclude": ["node_modules", "**/*.json"], - "include": ["src/**/*.ts", "scripts/**.ts"] + "include": ["src/**/*.ts", "scripts/**.ts", "src/shield.js", "src/shield.js"] } From 0f569ac7c937ad50fae61af5cf1a540f667287d8 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Tue, 3 Oct 2023 22:22:54 -0400 Subject: [PATCH 4/7] Add Vermont green banner --- shieldlib/src/shield_banner.ts | 12 ++++++++++-- shieldlib/src/shield_helper.d.ts | 3 ++- shieldlib/src/shield_helper.ts | 4 +++- shieldlib/src/types.ts | 4 ++++ src/js/shield_defs.js | 7 ++++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/shieldlib/src/shield_banner.ts b/shieldlib/src/shield_banner.ts index c7f76696e..7be6afd74 100644 --- a/shieldlib/src/shield_banner.ts +++ b/shieldlib/src/shield_banner.ts @@ -19,6 +19,11 @@ export function drawBanners( ctx: CanvasRenderingContext2D, shieldDef: ShieldDefinition ) { + if (shieldDef.bannerColor) { + ctx.fillStyle = shieldDef.bannerColor; + } else { + ctx.fillStyle = r.options.bannerTextColor; + } drawBannerPart(r, ctx, shieldDef, drawBannerText); } @@ -34,6 +39,11 @@ export function drawBannerHalos( ctx: CanvasRenderingContext2D, shieldDef: ShieldDefinition ) { + if (shieldDef.bannerHaloColor) { + ctx.strokeStyle = ctx.shadowColor = shieldDef.bannerHaloColor; + } else { + ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor; + } drawBannerPart(r, ctx, shieldDef, drawBannerHaloText); } @@ -147,7 +157,6 @@ function drawBannerTextComponent( ctx.textAlign = "center"; if (textComponent) { - ctx.fillStyle = r.options.bannerTextColor; ctx.fillText( text, textLayout.xBaseline, @@ -155,7 +164,6 @@ function drawBannerTextComponent( bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding) ); } else { - ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor; ctx.shadowBlur = 0; ctx.lineWidth = r.px(2); ctx.strokeText( diff --git a/shieldlib/src/shield_helper.d.ts b/shieldlib/src/shield_helper.d.ts index 85c942bee..177bf9a22 100644 --- a/shieldlib/src/shield_helper.d.ts +++ b/shieldlib/src/shield_helper.d.ts @@ -141,5 +141,6 @@ export declare function pillShield( export function banneredShield( baseDef: ShieldDefinition, - banners: string[] + banners: string[], + bannerColor?: string ): ShieldDefinition; diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 64b141e59..11b165f83 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -702,10 +702,12 @@ export function pillShield( */ export function banneredShield( baseDef: ShieldDefinition, - banners: string[] + banners: string[], + bannerColor?: string ): ShieldDefinition { return { banners, + bannerColor, ...baseDef, }; } diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 20b4f2fed..41b8363bd 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -19,6 +19,10 @@ export type Exclusive = export interface ShieldDefinitionBase { /** Color of text drawn on a shield */ textColor?: string; + /** Color of banner text */ + bannerColor?: string; + /** Color of banner text halo */ + bannerHaloColor?: string; /** Padding around shield text */ padding?: BoxPadding; /** Algorithm for expanding text to fill a shield background */ diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index 54b34f5e8..61b43b446 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -2428,7 +2428,12 @@ export function loadShields() { bottom: 2, }, }; - shields["US:VT:Alternate"] = banneredShield(shields["US:VT"], ["ALT"]); + shields["US:VT:Alternate"] = banneredShield( + shields["US:VT"], + ["ALT"], + Color.shields.green + ); + // Vermont routes town maintained sections - black and white ovals shields["US:VT:Town"] = ovalShield(Color.shields.white, Color.shields.black); From e012efa8715cde4fe46c00f2ec0fe122ddfadc1f Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 4 Oct 2023 18:29:09 -0400 Subject: [PATCH 5/7] Update docs and naming convention --- shieldlib/README.md | 4 ++++ shieldlib/src/shield_banner.ts | 8 ++++---- shieldlib/src/shield_helper.ts | 2 +- shieldlib/src/types.ts | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/shieldlib/README.md b/shieldlib/README.md index 1a85ff789..988dcd3db 100644 --- a/shieldlib/README.md +++ b/shieldlib/README.md @@ -136,6 +136,8 @@ You should create one definition entry for each network. The entry key must matc } }, "banners": ["ALT"], + "bannerTextColor": "#000", + "bannerTextHaloColor": "#FFF", "textLayout": { "constraintFunc": "roundedRect", "options": { @@ -173,6 +175,8 @@ You should create one definition entry for each network. The entry key must matc ![Bannered routes near Downington, PA](https://wiki.openstreetmap.org/w/images/f/f8/Downington_bannered_routes_Americana.png) +- **`bannerTextColor`**: specify the color of the banner text. +- **`bannerTextHaloColor`**: specify the color of the banner knockout halo. - **`textLayout`**: specify how text should be inscribed within the padded bounds of the shield. The text will be drawn at the maximum size allowed by this constraint. See the [text layout functions](#text-layout-functions) section for text layout options. - **`colorLighten`**: specify that the shield artwork should be lightened (multiplied) by the specified color. This means that black areas will be recolor with this color and white areas will remain the same. Alpha values will remain unmodified. - **`colorDarken`**: specify that the shield artwork should be darkened by the specified color. This means that white areas will be recolor with this color and black areas will remain the same. Alpha values will remain unmodified. diff --git a/shieldlib/src/shield_banner.ts b/shieldlib/src/shield_banner.ts index 7be6afd74..6961865b5 100644 --- a/shieldlib/src/shield_banner.ts +++ b/shieldlib/src/shield_banner.ts @@ -19,8 +19,8 @@ export function drawBanners( ctx: CanvasRenderingContext2D, shieldDef: ShieldDefinition ) { - if (shieldDef.bannerColor) { - ctx.fillStyle = shieldDef.bannerColor; + if (shieldDef.bannerTextColor) { + ctx.fillStyle = shieldDef.bannerTextColor; } else { ctx.fillStyle = r.options.bannerTextColor; } @@ -39,8 +39,8 @@ export function drawBannerHalos( ctx: CanvasRenderingContext2D, shieldDef: ShieldDefinition ) { - if (shieldDef.bannerHaloColor) { - ctx.strokeStyle = ctx.shadowColor = shieldDef.bannerHaloColor; + if (shieldDef.bannerTextHaloColor) { + ctx.strokeStyle = ctx.shadowColor = shieldDef.bannerTextHaloColor; } else { ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor; } diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 11b165f83..f70c8b0b4 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -707,7 +707,7 @@ export function banneredShield( ): ShieldDefinition { return { banners, - bannerColor, + bannerTextColor: bannerColor, ...baseDef, }; } diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 41b8363bd..63ae4ff1d 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -20,9 +20,9 @@ export interface ShieldDefinitionBase { /** Color of text drawn on a shield */ textColor?: string; /** Color of banner text */ - bannerColor?: string; + bannerTextColor?: string; /** Color of banner text halo */ - bannerHaloColor?: string; + bannerTextHaloColor?: string; /** Padding around shield text */ padding?: BoxPadding; /** Algorithm for expanding text to fill a shield background */ From 3a091683a335f84b396bf7ba345b09e98c5e4a76 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 4 Oct 2023 18:38:42 -0400 Subject: [PATCH 6/7] Add US:Historic banner color --- src/js/shield_defs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index 61b43b446..57e071453 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -509,7 +509,8 @@ export function loadShields() { textColor: Color.shields.brown, colorLighten: Color.shields.brown, }, - ["HIST"] + ["HIST"], + Color.shields.brown ); // Federal Agencies From 2733b53b731aa8cb0eb82bcfe6be5339c1f725e6 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 4 Oct 2023 22:28:12 -0400 Subject: [PATCH 7/7] Add route banner colors --- src/js/shield_defs.js | 99 +++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index 57e071453..c1085579c 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -554,7 +554,11 @@ export function loadShields() { notext: true, }; - shields["GLCT:Loop"] = banneredShield(shields["GLCT"], ["LOOP"]); + shields["GLCT:Loop"] = banneredShield( + shields["GLCT"], + ["LOOP"], + Color.shields.brown + ); // Alaska shields["US:AK"] = { @@ -766,7 +770,11 @@ export function loadShields() { bottom: 4, }, }; - shields["US:CA:Business"] = banneredShield(shields["US:CA"], ["BUS"]); + shields["US:CA:Business"] = banneredShield( + shields["US:CA"], + ["BUS"], + Color.shields.green + ); shields["US:CA:CR"] = pentagonUpShield( 3, 15, @@ -1171,7 +1179,8 @@ export function loadShields() { textColor: Color.shields.green, colorLighten: Color.shields.green, }, - ["BUS"] + ["BUS"], + Color.shields.green ); // Maine @@ -1571,9 +1580,11 @@ export function loadShields() { spriteBlank: "shield_us_nj_ace_noref", notext: true, }; - shields["US:NJ:ACE:Connector"] = banneredShield(shields["US:NJ:ACE"], [ - "CONN", - ]); + shields["US:NJ:ACE:Connector"] = banneredShield( + shields["US:NJ:ACE"], + ["CONN"], + Color.shields.blue + ); shields["US:NJ:GSP"] = { spriteBlank: "shield_us_nj_gsp_noref", notext: true, @@ -1618,8 +1629,16 @@ export function loadShields() { Color.shields.white, Color.shields.black ); - shields["US:NJ:CR:Spur"] = banneredShield(shields["US:NJ:CR"], ["SPUR"]); - shields["US:NJ:CR:Truck"] = banneredShield(shields["US:NJ:CR"], ["TRK"]); + shields["US:NJ:CR:Spur"] = banneredShield( + shields["US:NJ:CR"], + ["SPUR"], + Color.shields.blue + ); + shields["US:NJ:CR:Truck"] = banneredShield( + shields["US:NJ:CR"], + ["TRK"], + Color.shields.blue + ); // New Mexico shields["US:NM"] = pillShield( @@ -2127,9 +2146,21 @@ export function loadShields() { bottom: 3, }, }; - shields["US:SC:Truck"] = banneredShield(shields["US:SC"], ["TRK"]); - shields["US:SC:Business"] = banneredShield(shields["US:SC"], ["BUS"]); - shields["US:SC:Alternate"] = banneredShield(shields["US:SC"], ["ALT"]); + shields["US:SC:Truck"] = banneredShield( + shields["US:SC"], + ["TRK"], + Color.shields.blue + ); + shields["US:SC:Business"] = banneredShield( + shields["US:SC"], + ["BUS"], + Color.shields.blue + ); + shields["US:SC:Alternate"] = banneredShield( + shields["US:SC"], + ["ALT"], + Color.shields.blue + ); // South Dakota shields["US:SD"] = { @@ -2266,7 +2297,8 @@ export function loadShields() { textColor: Color.shields.brown, colorLighten: Color.shields.brown, }, - ["R"] + ["R"], + Color.shields.brown ); shields["US:TX:NASA"] = banneredShield(shields["US:TX"], ["NASA"]); @@ -2275,22 +2307,31 @@ export function loadShields() { Color.shields.blue, Color.shields.white ); - shields["US:TX:Express:Toll"] = banneredShield(shields["US:TX:Toll"], [ - "EXPR", - ]); - shields["US:TX:Loop:Toll"] = banneredShield(shields["US:TX:Toll"], ["LOOP"]); - shields["US:TX:Loop:Express:Toll"] = banneredShield(shields["US:TX:Toll"], [ - "EXPR", - "LOOP", - ]); + shields["US:TX:Express:Toll"] = banneredShield( + shields["US:TX:Toll"], + ["EXPR"], + Color.shields.blue + ); + shields["US:TX:Loop:Toll"] = banneredShield( + shields["US:TX:Toll"], + ["LOOP"], + Color.shields.blue + ); + shields["US:TX:Loop:Express:Toll"] = banneredShield( + shields["US:TX:Toll"], + ["EXPR", "LOOP"], + Color.shields.blue + ); shields["US:TX:CTRMA"] = roundedRectShield( Color.shields.blue, Color.shields.yellow, Color.shields.white ); - shields["US:TX:CTRMA:Express"] = banneredShield(shields["US:TX:CTRMA"], [ - "EXPR", - ]); + shields["US:TX:CTRMA:Express"] = banneredShield( + shields["US:TX:CTRMA"], + ["EXPR"], + Color.shields.blue + ); shields["US:TX:Montgomery:MCTRA"] = homePlateDownShield( 5, Color.shields.blue, @@ -2375,11 +2416,13 @@ export function loadShields() { ); shields["US:TX:Jackson"] = banneredShield( roundedRectShield(Color.shields.blue, Color.shields.white), - ["CR"] + ["CR"], + Color.shields.blue ); shields["US:TX:Andrews:Andrews:Loop"] = banneredShield( roundedRectShield(Color.shields.white, Color.shields.blue), - ["LOOP"] + ["LOOP"], + Color.shields.blue ); // Utah @@ -3507,7 +3550,8 @@ export function loadShields() { ); shields[`AU:${state_or_territory}:ALT`] = banneredShield( roundedRectShield(Color.shields.green, Color.shields.yellow), - ["ALT"] + ["ALT"], + Color.shields.green ); shields[`AU:${state_or_territory}:ALT_NR`] = banneredShield( homePlateDownShield(5, Color.shields.white, Color.shields.black), @@ -3515,7 +3559,8 @@ export function loadShields() { ); shields[`AU:${state_or_territory}:ALT_S`] = banneredShield( fishheadDownShield(Color.shields.blue, Color.shields.white), - ["ALT"] + ["ALT"], + Color.shields.blue ); } );