From d4441235fac2d964eb768b234398424bd17b5f86 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Sun, 12 May 2024 23:41:10 -0400 Subject: [PATCH] Convert shield.js to typescript --- shieldlib/src/{shield.js => shield.ts} | 76 +++++++++++++------------- shieldlib/src/shield_helper.ts | 36 ++++++------ shieldlib/src/shield_renderer.ts | 4 +- shieldlib/src/shield_text.ts | 4 +- shieldlib/src/types.d.ts | 4 +- shieldlib/src/types.ts | 23 ++++++-- 6 files changed, 82 insertions(+), 65 deletions(-) rename shieldlib/src/{shield.js => shield.ts} (72%) diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.ts similarity index 72% rename from shieldlib/src/shield.js rename to shieldlib/src/shield.ts index 8a8202cde..505fa57a4 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.ts @@ -4,8 +4,12 @@ import * as ShieldText from "./shield_text"; import * as ShieldDraw from "./shield_canvas_draw"; import * as Gfx from "./screen_gfx"; import { drawBanners, drawBannerHalos, getBannerCount } from "./shield_banner"; +import { ShieldRenderingContext } from "./shield_renderer"; +import { Dimension, RouteDefinition, ShieldDefinition, ShieldDefinitions } from "./types"; +import { TextPlacement } from "./shield_text"; +import { StyleImage } from "maplibre-gl"; -function compoundShieldSize(r, dimension, bannerCount) { +function compoundShieldSize(r: ShieldRenderingContext, dimension: Dimension, bannerCount: number): Dimension { return { width: dimension.width, height: @@ -14,7 +18,7 @@ function compoundShieldSize(r, dimension, bannerCount) { }; } -export function isValidRef(ref) { +export function isValidRef(ref: string): boolean { return ref !== null && ref.length !== 0 && ref.length <= 6; } @@ -27,24 +31,24 @@ export function isValidRef(ref) { * @param {*} routeDef - route tagging from OSM * @returns shield blank or null if no shield exists */ -function getRasterShieldBlank(r, shieldDef, routeDef) { - var shieldArtwork = null; - var textLayout; - var bannerCount = 0; - var bounds; +function getRasterShieldBlank(r: ShieldRenderingContext, shieldDef: ShieldDefinition, routeDef: RouteDefinition): StyleImage { + let shieldArtwork = null; + let textPlacement: TextPlacement; + let bannerCount: number = 0; + let bounds: Dimension; if (Array.isArray(shieldDef.spriteBlank)) { for (var i = 0; i < shieldDef.spriteBlank.length; i++) { shieldArtwork = r.spriteRepo.getSprite(shieldDef.spriteBlank[i]); bounds = compoundShieldSize(r, shieldArtwork.data, bannerCount); - textLayout = ShieldText.layoutShieldTextFromDef( + textPlacement = ShieldText.layoutShieldTextFromDef( r, routeDef.ref, shieldDef, bounds ); - if (textLayout.fontPx > r.px(Gfx.fontSizeThreshold)) { + if (textPlacement.fontPx > r.px(Gfx.fontSizeThreshold)) { break; } } @@ -55,16 +59,16 @@ function getRasterShieldBlank(r, shieldDef, routeDef) { return shieldArtwork; } -function textColor(shieldDef) { +function textColor(shieldDef: ShieldDefinition): string { if (shieldDef != null && typeof shieldDef.textColor != "undefined") { return shieldDef.textColor; } return "black"; } -function getDrawFunc(shieldDef) { +function getDrawFunc(shieldDef: ShieldDefinition): (r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, ref: string) => void { if (typeof shieldDef.shapeBlank != "undefined") { - return (r, ctx, ref) => + return (r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, ref: string) => ShieldDraw.draw( r, shieldDef.shapeBlank.drawFunc, @@ -73,17 +77,17 @@ function getDrawFunc(shieldDef) { ref ); } - return ShieldDraw.blank; + return () => ShieldDraw.blank; } -function getDrawHeight(r, shieldDef) { +function getDrawHeight(r: ShieldRenderingContext, shieldDef: ShieldDefinition): number { if (typeof shieldDef.shapeBlank != "undefined") { return ShieldDraw.shapeHeight(r, shieldDef.shapeBlank.drawFunc); } return r.shieldSize(); } -function drawShieldText(r, ctx, shieldDef, routeDef, shieldBounds) { +function drawShieldText(r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, shieldDef: ShieldDefinition, routeDef: RouteDefinition, shieldBounds: Dimension): CanvasRenderingContext2D { if (shieldDef.notext) { //If the shield definition says not to draw a ref, ignore ref return ctx; @@ -97,8 +101,8 @@ function drawShieldText(r, ctx, shieldDef, routeDef, shieldBounds) { shieldBounds ); - if (typeof r.options.SHIELD_TEXT_HALO_COLOR_OVERRIDE !== "undefined") { - ctx.strokeStyle = options.SHIELD_TEXT_HALO_COLOR_OVERRIDE; + if (typeof r.debugOptions?.shieldTextHaloColor !== "undefined") { + ctx.strokeStyle = r.debugOptions.shieldTextHaloColor; ShieldText.drawShieldHaloText(r, ctx, routeDef.ref, textLayout); } else if (shieldDef.textHaloColor) { ctx.strokeStyle = shieldDef.textHaloColor; @@ -108,23 +112,23 @@ function drawShieldText(r, ctx, shieldDef, routeDef, shieldBounds) { ctx.fillStyle = textColor(shieldDef); ShieldText.renderShieldText(r, ctx, routeDef.ref, textLayout); - if (r.options.SHIELD_TEXT_BBOX_COLOR) { - ctx.strokeStyle = r.options.SHIELD_TEXT_BBOX_COLOR; //TODO move to debugOptions + if (r.debugOptions?.shieldTextBboxColor) { + ctx.strokeStyle = r.debugOptions.shieldTextBboxColor; //TODO move to debugOptions ctx.lineWidth = r.px(1); ctx.strokeRect( r.px(shieldDef.padding.left - 0.5), r.px(shieldDef.padding.top - 0.5), shieldBounds.width - - r.px(shieldDef.padding.left + shieldDef.padding.right - 1), + r.px(shieldDef.padding.left + shieldDef.padding.right - 1), shieldBounds.height - - r.px(shieldDef.padding.top + shieldDef.padding.bottom - 1) + r.px(shieldDef.padding.top + shieldDef.padding.bottom - 1) ); } return ctx; } -export function missingIconLoader(r, routeDef, spriteID, update) { +export function missingIconLoader(r: ShieldRenderingContext, routeDef: RouteDefinition, spriteID: string, update: boolean): void { let ctx = generateShieldCtx(r, routeDef); if (ctx == null) { // Want to return null here, but that gives a corrupted display. See #243 @@ -134,7 +138,7 @@ export function missingIconLoader(r, routeDef, spriteID, update) { storeSprite(r, spriteID, ctx, update); } -function storeSprite(r, id, ctx, update) { +function storeSprite(r: ShieldRenderingContext, id: string, ctx: CanvasRenderingContext2D, update: boolean): void { const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); r.spriteRepo.putSprite( id, @@ -148,11 +152,11 @@ function storeSprite(r, id, ctx, update) { ); } -export function storeNoShield(r, id) { - storeSprite(r, id, r.emptySprite()); +export function storeNoShield(r: ShieldRenderingContext, id: string): void { + storeSprite(r, id, r.emptySprite(), false); } -function refForDefs(routeDef, shieldDef) { +function refForDefs(routeDef: RouteDefinition, shieldDef: ShieldDefinition) { // Handle special case for manually-applied abbreviations if ( shieldDef.refsByName && @@ -164,7 +168,7 @@ function refForDefs(routeDef, shieldDef) { return routeDef.ref; } -function getShieldDef(shields, routeDef) { +function getShieldDef(shields: ShieldDefinitions, routeDef: RouteDefinition): ShieldDefinition { if (!shields) { //This occurs if the ShieldJSON is loaded from the network and hasn't loaded yet. return null; @@ -179,7 +183,7 @@ function getShieldDef(shields, routeDef) { if (shieldDef == null) { // Default to plain black text with halo and no background shield console.debug("Generic shield for", JSON.stringify(routeDef)); - return isValidRef(routeDef.ref) ? shields.default : null; + return isValidRef(routeDef.ref) ? shields.shield["default"] : null; } var ref = refForDefs(routeDef, shieldDef); @@ -222,7 +226,7 @@ function getShieldDef(shields, routeDef) { * Reformats an alphanumeric ref as Roman numerals, preserving any alphabetic * suffix. */ -export function romanizeRef(ref) { +export function romanizeRef(ref: string): string { let number = parseInt(ref, 10); if (isNaN(number)) { return ref; @@ -246,7 +250,7 @@ export function romanizeRef(ref) { return roman + ref.slice(number.toString().length); } -function getDrawnShieldBounds(r, shieldDef, ref) { +function getDrawnShieldBounds(r: ShieldRenderingContext, shieldDef: ShieldDefinition, ref: string): Dimension { let width = Math.max( r.shieldSize(), ShieldDraw.computeWidth( @@ -261,19 +265,17 @@ function getDrawnShieldBounds(r, shieldDef, ref) { return { width, height }; } -function bannerAreaHeight(r, bannerCount) { +function bannerAreaHeight(r: ShieldRenderingContext, bannerCount: number): number { if (bannerCount === 0) { return 0; } - return ( - bannerCount * r.px(r.options.bannerHeight) + + return bannerCount * r.px(r.options.bannerHeight) + //No padding after last banner - (bannerCount - 1) * r.px(r.options.bannerPadding) - ); + (bannerCount - 1) * r.px(r.options.bannerPadding); } -export function generateShieldCtx(r, routeDef) { - let shieldDef = getShieldDef(r.shieldDef, routeDef); +export function generateShieldCtx(r: ShieldRenderingContext, routeDef: RouteDefinition): CanvasRenderingContext2D { + let shieldDef: ShieldDefinition = getShieldDef(r.shieldDef, routeDef); if (shieldDef == null) { return null; diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index da386c6e6..fc2d8cc0c 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -40,7 +40,7 @@ export function ovalShield( strokeColor: string, textColor: string, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; return { shapeBlank: { @@ -74,7 +74,7 @@ export function circleShield( fillColor: string, strokeColor: string, textColor: string -): ShieldDefinition { +): Partial { return ovalShield(fillColor, strokeColor, textColor, 20); } @@ -94,7 +94,7 @@ export function roundedRectShield( textColor: string, rectWidth: number, radius: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius = radius ?? 2; return { @@ -136,7 +136,7 @@ export function escutcheonDownShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius = radius ?? 0; return { @@ -176,7 +176,7 @@ export function fishheadDownShield( strokeColor: string, textColor: string, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; return { shapeBlank: { @@ -215,7 +215,7 @@ export function triangleDownShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius = radius ?? 2; @@ -259,7 +259,7 @@ export function trapezoidDownShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 0; @@ -304,7 +304,7 @@ export function trapezoidUpShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 0; @@ -347,7 +347,7 @@ export function diamondShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius = radius ?? 2; return { @@ -393,7 +393,7 @@ export function pentagonUpShield( radius1: number, radius2: number, rectWidth: number -): ShieldDefinition { +): Partial { let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius1 = radius1 ?? 2; @@ -444,7 +444,7 @@ export function homePlateDownShield( radius1: number, radius2: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius1 = radius1 ?? 2; radius2 = radius2 ?? 2; @@ -493,7 +493,7 @@ export function homePlateUpShield( radius1: number, radius2: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius1 = radius1 ?? 2; radius2 = radius2 ?? 2; @@ -540,7 +540,7 @@ export function hexagonVerticalShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; radius = radius ?? 2; return { @@ -583,7 +583,7 @@ export function hexagonHorizontalShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 2; @@ -629,7 +629,7 @@ export function octagonVerticalShield( textColor: string, radius: number, rectWidth: number -): ShieldDefinition { +): Partial { let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 2; @@ -670,7 +670,7 @@ export function pillShield( strokeColor: string, textColor: string, rectWidth: number -): ShieldDefinition { +): Partial { textColor = textColor ?? strokeColor; return { shapeBlank: { @@ -722,7 +722,7 @@ export function banneredShield( export function paBeltShield( fillColor: string, strokeColor: string -): ShieldDefinition { +): Partial { return { notext: true, shapeBlank: { @@ -745,7 +745,7 @@ export function paBeltShield( export function bransonRouteShield( fillColor: string, strokeColor: string -): ShieldDefinition { +): Partial { return { notext: true, shapeBlank: { diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 826555688..87f72cb55 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -27,7 +27,7 @@ import { DOMGraphicsFactory } from "./document_graphics"; export class ShieldRenderingContext { shieldDef: ShieldDefinitions; options: ShieldOptions; - debugOptions: DebugOptions; + debugOptions?: DebugOptions; gfxFactory: GraphicsFactory; spriteRepo: SpriteRepository; private _emptySpriteCache: CanvasRenderingContext2D; @@ -67,7 +67,7 @@ class MaplibreGLSpriteRepository implements SpriteRepository { putSprite( spriteID: string, image: ImageData, - options: StyleImageMetadata, + options: Partial, update: boolean ): void { if (update && this.map.listImages().includes(spriteID)) { diff --git a/shieldlib/src/shield_text.ts b/shieldlib/src/shield_text.ts index 5bc1071d0..8d19a92a9 100644 --- a/shieldlib/src/shield_text.ts +++ b/shieldlib/src/shield_text.ts @@ -250,7 +250,7 @@ export function layoutShieldText( }; } -const defaultDefForLayout: ShieldDefinition = { +const defaultDefForLayout: Partial = { padding: { top: 0, bottom: 0, @@ -279,7 +279,7 @@ const defaultDefForLayout: ShieldDefinition = { export function layoutShieldTextFromDef( r: ShieldRenderingContext, text: string, - def: ShieldDefinition, + def: Partial, bounds: Dimension ): TextPlacement { //FIX diff --git a/shieldlib/src/types.d.ts b/shieldlib/src/types.d.ts index cb78e4ad2..a91903395 100644 --- a/shieldlib/src/types.d.ts +++ b/shieldlib/src/types.d.ts @@ -1,4 +1,4 @@ -import { StyleImage } from "maplibre-gl"; +import { StyleImage, StyleImageMetadata } from "maplibre-gl"; export interface RouteDefinition { network: string; ref: string; @@ -26,7 +26,7 @@ export interface SpriteConsumer { putSprite( spriteID: string, image: ImageData, - pixelRatio: number, + options: Partial, update: boolean ): void; } diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 5ba35c4c9..ce85408ee 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -1,4 +1,4 @@ -import { StyleImage, StyleImageMetadata } from "maplibre-gl"; +import { StyleImage, StyleImageInterface, StyleImageMetadata } from "maplibre-gl"; /** Defines the set of routes that a shield applies to */ export interface RouteDefinition { @@ -19,6 +19,8 @@ export type Exclusive = export interface ShieldDefinitionBase { /** Color of text drawn on a shield */ textColor?: string; + /** Color of the halo drawn around text */ + textHaloColor?: string; /** Color of banner text */ bannerTextColor?: string; /** Color of banner text halo */ @@ -33,6 +35,16 @@ export interface ShieldDefinitionBase { notext?: boolean; /** Maximum size of shield text */ maxFontSize?: number; + /** ref values that can be mapped from names */ + refsByName?: Map; + /** Transpose numbering system, for example "roman" for Roman numerals */ + numberingSystem?: string; + /** Reflect this shield vertically */ + verticalReflect: boolean; + /** Perform a color lighten operation with this color */ + colorLighten: string; + /** Perform a color darken operation with this color */ + colorDarken: string; } /** @@ -137,8 +149,8 @@ export interface SpriteProducer { export interface SpriteConsumer { putSprite( spriteID: string, - image: ImageData, - options: StyleImageMetadata, + image: ImageData | StyleImageInterface, + options: Partial, update: boolean ): void; } @@ -153,10 +165,13 @@ export interface ShieldDefinitions { }; } -/** Additional debugging-only options */ +/** Additional debugging-only override options */ export interface DebugOptions { /** If set, draw a colored box around shield text constraint */ shieldTextBboxColor?: string; + + /** If set, draw a halo of this color around text */ + shieldTextHaloColor?: string; } /** Global options for shield rendering */