From b018421c414d176c402b79754e1a00b3cda6089d Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Thu, 24 Aug 2023 19:06:10 -0400 Subject: [PATCH 01/25] Move standard helper functions to shieldlib --- shieldlib/src/index.d.ts | 3 +- shieldlib/src/index.ts | 1 + shieldlib/src/shield_helper.d.ts | 156 +++++++ shieldlib/src/shield_helper.ts | 700 +++++++++++++++++++++++++++++++ shieldlib/src/types.ts | 54 ++- src/js/shield_defs.js | 691 +----------------------------- 6 files changed, 926 insertions(+), 679 deletions(-) create mode 100644 shieldlib/src/shield_helper.d.ts create mode 100644 shieldlib/src/shield_helper.ts diff --git a/shieldlib/src/index.d.ts b/shieldlib/src/index.d.ts index 1fe27f46d..0b7805a33 100644 --- a/shieldlib/src/index.d.ts +++ b/shieldlib/src/index.d.ts @@ -1,6 +1,6 @@ export { Bounds, - GfxFactory, + GraphicsFactory, RouteDefinition, ShieldSpecification, SpriteRepository, @@ -12,3 +12,4 @@ export { InMemorySpriteRepository, } from "./shield_renderer"; export { getDOMPixelRatio } from "./document_graphics"; +export * from "./shield_helper"; diff --git a/shieldlib/src/index.ts b/shieldlib/src/index.ts index 9cd52f669..14bcb9ec1 100644 --- a/shieldlib/src/index.ts +++ b/shieldlib/src/index.ts @@ -15,3 +15,4 @@ export { } from "./shield_renderer"; export { getDOMPixelRatio } from "./document_graphics"; +export * from "./shield_helper"; diff --git a/shieldlib/src/shield_helper.d.ts b/shieldlib/src/shield_helper.d.ts new file mode 100644 index 000000000..64bc9f72e --- /dev/null +++ b/shieldlib/src/shield_helper.d.ts @@ -0,0 +1,156 @@ +import { ShieldDefinition, TextLayout } from "./types"; + +export declare function roundedRectTextConstraint(radius: number): TextLayout; +export declare function textConstraint(fxn: string): TextLayout; +export declare function ovalShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition; + +export declare function circleShield( + fillColor: string, + strokeColor: string, + textColor: string +): ShieldDefinition; + +export declare function roundedRectShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number, + radius: number +): ShieldDefinition; + +export declare function escutcheonDownShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function fishheadDownShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition; + +export declare function triangleDownShield( + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function trapezoidDownShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +); + +export declare function trapezoidUpShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function diamondShield( + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function pentagonUpShield( + offset: number, + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition; + +export declare function homePlateDownShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition; + +export declare function homePlateUpShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition; + +export declare function hexagonVerticalShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +/** + * Draws a shield with a horizontally-aligned hexagon background + * + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of hexagon background fill + * @param {*} strokeColor - Color of hexagon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of hexagon (defaults to 2) + * @param {*} rectWidth - Width of hexagon (defaults to variable-width) + * @returns a shield definition object + */ +export declare function hexagonHorizontalShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function octagonVerticalShield( + offset: number, + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition; + +export declare function pillShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition; + +export function banneredShield( + baseDef: ShieldDefinition, + banners: string[] +): ShieldDefinition; diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts new file mode 100644 index 000000000..3a82b75ad --- /dev/null +++ b/shieldlib/src/shield_helper.ts @@ -0,0 +1,700 @@ +import { ShieldDefinition, TextLayout } from "./types"; + +export function roundedRectTextConstraint(radius: number): TextLayout { + return { + constraintFunc: "roundedRect", + options: { + radius: radius, + }, + }; +} + +export function textConstraint(fxn: string): TextLayout { + return { + constraintFunc: fxn, + }; +} + +/** + * Draws a shield with an ellipse background + * + * @param {*} fillColor - Color of ellipse background fill + * @param {*} strokeColor - Color of ellipse outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} rectWidth - Width of ellipse (defaults to variable-width) + * @returns a shield definition object + */ +export function ovalShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + return { + shapeBlank: { + drawFunc: "ellipse", + params: { + fillColor, + strokeColor, + rectWidth, + }, + }, + textLayout: textConstraint("ellipse"), + padding: { + left: 2, + right: 2, + top: 2, + bottom: 2, + }, + textColor, + }; +} + +/** + * Draws a shield with circle background (special case of ovalShield) + * + * @param {*} fillColor - Color of circle background fill + * @param {*} strokeColor - Color of circle outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @returns a shield definition object + */ +export function circleShield( + fillColor: string, + strokeColor: string, + textColor: string +): ShieldDefinition { + return ovalShield(fillColor, strokeColor, textColor, 20); +} + +/** + * Draws a shield with a rectangle background + * + * @param {*} fillColor - Color of rectangle background fill + * @param {*} strokeColor - Color of rectangle outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} rectWidth - Width of rectangle (defaults to variable-width) + * @param {*} radius - Corner radius of rectangle (defaults to 2) + * @returns a shield definition object + */ +export function roundedRectShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number, + radius: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + return { + shapeBlank: { + drawFunc: "roundedRectangle", + params: { + fillColor, + strokeColor, + rectWidth, + radius, + }, + }, + textLayout: roundedRectTextConstraint(radius), + padding: { + left: 3, + right: 3, + top: 3, + bottom: 3, + }, + textColor, + }; +} + +/** + * Draws a shield with an escutcheon background, pointed downward + * + * @param {*} offset - Height of curved portion + * @param {*} fillColor - Color of escutcheon background fill + * @param {*} strokeColor - Color of escutcheon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of escutcheon (defaults to 0) + * @param {*} rectWidth - Width of escutcheon (defaults to variable-width) + * @returns a shield definition object + */ +export function escutcheonDownShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius = radius ?? 0; + return { + shapeBlank: { + drawFunc: "escutcheon", + params: { + offset, + fillColor, + strokeColor, + rectWidth, + radius, + outlineWidth: 1, + }, + }, + textLayout: roundedRectTextConstraint(radius), + padding: { + left: 2, + right: 2, + top: 2, + bottom: 0 + offset / 2, + }, + textColor, + }; +} + +/** + * Draws a shield with a fishhead background, pointed downward + * + * @param {*} fillColor - Color of fishhead background fill + * @param {*} strokeColor - Color of fishhead outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} rectWidth - Width of fishhead (defaults to variable-width) + * @returns a shield definition object + */ +export function fishheadDownShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + return { + shapeBlank: { + drawFunc: "fishhead", + params: { + fillColor, + strokeColor, + rectWidth, + outlineWidth: 1, + }, + }, + textLayout: textConstraint("roundedRect"), + padding: { + left: 3, + right: 3, + top: 2, + bottom: 6, + }, + textColor, + }; +} + +/** + * Draws a shield with a triangle background, pointed downward + * + * @param {*} fillColor - Color of triangle background fill + * @param {*} strokeColor - Color of triangle outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of triangle (defaults to 2) + * @param {*} rectWidth - Width of triangle (defaults to variable-width) + * @returns a shield definition object + */ +export function triangleDownShield( + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + + return { + shapeBlank: { + drawFunc: "triangle", + params: { + pointUp: false, + fillColor, + strokeColor, + rectWidth, + radius, + }, + }, + textLayout: textConstraint("triangleDown"), + padding: { + left: 1, + right: 1, + top: 2, + bottom: 1, + }, + textColor, + }; +} + +/** + * Draws a shield with a trapezoid background, with the short side on bottom + * + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of trapezoid background fill + * @param {*} strokeColor - Color of trapezoid outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of trapezoid (defaults to 0) + * @param {*} rectWidth - Width of trapezoid (defaults to variable-width) + * @returns a shield definition object + */ +export function trapezoidDownShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + let angleInRadians = (angle * Math.PI) / 180; + textColor = textColor ?? strokeColor; + radius = radius ?? 0; + + return { + shapeBlank: { + drawFunc: "trapezoid", + params: { + angle: angleInRadians, + fillColor, + strokeColor, + rectWidth, + radius, + }, + }, + textLayout: roundedRectTextConstraint(radius), + padding: { + left: 2 + 10 * Math.tan(angleInRadians), + right: 2 + 10 * Math.tan(angleInRadians), + top: 2, + bottom: 4, + }, + textColor, + }; +} + +/** + * Draws a shield with a trapezoid background, with the short side on top + * + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of trapezoid background fill + * @param {*} strokeColor - Color of trapezoid outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of trapezoid (defaults to 0) + * @param {*} rectWidth - Width of trapezoid (defaults to variable-width) + * @returns a shield definition object + */ +export function trapezoidUpShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + let angleInRadians = (angle * Math.PI) / 180; + textColor = textColor ?? strokeColor; + radius = radius ?? 0; + return { + shapeBlank: { + drawFunc: "trapezoid", + params: { + shortSideUp: true, + angle: angleInRadians, + fillColor, + strokeColor, + rectWidth, + radius, + }, + }, + textLayout: roundedRectTextConstraint(radius), + padding: { + left: 2 + 10 * Math.tan(angleInRadians), + right: 2 + 10 * Math.tan(angleInRadians), + top: 4, + bottom: 2, + }, + textColor, + }; +} + +/** + * Draws a shield with a diamond background + * + * @param {*} fillColor - Color of diamond background fill + * @param {*} strokeColor - Color of diamond outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of diamond (defaults to 2) + * @param {*} rectWidth - Width of diamond (defaults to variable-width) + * @returns a shield definition object + */ +export function diamondShield( + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + return { + shapeBlank: { + drawFunc: "diamond", + params: { + fillColor, + strokeColor, + radius, + rectWidth, + }, + }, + textLayout: textConstraint("diamond"), + padding: { + left: 1, + right: 1, + top: 1, + bottom: 1, + }, + textColor, + }; +} + +/** + * Draws a shield with a pentagon background, pointed upward + * + * @param {*} offset - Height of diagonal edges + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of pentagon background fill + * @param {*} strokeColor - Color of pentagon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius1 - Corner radius of pointed side of pentagon (defaults to 2) + * @param {*} radius2 - Corner radius of flat side of pentagon (defaults to 0) + * @param {*} rectWidth - Width of pentagon (defaults to variable-width) + * @returns a shield definition object + */ +export function pentagonUpShield( + offset: number, + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition { + let angleInRadians = (angle * Math.PI) / 180; + textColor = textColor ?? strokeColor; + radius1 = radius1 ?? 2; + radius2 = radius2 ?? 0; + return { + shapeBlank: { + drawFunc: "pentagon", + params: { + offset: offset, + angle: angleInRadians, + fillColor: fillColor, + strokeColor: strokeColor, + radius1, + radius2, + rectWidth, + }, + }, + textLayout: { + constraintFunc: "rect", + }, + padding: { + left: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, + right: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, + top: 1 + offset / 2, + bottom: 3, + }, + textColor, + }; +} + +/** + * Draws a shield with a home plate background, pointed downward + * + * @param {*} offset - Height of diagonal edges + * @param {*} fillColor - Color of home plate background fill + * @param {*} strokeColor - Color of home plate outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius1 - Corner radius of pointed side of home plate (defaults to 2) + * @param {*} radius2 - Corner radius of flat side of home plate (defaults to 2) + * @param {*} rectWidth - Width of home plate (defaults to variable-width) + * @returns a shield definition object + */ +export function homePlateDownShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius1 = radius1 ?? 2; + radius2 = radius2 ?? 2; + return { + shapeBlank: { + drawFunc: "pentagon", + params: { + pointUp: false, + offset, + angle: 0, + fillColor, + strokeColor, + radius1, + radius2, + rectWidth, + }, + }, + textLayout: roundedRectTextConstraint(radius2), + padding: { + left: 2, + right: 2, + top: 2, + bottom: 1 + offset, + }, + textColor, + }; +} + +/** + * Draws a shield with a home plate background, pointed upward + * + * @param {*} offset - Height of diagonal edges + * @param {*} fillColor - Color of home plate background fill + * @param {*} strokeColor - Color of home plate outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius1 - Corner radius of pointed side of home plate (defaults to 2) + * @param {*} radius2 - Corner radius of flat side of home plate (defaults to 2) + * @param {*} rectWidth - Width of home plate (defaults to variable-width) + * @returns a shield definition object + */ +export function homePlateUpShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius1: number, + radius2: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius1 = radius1 ?? 2; + radius2 = radius2 ?? 2; + return { + shapeBlank: { + drawFunc: "pentagon", + params: { + pointUp: true, + offset: offset, + angle: 0, + fillColor, + strokeColor, + radius1, + radius2, + rectWidth, + }, + }, + textLayout: roundedRectTextConstraint(radius2), + padding: { + left: 2, + right: 2, + top: 1 + offset, + bottom: 2, + }, + textColor, + }; +} + +/** + * Draws a shield with a vertically-aligned hexagon background + * + * @param {*} offset - Height of diagonal edges + * @param {*} fillColor - Color of hexagon background fill + * @param {*} strokeColor - Color of hexagon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of hexagon (defaults to 2) + * @param {*} rectWidth - Width of hexagon (defaults to variable-width) + * @returns a shield definition object + */ +export function hexagonVerticalShield( + offset: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + return { + shapeBlank: { + drawFunc: "hexagonVertical", + params: { + offset, + fillColor, + strokeColor, + radius, + rectWidth, + }, + }, + textLayout: roundedRectTextConstraint(radius), + padding: { + left: 2, + right: 2, + top: 1 + offset, + bottom: 1 + offset, + }, + textColor, + }; +} + +/** + * Draws a shield with a horizontally-aligned hexagon background + * + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of hexagon background fill + * @param {*} strokeColor - Color of hexagon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of hexagon (defaults to 2) + * @param {*} rectWidth - Width of hexagon (defaults to variable-width) + * @returns a shield definition object + */ +export function hexagonHorizontalShield( + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + let angleInRadians = (angle * Math.PI) / 180; + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + return { + shapeBlank: { + drawFunc: "hexagonHorizontal", + params: { + angle: angleInRadians, + fillColor: fillColor, + strokeColor: strokeColor, + radius: radius, + rectWidth: rectWidth, + }, + }, + textLayout: textConstraint("ellipse"), + padding: { + left: 3, + right: 3, + top: 2, + bottom: 2, + }, + textColor, + }; +} + +/** + * Draws a shield with an octagon background + * + * @param {*} offset - Height of diagonal edges + * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} fillColor - Color of octagon background fill + * @param {*} strokeColor - Color of octagon outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} radius - Corner radius of octagon (defaults to 2) + * @param {*} rectWidth - Width of octagon (defaults to variable-width) + * @returns a shield definition object + */ +export function octagonVerticalShield( + offset: number, + angle: number, + fillColor: string, + strokeColor: string, + textColor: string, + radius: number, + rectWidth: number +): ShieldDefinition { + let angleInRadians = (angle * Math.PI) / 180; + textColor = textColor ?? strokeColor; + radius = radius ?? 2; + return { + shapeBlank: { + drawFunc: "octagonVertical", + params: { + offset, + angle: angleInRadians, + fillColor, + strokeColor, + radius, + rectWidth, + }, + }, + textLayout: textConstraint("ellipse"), + padding: { + left: 2, + right: 2, + top: 2, + bottom: 2, + }, + textColor, + }; +} + +/** + * Draws a shield with a pill-shaped background + * + * @param {*} fillColor - Color of pill background fill + * @param {*} strokeColor - Color of pill outline stroke + * @param {*} textColor - Color of text (defaults to strokeColor) + * @param {*} rectWidth - Width of pill (defaults to variable-width) + * @returns a shield definition object + */ +export function pillShield( + fillColor: string, + strokeColor: string, + textColor: string, + rectWidth: number +): ShieldDefinition { + textColor = textColor ?? strokeColor; + return { + shapeBlank: { + drawFunc: "roundedRectangle", + params: { + fillColor, + strokeColor, + rectWidth, + radius: 10, + }, + }, + textLayout: textConstraint("ellipse"), + padding: { + left: 2, + right: 2, + top: 2, + bottom: 2, + }, + textColor, + }; +} + +/** + * Adds banner text above a shield + * + * @param {*} baseDef - Shield definition object + * @param {*} banners - Array of strings to be displayed above shield + * @returns a shield definition object + */ +export function banneredShield( + baseDef: ShieldDefinition, + banners: string[] +): ShieldDefinition { + return { + banners, + ...baseDef, + }; +} diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 170eef2bc..9933ca9b5 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -7,14 +7,54 @@ export interface RouteDefinition { spriteID?: string; } -export interface ShieldDefinition { - spriteBlank: string[]; +//Enforce a requirement that one field OR another field must be specified, but not both. +type Exclusive = + | (T & { [P in keyof U]?: never }) + | (U & { [P in keyof T]?: never }); + +interface ShieldDefinitionBase { textColor: string; - padding: { - left: number; - right: number; - top: number; - bottom: number; + padding: BoxPadding; + textLayout: TextLayout; + banners?: string[]; +} + +export type ShieldDefinition = Exclusive< + { spriteBlank: string[] }, + { shapeBlank: ShapeDefinition } +> & + ShieldDefinitionBase; + +export interface ShapeDefinition { + drawFunc: string; + params: ShapeBlankParams; +} + +export interface BoxPadding { + left: number; + right: number; + top: number; + bottom: number; +} + +export interface ShapeBlankParams { + fillColor: string; + strokeColor: string; + rectWidth: number; + radius?: number; + radius1?: number; + radius2?: number; + offset?: number; + outlineWidth?: number; + pointUp?: boolean; + shortSideUp?: boolean; + angle?: number; +} + +export interface TextLayout { + constraintFunc: string; + options?: { + radius: number; }; } diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index fbebff4cf..7195a3bf1 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -1,663 +1,26 @@ "use strict"; import * as Color from "../constants/color.js"; - -/** - * Draws a shield with an ellipse background - * - * @param {*} fillColor - Color of ellipse background fill - * @param {*} strokeColor - Color of ellipse outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} rectWidth - Width of ellipse (defaults to variable-width) - * @returns a shield definition object - */ -function ovalShield(fillColor, strokeColor, textColor, rectWidth) { - textColor = textColor ?? strokeColor; - return { - shapeBlank: { - drawFunc: "ellipse", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - }, - }, - textLayout: textConstraint("ellipse"), - padding: { - left: 2, - right: 2, - top: 2, - bottom: 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with circle background (special case of ovalShield) - * - * @param {*} fillColor - Color of circle background fill - * @param {*} strokeColor - Color of circle outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @returns a shield definition object - */ -function circleShield(fillColor, strokeColor, textColor) { - return ovalShield(fillColor, strokeColor, textColor, 20); -} - -function roundedRectTextConstraint(radius) { - return { - constraintFunc: "roundedRect", - options: { - radius: radius, - }, - }; -} - -function textConstraint(fxn) { - return { - constraintFunc: fxn, - }; -} - -/** - * Draws a shield with a rectangle background - * - * @param {*} fillColor - Color of rectangle background fill - * @param {*} strokeColor - Color of rectangle outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} rectWidth - Width of rectangle (defaults to variable-width) - * @param {*} radius - Corner radius of rectangle (defaults to 2) - * @returns a shield definition object - */ -function roundedRectShield( - fillColor, - strokeColor, - textColor, - rectWidth, - radius -) { - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - return { - shapeBlank: { - drawFunc: "roundedRectangle", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: radius, - }, - }, - textLayout: roundedRectTextConstraint(radius), - padding: { - left: 3, - right: 3, - top: 3, - bottom: 3, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with an escutcheon background, pointed downward - * - * @param {*} offset - Height of curved portion - * @param {*} fillColor - Color of escutcheon background fill - * @param {*} strokeColor - Color of escutcheon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of escutcheon (defaults to 0) - * @param {*} rectWidth - Width of escutcheon (defaults to variable-width) - * @returns a shield definition object - */ -function escutcheonDownShield( - offset, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - textColor = textColor ?? strokeColor; - radius = radius ?? 0; - return { - shapeBlank: { - drawFunc: "escutcheon", - params: { - offset: offset, - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: radius, - outlineWidth: 1, - }, - }, - textLayout: roundedRectTextConstraint(radius), - padding: { - left: 2, - right: 2, - top: 2, - bottom: 0 + offset / 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a fishhead background, pointed downward - * - * @param {*} fillColor - Color of fishhead background fill - * @param {*} strokeColor - Color of fishhead outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} rectWidth - Width of fishhead (defaults to variable-width) - * @returns a shield definition object - */ -function fishheadDownShield(fillColor, strokeColor, textColor, rectWidth) { - textColor = textColor ?? strokeColor; - return { - shapeBlank: { - drawFunc: "fishhead", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - outlineWidth: 1, - }, - }, - textLayout: textConstraint("roundedRect"), - padding: { - left: 3, - right: 3, - top: 2, - bottom: 6, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a triangle background, pointed downward - * - * @param {*} fillColor - Color of triangle background fill - * @param {*} strokeColor - Color of triangle outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of triangle (defaults to 2) - * @param {*} rectWidth - Width of triangle (defaults to variable-width) - * @returns a shield definition object - */ -function triangleDownShield( - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - - return { - shapeBlank: { - drawFunc: "triangle", - params: { - pointUp: false, - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: radius, - }, - }, - textLayout: textConstraint("triangleDown"), - padding: { - left: 1, - right: 1, - top: 2, - bottom: 1, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a trapezoid background, with the short side on bottom - * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of trapezoid background fill - * @param {*} strokeColor - Color of trapezoid outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of trapezoid (defaults to 0) - * @param {*} rectWidth - Width of trapezoid (defaults to variable-width) - * @returns a shield definition object - */ -function trapezoidDownShield( - angle, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - let angleInRadians = (angle * Math.PI) / 180; - textColor = textColor ?? strokeColor; - radius = radius ?? 0; - - return { - shapeBlank: { - drawFunc: "trapezoid", - params: { - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: radius, - }, - }, - textLayout: roundedRectTextConstraint(radius), - padding: { - left: 2 + 10 * Math.tan(angleInRadians), - right: 2 + 10 * Math.tan(angleInRadians), - top: 2, - bottom: 4, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a trapezoid background, with the short side on top - * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of trapezoid background fill - * @param {*} strokeColor - Color of trapezoid outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of trapezoid (defaults to 0) - * @param {*} rectWidth - Width of trapezoid (defaults to variable-width) - * @returns a shield definition object - */ -function trapezoidUpShield( - angle, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - let angleInRadians = (angle * Math.PI) / 180; - textColor = textColor ?? strokeColor; - radius = radius ?? 0; - return { - shapeBlank: { - drawFunc: "trapezoid", - params: { - shortSideUp: true, - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: radius, - }, - }, - textLayout: roundedRectTextConstraint(radius), - padding: { - left: 2 + 10 * Math.tan(angleInRadians), - right: 2 + 10 * Math.tan(angleInRadians), - top: 4, - bottom: 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a diamond background - * - * @param {*} fillColor - Color of diamond background fill - * @param {*} strokeColor - Color of diamond outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of diamond (defaults to 2) - * @param {*} rectWidth - Width of diamond (defaults to variable-width) - * @returns a shield definition object - */ -function diamondShield(fillColor, strokeColor, textColor, radius, rectWidth) { - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - return { - shapeBlank: { - drawFunc: "diamond", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - radius: radius, - rectWidth: rectWidth, - }, - }, - textLayout: textConstraint("diamond"), - padding: { - left: 1, - right: 1, - top: 1, - bottom: 1, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a pentagon background, pointed upward - * - * @param {*} offset - Height of diagonal edges - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of pentagon background fill - * @param {*} strokeColor - Color of pentagon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius1 - Corner radius of pointed side of pentagon (defaults to 2) - * @param {*} radius2 - Corner radius of flat side of pentagon (defaults to 0) - * @param {*} rectWidth - Width of pentagon (defaults to variable-width) - * @returns a shield definition object - */ -function pentagonUpShield( - offset, - angle, - fillColor, - strokeColor, - textColor, - radius1, - radius2, - rectWidth -) { - let angleInRadians = (angle * Math.PI) / 180; - textColor = textColor ?? strokeColor; - radius1 = radius1 ?? 2; - radius2 = radius2 ?? 0; - return { - shapeBlank: { - drawFunc: "pentagon", - params: { - offset: offset, - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - radius1: radius1, - radius2: radius2, - rectWidth: rectWidth, - }, - }, - textLayout: { - constraintFunc: "rect", - }, - padding: { - left: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, - right: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, - top: 1 + offset / 2, - bottom: 3, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a home plate background, pointed downward - * - * @param {*} offset - Height of diagonal edges - * @param {*} fillColor - Color of home plate background fill - * @param {*} strokeColor - Color of home plate outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius1 - Corner radius of pointed side of home plate (defaults to 2) - * @param {*} radius2 - Corner radius of flat side of home plate (defaults to 2) - * @param {*} rectWidth - Width of home plate (defaults to variable-width) - * @returns a shield definition object - */ -function homePlateDownShield( - offset, - fillColor, - strokeColor, - textColor, - radius1, - radius2, - rectWidth -) { - textColor = textColor ?? strokeColor; - radius1 = radius1 ?? 2; - radius2 = radius2 ?? 2; - return { - shapeBlank: { - drawFunc: "pentagon", - params: { - pointUp: false, - offset: offset, - angle: 0, - fillColor: fillColor, - strokeColor: strokeColor, - radius1: radius1, - radius2: radius2, - rectWidth: rectWidth, - }, - }, - textLayout: roundedRectTextConstraint(radius2), - padding: { - left: 2, - right: 2, - top: 2, - bottom: 1 + offset, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a home plate background, pointed upward - * - * @param {*} offset - Height of diagonal edges - * @param {*} fillColor - Color of home plate background fill - * @param {*} strokeColor - Color of home plate outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius1 - Corner radius of pointed side of home plate (defaults to 2) - * @param {*} radius2 - Corner radius of flat side of home plate (defaults to 2) - * @param {*} rectWidth - Width of home plate (defaults to variable-width) - * @returns a shield definition object - */ -function homePlateUpShield( - offset, - fillColor, - strokeColor, - textColor, - radius1, - radius2, - rectWidth -) { - textColor = textColor ?? strokeColor; - radius1 = radius1 ?? 2; - radius2 = radius2 ?? 2; - return { - shapeBlank: { - drawFunc: "pentagon", - params: { - pointUp: true, - offset: offset, - angle: 0, - fillColor: fillColor, - strokeColor: strokeColor, - radius1: radius1, - radius2: radius2, - rectWidth: rectWidth, - }, - }, - textLayout: roundedRectTextConstraint(radius2), - padding: { - left: 2, - right: 2, - top: 1 + offset, - bottom: 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a vertically-aligned hexagon background - * - * @param {*} offset - Height of diagonal edges - * @param {*} fillColor - Color of hexagon background fill - * @param {*} strokeColor - Color of hexagon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of hexagon (defaults to 2) - * @param {*} rectWidth - Width of hexagon (defaults to variable-width) - * @returns a shield definition object - */ -function hexagonVerticalShield( - offset, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - return { - shapeBlank: { - drawFunc: "hexagonVertical", - params: { - offset: offset, - fillColor: fillColor, - strokeColor: strokeColor, - radius: radius, - rectWidth: rectWidth, - }, - }, - textLayout: roundedRectTextConstraint(radius), - padding: { - left: 2, - right: 2, - top: 1 + offset, - bottom: 1 + offset, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a horizontally-aligned hexagon background - * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of hexagon background fill - * @param {*} strokeColor - Color of hexagon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of hexagon (defaults to 2) - * @param {*} rectWidth - Width of hexagon (defaults to variable-width) - * @returns a shield definition object - */ -function hexagonHorizontalShield( - angle, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - let angleInRadians = (angle * Math.PI) / 180; - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - return { - shapeBlank: { - drawFunc: "hexagonHorizontal", - params: { - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - radius: radius, - rectWidth: rectWidth, - }, - }, - textLayout: textConstraint("ellipse"), - padding: { - left: 3, - right: 3, - top: 2, - bottom: 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with an octagon background - * - * @param {*} offset - Height of diagonal edges - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of octagon background fill - * @param {*} strokeColor - Color of octagon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of octagon (defaults to 2) - * @param {*} rectWidth - Width of octagon (defaults to variable-width) - * @returns a shield definition object - */ -function octagonVerticalShield( - offset, - angle, - fillColor, - strokeColor, - textColor, - radius, - rectWidth -) { - let angleInRadians = (angle * Math.PI) / 180; - textColor = textColor ?? strokeColor; - radius = radius ?? 2; - return { - shapeBlank: { - drawFunc: "octagonVertical", - params: { - offset: offset, - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - radius: radius, - rectWidth: rectWidth, - }, - }, - textLayout: textConstraint("ellipse"), - padding: { - left: 2, - right: 2, - top: 2, - bottom: 2, - }, - textColor: textColor, - }; -} - -/** - * Draws a shield with a pill-shaped background - * - * @param {*} fillColor - Color of pill background fill - * @param {*} strokeColor - Color of pill outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} rectWidth - Width of pill (defaults to variable-width) - * @returns a shield definition object - */ -function pillShield(fillColor, strokeColor, textColor, rectWidth) { - textColor = textColor ?? strokeColor; - return { - shapeBlank: { - drawFunc: "roundedRectangle", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - rectWidth: rectWidth, - radius: 10, - }, - }, - textLayout: textConstraint("ellipse"), - padding: { - left: 2, - right: 2, - top: 2, - bottom: 2, - }, - textColor: textColor, - }; -} +import { + textConstraint, + homePlateDownShield, + octagonVerticalShield, + hexagonHorizontalShield, + hexagonVerticalShield, + homePlateUpShield, + ovalShield, + circleShield, + roundedRectShield, + pillShield, + trapezoidUpShield, + trapezoidDownShield, + pentagonUpShield, + diamondShield, + triangleDownShield, + escutcheonDownShield, + fishheadDownShield, + banneredShield, +} from "@americana/maplibre-shield-generator"; /** * Draws a circle icon inside a black-outlined white square shield @@ -699,20 +62,6 @@ function bransonRouteShield(fillColor, strokeColor) { }; } -/** - * Adds banner text above a shield - * - * @param {*} baseDef - Shield definition object - * @param {*} banners - Array of strings to be displayed above shield - * @returns a shield definition object - */ -function banneredShield(baseDef, banners) { - return { - banners: banners, - ...baseDef, - }; -} - export function loadShields() { const shields = {}; From b16bc0a26fa68a45bc5597e4d3168e48aa3efc85 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Thu, 24 Aug 2023 19:14:28 -0400 Subject: [PATCH 02/25] Migrate special case helpers --- shieldlib/src/shield_helper.ts | 47 ++++++++++++++++++++++++++++++++++ shieldlib/src/types.ts | 9 ++++--- src/js/shield_defs.js | 42 ++---------------------------- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 3a82b75ad..d27b33754 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -1,3 +1,4 @@ +import { Color } from "color-namer"; import { ShieldDefinition, TextLayout } from "./types"; export function roundedRectTextConstraint(radius: number): TextLayout { @@ -698,3 +699,49 @@ export function banneredShield( ...baseDef, }; } + +/** + * Draws a circle icon inside a black-outlined white square shield + * + * @param {*} fillColor - Color of circle icon background fill + * @param {*} strokeColor - Color of circle icon outline + * @returns a shield definition object + */ +export function paBeltShield( + fillColor: string, + strokeColor: string +): ShieldDefinition { + return { + notext: true, + shapeBlank: { + drawFunc: "paBelt", + params: { + fillColor, + strokeColor, + }, + }, + }; +} + +/** + * Draws a rectangle icon inside a white-outlined green square shield + * + * @param {*} fillColor - Color of rectangle icon background fill + * @param {*} strokeColor - Color of rectangle icon outline + * @returns a shield definition object + */ +export function bransonRouteShield( + fillColor: string, + strokeColor: string +): ShieldDefinition { + return { + notext: true, + shapeBlank: { + drawFunc: "branson", + params: { + fillColor, + strokeColor, + }, + }, + }; +} diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 9933ca9b5..946d4a4f3 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -13,10 +13,11 @@ type Exclusive = | (U & { [P in keyof T]?: never }); interface ShieldDefinitionBase { - textColor: string; - padding: BoxPadding; - textLayout: TextLayout; + textColor?: string; + padding?: BoxPadding; + textLayout?: TextLayout; banners?: string[]; + notext?: boolean; } export type ShieldDefinition = Exclusive< @@ -40,7 +41,7 @@ export interface BoxPadding { export interface ShapeBlankParams { fillColor: string; strokeColor: string; - rectWidth: number; + rectWidth?: number; radius?: number; radius1?: number; radius2?: number; diff --git a/src/js/shield_defs.js b/src/js/shield_defs.js index 7195a3bf1..57dd47212 100644 --- a/src/js/shield_defs.js +++ b/src/js/shield_defs.js @@ -20,48 +20,10 @@ import { escutcheonDownShield, fishheadDownShield, banneredShield, + paBeltShield, + bransonRouteShield, } from "@americana/maplibre-shield-generator"; -/** - * Draws a circle icon inside a black-outlined white square shield - * - * @param {*} fillColor - Color of circle icon background fill - * @param {*} strokeColor - Color of circle icon outline - * @returns a shield definition object - */ -function paBeltShield(fillColor, strokeColor) { - return { - notext: true, - shapeBlank: { - drawFunc: "paBelt", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - }, - }, - }; -} - -/** - * Draws a rectangle icon inside a white-outlined green square shield - * - * @param {*} fillColor - Color of rectangle icon background fill - * @param {*} strokeColor - Color of rectangle icon outline - * @returns a shield definition object - */ -function bransonRouteShield(fillColor, strokeColor) { - return { - notext: true, - shapeBlank: { - drawFunc: "branson", - params: { - fillColor: fillColor, - strokeColor: strokeColor, - }, - }, - }; -} - export function loadShields() { const shields = {}; From 6aa8c2d68821e86cd116a9fd688133a62515dc75 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Thu, 24 Aug 2023 19:44:25 -0400 Subject: [PATCH 03/25] Add documentation --- .gitignore | 1 + package-lock.json | 91 ++++++++++++++++++++++++++++++++++++++++++ shieldlib/package.json | 2 + 3 files changed, 94 insertions(+) diff --git a/.gitignore b/.gitignore index 8448264f5..3c0c0b15a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ config.js dist download local.config.js +shieldlib/docs diff --git a/package-lock.json b/package-lock.json index 793f57612..1a692fc82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -730,6 +730,12 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2675,6 +2681,12 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", @@ -2764,6 +2776,12 @@ "node": "14 || >=16.14" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -2831,6 +2849,18 @@ "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -4349,6 +4379,18 @@ "node": "*" } }, + "node_modules/shiki": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", + "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/shortid": { "version": "2.2.16", "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", @@ -5036,6 +5078,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedoc": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -5101,6 +5179,18 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -5305,6 +5395,7 @@ "shx": "^0.3.4", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", + "typedoc": "^0.24.8", "typescript": "^4.9.5" } } diff --git a/shieldlib/package.json b/shieldlib/package.json index e23b2ee09..5c60f21a9 100644 --- a/shieldlib/package.json +++ b/shieldlib/package.json @@ -25,12 +25,14 @@ "shx": "^0.3.4", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", + "typedoc": "^0.24.8", "typescript": "^4.9.5" }, "scripts": { "code_format": "run-s code_format:prettier", "code_format:prettier": "prettier --write --list-different .", "clean": "shx rm -rf dist", + "docs": "npx typedoc src/index.ts", "test": "npm exec -- ts-mocha", "build:code": "node scripts/build.js", "build": "run-s clean build:code", From 05f5f7c5fca87ad287ce4906ca6759089e1da454 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Thu, 24 Aug 2023 20:01:08 -0400 Subject: [PATCH 04/25] Fix publish --- shieldlib/README.md | 4 ++++ shieldlib/package.json | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/shieldlib/README.md b/shieldlib/README.md index bd41b6c26..c14eaa47d 100644 --- a/shieldlib/README.md +++ b/shieldlib/README.md @@ -313,3 +313,7 @@ The following `params` options can be specified: ### Custom shield graphics In addition to the stock drawing functions, a custom draw function can be specified. `paDot` and `branson` are included as examples of this, for rendering the [Allegheny County belt system](https://en.wikipedia.org/wiki/Allegheny_County_belt_system) and the Branson, Missouri colored route system. See the file `src/custom_shields.mjs` for an example of how this is done. + +## Documentation + +See [TypeDoc generated documentation](./docs/index.html) for detailed API information. diff --git a/shieldlib/package.json b/shieldlib/package.json index 5c60f21a9..34c40ab84 100644 --- a/shieldlib/package.json +++ b/shieldlib/package.json @@ -53,5 +53,10 @@ }, "directories": { "test": "test" - } + }, + "files": [ + "dist/", + "docs/", + "README.md" + ] } From 50ca7c527b281d6884f72805efb8493b063bf204 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 07:57:54 +0200 Subject: [PATCH 05/25] Remove built docs on clean --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 192a64e38..e12dcf631 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "clean": "run-s clean:shieldlib clean:code clean-download clean-build", "clean-download": "shx rm -rf download", "clean-build": "shx rm -rf dist build", - "clean:shieldlib": "cd shieldlib && shx rm -rf dist", + "clean:shieldlib": "cd shieldlib && shx rm -rf dist docs", "clean:code": "shx rm -rf dist", "config": "shx cp src/configs/config.maptiler.js src/config.js", "code_format": "run-s code_format:prettier code_format:svgo", From e0406b26d3a30d4b59184dc07b9e4915c0c418a0 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:00:39 +0200 Subject: [PATCH 06/25] Remove built docs on clean --- shieldlib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shieldlib/package.json b/shieldlib/package.json index 34c40ab84..817af0ed6 100644 --- a/shieldlib/package.json +++ b/shieldlib/package.json @@ -31,7 +31,7 @@ "scripts": { "code_format": "run-s code_format:prettier", "code_format:prettier": "prettier --write --list-different .", - "clean": "shx rm -rf dist", + "clean": "shx rm -rf dist docs", "docs": "npx typedoc src/index.ts", "test": "npm exec -- ts-mocha", "build:code": "node scripts/build.js", From d8c0f320a2cffff5258aa5c28f45ccf227b32079 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:17:09 +0200 Subject: [PATCH 07/25] Deploy docs --- .github/workflows/deploy.yml | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 13b362704..5c1885188 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,12 +25,15 @@ jobs: with: node-version: 18.16.1 #18.17.0 is buggy - name: Install and Build 🔧 + # TODO: when we move shieldlib to its own repo, move shieldlib docs CI also run: | npm ci --include=dev cp src/configs/config.aws.js src/config.js npm run build npm run style npm run shields + mkdir -p dist/shield-docs + cp -r shieldlib/docs/* dist/shield-docs - name: Upload 🏗 uses: actions/upload-pages-artifact@v1 with: diff --git a/package.json b/package.json index e12dcf631..dfa558db8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "shieldlib" ], "scripts": { - "build:shieldlib": "cd shieldlib && node scripts/build.js", + "build:shieldlib": "cd shieldlib && node scripts/build.js && npm run docs", "build:code": "exec ts-node scripts/build", "build": "run-s clean-build sprites build:shieldlib build:code taginfo status_map", "clean": "run-s clean:shieldlib clean:code clean-download clean-build", From 782f383217f442d24937fd74bb934761a8e84347 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:20:06 +0200 Subject: [PATCH 08/25] Bump version, fix path, add docs to build --- shieldlib/README.md | 2 +- shieldlib/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shieldlib/README.md b/shieldlib/README.md index c14eaa47d..c1f4e6379 100644 --- a/shieldlib/README.md +++ b/shieldlib/README.md @@ -316,4 +316,4 @@ In addition to the stock drawing functions, a custom draw function can be specif ## Documentation -See [TypeDoc generated documentation](./docs/index.html) for detailed API information. +See [TypeDoc generated documentation](https://zelonewolf.github.io/openstreetmap-americana/shield-docs/index.html) for detailed API information. diff --git a/shieldlib/package.json b/shieldlib/package.json index 817af0ed6..d1b76554f 100644 --- a/shieldlib/package.json +++ b/shieldlib/package.json @@ -1,7 +1,7 @@ { "name": "@americana/maplibre-shield-generator", "description": "Generate highway shields for maplibre-gl-js maps", - "version": "0.0.3", + "version": "0.0.4", "author": "OpenStreetMap Americana Contributors", "type": "module", "keywords": [ @@ -35,7 +35,7 @@ "docs": "npx typedoc src/index.ts", "test": "npm exec -- ts-mocha", "build:code": "node scripts/build.js", - "build": "run-s clean build:code", + "build": "run-s clean build:code docs", "preversion": "npm version --no-git-tag-version --preid alpha", "publish-alpha": "npm publish --access=public --tag alpha" }, From db9b7c1acea96e9950291d6b3b7f81c36dc2dbad Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:25:24 +0200 Subject: [PATCH 09/25] Remove unused import --- shieldlib/src/shield_helper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index d27b33754..4d4c0b87c 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -1,4 +1,3 @@ -import { Color } from "color-namer"; import { ShieldDefinition, TextLayout } from "./types"; export function roundedRectTextConstraint(radius: number): TextLayout { From 56ffbb117173e022d797337e85ccbb7082fce472 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:27:35 +0200 Subject: [PATCH 10/25] Add canvas dependency to shield library --- package-lock.json | 3 ++- shieldlib/package.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 1a692fc82..dbab6f75c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5381,7 +5381,7 @@ }, "shieldlib": { "name": "@americana/maplibre-shield-generator", - "version": "0.0.3", + "version": "0.0.4", "license": "CC0-1.0", "dependencies": { "color-rgba": "^2.4.0", @@ -5389,6 +5389,7 @@ }, "devDependencies": { "@types/color-rgba": "^2.1.0", + "canvas": "^2.11.2", "esbuild": "^0.17.10", "npm-run-all": "^4.1.5", "prettier": "^2.8.4", diff --git a/shieldlib/package.json b/shieldlib/package.json index d1b76554f..112a26433 100644 --- a/shieldlib/package.json +++ b/shieldlib/package.json @@ -19,6 +19,7 @@ "source": "src/index.ts", "devDependencies": { "@types/color-rgba": "^2.1.0", + "canvas": "^2.11.2", "esbuild": "^0.17.10", "npm-run-all": "^4.1.5", "prettier": "^2.8.4", From a76f747483d3b2b9a9d73942695edc162a3656d5 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 08:35:54 +0200 Subject: [PATCH 11/25] Add docs to preview --- .github/workflows/build-preview.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-preview.yml b/.github/workflows/build-preview.yml index ae0bb9f69..e3426441d 100644 --- a/.github/workflows/build-preview.yml +++ b/.github/workflows/build-preview.yml @@ -36,12 +36,15 @@ jobs: name: pr_number path: pr/ - name: Install and Build 🔧 + # TODO: when we move shieldlib to its own repo, move shieldlib docs CI also run: | npm ci --include=dev npm run build npm run style npm run shields cp src/configs/config.aws.js src/config.js + mkdir -p dist/shield-docs + cp -r shieldlib/docs/* dist/shield-docs - name: Upload Build artifact uses: actions/upload-artifact@v3 with: From fbbfa1203bae911b1622f7c6b9cb006e73bdefb9 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 17:55:58 +0200 Subject: [PATCH 12/25] Documentation fixes --- shieldlib/src/index.d.ts | 9 +--- shieldlib/src/index.ts | 8 +-- shieldlib/src/shield.d.ts | 3 +- shieldlib/src/shield_helper.ts | 2 +- shieldlib/src/shield_renderer.d.ts | 2 +- shieldlib/src/shield_renderer.ts | 6 +-- shieldlib/src/types.d.ts | 1 - shieldlib/src/types.ts | 80 +++++++++++++++++++++++++----- 8 files changed, 77 insertions(+), 34 deletions(-) diff --git a/shieldlib/src/index.d.ts b/shieldlib/src/index.d.ts index 0b7805a33..46116e4fa 100644 --- a/shieldlib/src/index.d.ts +++ b/shieldlib/src/index.d.ts @@ -1,15 +1,10 @@ -export { - Bounds, - GraphicsFactory, - RouteDefinition, - ShieldSpecification, - SpriteRepository, -} from "./types"; +export * from "./types"; export { transposeImageData } from "./screen_gfx"; export { URLShieldRenderer, ShieldRenderer, InMemorySpriteRepository, + AbstractShieldRenderer, } from "./shield_renderer"; export { getDOMPixelRatio } from "./document_graphics"; export * from "./shield_helper"; diff --git a/shieldlib/src/index.ts b/shieldlib/src/index.ts index 14bcb9ec1..f6218eef4 100644 --- a/shieldlib/src/index.ts +++ b/shieldlib/src/index.ts @@ -1,10 +1,4 @@ -export { - Bounds, - GraphicsFactory, - RouteDefinition, - ShieldSpecification, - SpriteRepository, -} from "./types"; +export * from "./types"; export { transposeImageData } from "./screen_gfx"; diff --git a/shieldlib/src/shield.d.ts b/shieldlib/src/shield.d.ts index 77589c812..fb44bf80b 100644 --- a/shieldlib/src/shield.d.ts +++ b/shieldlib/src/shield.d.ts @@ -14,7 +14,8 @@ export function storeNoShield( export function missingIconLoader( renderContext: ShieldRenderingContext, - routeDef: RouteDefinition + routeDef: RouteDefinition, + spriteID: string ): void; export function romanizeRef(ref: string): string; diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 4d4c0b87c..8d1967dfe 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -723,7 +723,7 @@ export function paBeltShield( } /** - * Draws a rectangle icon inside a white-outlined green square shield + * Draws a Branson, Missouri route shield * * @param {*} fillColor - Color of rectangle icon background fill * @param {*} strokeColor - Color of rectangle icon outline diff --git a/shieldlib/src/shield_renderer.d.ts b/shieldlib/src/shield_renderer.d.ts index 7b3fd26a9..a4b0ddbf6 100644 --- a/shieldlib/src/shield_renderer.d.ts +++ b/shieldlib/src/shield_renderer.d.ts @@ -21,7 +21,7 @@ export declare class ShieldRenderingContext { px(pixels: number): number; shieldSize(): number; } -declare class AbstractShieldRenderer { +export declare class AbstractShieldRenderer { private _shieldPredicate; private _networkPredicate; private _routeParser; diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 1f421dc3a..09bc488eb 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -56,7 +56,7 @@ class MaplibreGLSpriteRepository implements SpriteRepository { } } -class AbstractShieldRenderer { +export class AbstractShieldRenderer { private _shieldPredicate: StringPredicate = () => true; private _networkPredicate: StringPredicate = () => true; private _routeParser: RouteParser; @@ -140,10 +140,9 @@ class AbstractShieldRenderer { storeNoShield(this._renderContext, e.id); return; } - routeDef.spriteID = e.id; //Original ID so we can store the sprite this._renderContext.debugOptions = this.debugOptions; if (routeDef) { - missingIconLoader(this._renderContext, routeDef); + missingIconLoader(this._renderContext, routeDef, e.id); } } catch (err) { console.error(`Exception while loading image ‘${e?.id}’:\n`, err); @@ -156,7 +155,6 @@ class AbstractShieldRenderer { network, ref, name, - spriteID: this._routeParser.format(network, ref, name), }); } diff --git a/shieldlib/src/types.d.ts b/shieldlib/src/types.d.ts index eb2a19e7d..363863df1 100644 --- a/shieldlib/src/types.d.ts +++ b/shieldlib/src/types.d.ts @@ -3,7 +3,6 @@ export interface RouteDefinition { network: string; ref: string; name: string; - spriteID?: string; } export interface ShieldDefinition { spriteBlank: string[]; diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 946d4a4f3..508f07a25 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -1,18 +1,36 @@ import { StyleImage } from "maplibre-gl"; +/** + * Defines the set of routes that a shield applies to + */ export interface RouteDefinition { + /** + * Only match routes with this network value + */ network: string; - ref: string; - name: string; - spriteID?: string; + + /** + * If set, only match routes with this ref value + */ + ref?: string; + + /** + * If set, only match routes with this name value + */ + name?: string; } -//Enforce a requirement that one field OR another field must be specified, but not both. -type Exclusive = +/** + * Enforce a requirement that one field OR another field must be specified, but not both. + */ +export type Exclusive = | (T & { [P in keyof U]?: never }) | (U & { [P in keyof T]?: never }); -interface ShieldDefinitionBase { +/** + * Parameters that apply to all types of shield definitions + */ +export interface ShieldDefinitionBase { textColor?: string; padding?: BoxPadding; textLayout?: TextLayout; @@ -20,17 +38,26 @@ interface ShieldDefinitionBase { notext?: boolean; } +/** + * Define how the renderer should draw the shield for a particular route + */ export type ShieldDefinition = Exclusive< { spriteBlank: string[] }, { shapeBlank: ShapeDefinition } > & ShieldDefinitionBase; +/** + * Define a shield which is created by drawing a shape, optionally with text on top + */ export interface ShapeDefinition { drawFunc: string; params: ShapeBlankParams; } +/** + * Rectangular padding values + */ export interface BoxPadding { left: number; right: number; @@ -38,6 +65,9 @@ export interface BoxPadding { bottom: number; } +/** + * Parameters for drawing shield shapes + */ export interface ShapeBlankParams { fillColor: string; strokeColor: string; @@ -52,6 +82,9 @@ export interface ShapeBlankParams { angle?: number; } +/** + * Parameters for laying out text on a shield background + */ export interface TextLayout { constraintFunc: string; options?: { @@ -59,27 +92,43 @@ export interface TextLayout { }; } +/** + * A predicate which determines whether to draw a shield for a particular sprite ID. + * This allows the library to consume a subset of sprite IDs passed in from maplibre + * while allowing other code to handle other sprite IDs. + */ export type StringPredicate = (spriteID: string) => boolean; -// RouteParser unpacks a route definition from a sprite image string. +/** + * RouteParser unpacks a route definition from a sprite image string. + */ export interface RouteParser { parse(spriteID: string): RouteDefinition; format(network: string, ref: string, name: string): string; } -// SpriteProducer returns a sprite graphic based on an ID. +/** + * Retrieve a sprite graphic based on an ID. + */ export interface SpriteProducer { getSprite(spriteID: string): StyleImage; } -// SpriteConsumer stores a sprite graphic based on an ID. +/** + * Store a sprite graphic based on an ID. + */ export interface SpriteConsumer { putSprite(spriteID: string, image: ImageData, pixelRatio: number): void; } +/** + * Respository that can store and retrieve sprite graphics. + */ export type SpriteRepository = SpriteProducer & SpriteConsumer; -// ShieldDefinitions maps routes to visual appearances +/** + * A map of shield definitions that associates a network name to its rendering + */ export interface ShieldDefinitions { shield: { [key: string]: ShieldDefinition; @@ -88,6 +137,9 @@ export interface ShieldDefinitions { export interface DebugOptions {} +/** + * Global options for shield rendering + */ export interface ShieldOptions { bannerHeight: number; bannerPadding: number; @@ -97,18 +149,22 @@ export interface ShieldOptions { shieldSize: number; } +/** + * A user-supplied specification for rendering shields + */ export interface ShieldSpecification { networks: ShieldDefinitions; options: ShieldOptions; } +/** + * Rectangular bounds, in scaled pixels + */ export type Bounds = { width: number; height: number; }; -// export type GfxFactory = (bounds: Bounds) => CanvasRenderingContext2D; - export interface GraphicsFactory { createGraphics(bounds: Bounds): CanvasRenderingContext2D; /** From be48fd0fe21e3d96adc13d45f2bb51dbff09308e Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 4 Sep 2023 22:36:07 +0200 Subject: [PATCH 13/25] Fix failing test case --- shieldlib/src/shield.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index d0ccaa296..7255caf2d 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -186,14 +186,14 @@ function drawShieldText(r, ctx, shieldDef, routeDef) { return ctx; } -export function missingIconLoader(r, routeDef) { +export function missingIconLoader(r, routeDef, spriteID) { let ctx = generateShieldCtx(r, routeDef); if (ctx == null) { // Want to return null here, but that gives a corrupted display. See #243 console.warn("Didn't produce a shield for", JSON.stringify(routeDef)); ctx = r.gfxFactory.createGraphics({ width: 1, height: 1 }); } - storeSprite(r, routeDef.spriteID, ctx); + storeSprite(r, spriteID, ctx); } function storeSprite(r, id, ctx) { From b552a92b582f7772aea40a71b60bcdacd166d5f9 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Sat, 9 Sep 2023 07:12:39 -0400 Subject: [PATCH 14/25] Document shield_renderer.ts --- shieldlib/src/index.ts | 1 + shieldlib/src/shield_renderer.ts | 69 +++++++++++++++++++- shieldlib/src/types.ts | 108 +++++++++++++++---------------- 3 files changed, 118 insertions(+), 60 deletions(-) diff --git a/shieldlib/src/index.ts b/shieldlib/src/index.ts index f6218eef4..97b3a5991 100644 --- a/shieldlib/src/index.ts +++ b/shieldlib/src/index.ts @@ -6,6 +6,7 @@ export { URLShieldRenderer, ShieldRenderer, InMemorySpriteRepository, + AbstractShieldRenderer, } from "./shield_renderer"; export { getDOMPixelRatio } from "./document_graphics"; diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 09bc488eb..33846b9fe 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -56,19 +56,23 @@ class MaplibreGLSpriteRepository implements SpriteRepository { } } +/** Base class for shield renderers. Shield renderers use a builder pattern to configure its options. */ export class AbstractShieldRenderer { private _shieldPredicate: StringPredicate = () => true; private _networkPredicate: StringPredicate = () => true; private _routeParser: RouteParser; + /** @hidden */ private _renderContext: ShieldRenderingContext; private _shieldDefCallbacks = []; + /** Create a shield renderer */ constructor(routeParser: RouteParser) { this._routeParser = routeParser; this._renderContext = new ShieldRenderingContext(); this._renderContext.gfxFactory = new DOMGraphicsFactory(); } + /** Specify which shields to draw and with what graphics */ protected setShields(shieldSpec: ShieldSpecification) { this._renderContext.options = shieldSpec.options; this._renderContext.shieldDef = shieldSpec.networks; @@ -77,15 +81,18 @@ export class AbstractShieldRenderer { ); } + /** Get the shield definitions */ public getShieldDefinitions(): ShieldDefinitions { return this._renderContext.shieldDef; } + /** Set debugging options */ public debugOptions(debugOptions: DebugOptions): AbstractShieldRenderer { this._renderContext.debugOptions = debugOptions; return this; } + /** Set which unhandled sprite IDs this renderer handles */ public filterImageID( shieldPredicate: StringPredicate ): AbstractShieldRenderer { @@ -93,6 +100,7 @@ export class AbstractShieldRenderer { return this; } + /** Set which network values this renderer handles */ public filterNetwork( networkPredicate: StringPredicate ): AbstractShieldRenderer { @@ -100,17 +108,20 @@ export class AbstractShieldRenderer { return this; } + /** Set which graphics context to draw shields to */ public graphicsFactory(gfxFactory: GraphicsFactory): AbstractShieldRenderer { this._renderContext.gfxFactory = gfxFactory; return this; } + /** Set which MaplibreGL map to handle shields for */ public renderOnMaplibreGL(map: Map): AbstractShieldRenderer { this.renderOnRepository(new MaplibreGLSpriteRepository(map)); map.on("styleimagemissing", this.getStyleImageMissingHandler()); return this; } + /** Set a callback that fires when shield definitions are loaded */ public onShieldDefLoad( callback: (shields: ShieldDefinitions) => void ): AbstractShieldRenderer { @@ -122,6 +133,7 @@ export class AbstractShieldRenderer { return this; } + /** Set the storage location for existing and generated sprite images */ public renderOnRepository(repo: SpriteRepository): AbstractShieldRenderer { if (!this._renderContext.spriteRepo) { this._renderContext.spriteRepo = repo; @@ -129,6 +141,11 @@ export class AbstractShieldRenderer { return this; } + /** + * Get the handler function for styleimagemissing event calls + * + * See [MapStyleImageMissingEvent](https://maplibre.org/maplibre-gl-js/docs/API/types/maplibregl.MapStyleImageMissingEvent/) for more details. + **/ public getStyleImageMissingHandler() { return (e: MapStyleImageMissingEvent) => { try { @@ -140,7 +157,6 @@ export class AbstractShieldRenderer { storeNoShield(this._renderContext, e.id); return; } - this._renderContext.debugOptions = this.debugOptions; if (routeDef) { missingIconLoader(this._renderContext, routeDef, e.id); } @@ -150,6 +166,7 @@ export class AbstractShieldRenderer { }; } + /** Get the graphic for a specified route */ public getGraphicForRoute(network: string, ref: string, name: string) { return generateShieldCtx(this._renderContext, { network, @@ -158,19 +175,47 @@ export class AbstractShieldRenderer { }); } + /** Get a blank route shield sprite in the default size */ public emptySprite(): CanvasRenderingContext2D { return this._renderContext.emptySprite(); } + /** Get a blank route shield graphics context in a specified size */ public createGraphics(bounds: Bounds) { return this._renderContext.gfxFactory.createGraphics(bounds); } + /** Get the current pixel ration (1x/2x) */ public pixelRatio(): number { return this._renderContext.px(1); } } +/** + * A shield renderer configured from a JSON specification + * + * @example + * + * const shields = { + * "US:I": { + * textColor: Color.shields.white, + * spriteBlank: ["shield_us_interstate_2", "shield_us_interstate_3"], + * textLayout: textConstraint("southHalfEllipse"), + * padding: { + * left: 4, + * right: 4, + * top: 6, + * bottom: 5, + * }, + * } + * }; + * + * const shieldRenderer = new ShieldRenderer(shields, routeParser) + * .filterImageID(shieldPredicate) + * .filterNetwork(networkPredicate) + * .renderOnMaplibreGL(map) + * .onShieldDefLoad((shields) => afterShieldRendererLoads(shields)); //Post config + */ export class ShieldRenderer extends AbstractShieldRenderer { constructor(shieldSpec: ShieldSpecification, routeParser: RouteParser) { super(routeParser); @@ -178,13 +223,30 @@ export class ShieldRenderer extends AbstractShieldRenderer { } } +/** + * A shield renderer configured from a URL containing a JSON specification + * + * @example + * + * const shieldRenderer = new URLShieldRenderer("shields.json", routeParser) + * .filterImageID(shieldPredicate) + * .filterNetwork(networkPredicate) + * .renderOnMaplibreGL(map) + * .onShieldDefLoad((shields) => afterShieldRendererLoads(shields)); //Post config + **/ export class URLShieldRenderer extends AbstractShieldRenderer { - constructor(shieldsURL: URL, routeParser: RouteParser) { + constructor( + /** URL containing the JSON shield definition */ + shieldsURL: URL, + /** Function that extracts route identification from a sprite string */ + routeParser: RouteParser + ) { super(routeParser); this.setShieldURL(shieldsURL); } - public async setShieldURL(shieldsURL: URL) { + /** Set the URL containing the shield specification */ + private async setShieldURL(shieldsURL: URL) { await fetch(shieldsURL) .then((res) => res.json()) .then((json) => super.setShields(json)) @@ -192,6 +254,7 @@ export class URLShieldRenderer extends AbstractShieldRenderer { } } +/** @hidden Used for testing */ export class InMemorySpriteRepository implements SpriteRepository { sprites = {}; public getSprite(spriteID: string): StyleImage { diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 508f07a25..19314de5c 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -1,90 +1,88 @@ import { StyleImage } from "maplibre-gl"; -/** - * Defines the set of routes that a shield applies to - */ +/** Defines the set of routes that a shield applies to */ export interface RouteDefinition { - /** - * Only match routes with this network value - */ + /** Only match routes with this network value */ network: string; - - /** - * If set, only match routes with this ref value - */ + /** If set, only match routes with this ref value */ ref?: string; - - /** - * If set, only match routes with this name value - */ + /** If set, only match routes with this name value */ name?: string; } -/** - * Enforce a requirement that one field OR another field must be specified, but not both. - */ +/** Enforce a requirement that one field OR another field must be specified, but not both */ export type Exclusive = | (T & { [P in keyof U]?: never }) | (U & { [P in keyof T]?: never }); -/** - * Parameters that apply to all types of shield definitions - */ +/** Parameters that apply to all types of shield definitions */ export interface ShieldDefinitionBase { + /** Color of text drawn on a shield */ textColor?: string; + /** Padding around shield text */ padding?: BoxPadding; + /** Algorithm for expanding text to fill a shield background */ textLayout?: TextLayout; + /** Banners to be drawn above a shield */ banners?: string[]; + /** If true, no next should be drawn on this shield */ notext?: boolean; } -/** - * Define how the renderer should draw the shield for a particular route - */ +/** Define how the renderer should draw the shield for a particular route */ export type ShieldDefinition = Exclusive< { spriteBlank: string[] }, { shapeBlank: ShapeDefinition } > & ShieldDefinitionBase; -/** - * Define a shield which is created by drawing a shape, optionally with text on top - */ +/** Define a shield which is created by drawing a shape, optionally with text on top */ export interface ShapeDefinition { + /** Which shape to draw */ drawFunc: string; + /** Parameters for drawing the shape */ params: ShapeBlankParams; } -/** - * Rectangular padding values - */ +/** Rectangular padding values */ export interface BoxPadding { + /** Minimum padding to the left of the text */ left: number; + /** Minimum padding to the right of the text */ right: number; + /** Minimum padding above the text */ top: number; + /** Minimum padding below the text */ bottom: number; } -/** - * Parameters for drawing shield shapes - */ +/** Parameters for drawing shield shapes */ export interface ShapeBlankParams { + /** Fill color of the shape */ fillColor: string; + /** Stroke (border) color */ strokeColor: string; + /** Width of the shape */ rectWidth?: number; + /** Radius of the shape's corners */ radius?: number; + /** Radius of the shapes's first corner. This is used for shapes that can specify multiple raidus values */ radius1?: number; + /** Radius of the shapes's second corner. This is used for shapes that can specify multiple raidus values */ radius2?: number; + /** Height of diagonal edges */ offset?: number; + /** Width of the shape's outline */ outlineWidth?: number; + /** Specify whether the pointy end of the shape is on top */ pointUp?: boolean; + /** Specify whether the short side of the shape is on top */ shortSideUp?: boolean; + /** Specify the angle of the defining vertex of the shape */ angle?: number; } -/** - * Parameters for laying out text on a shield background - */ +/** Parameters for laying out text on a shield background */ export interface TextLayout { constraintFunc: string; options?: { @@ -99,59 +97,55 @@ export interface TextLayout { */ export type StringPredicate = (spriteID: string) => boolean; -/** - * RouteParser unpacks a route definition from a sprite image string. - */ +/** RouteParser unpacks a route definition from a sprite image string */ export interface RouteParser { parse(spriteID: string): RouteDefinition; format(network: string, ref: string, name: string): string; } -/** - * Retrieve a sprite graphic based on an ID. - */ +/** Retrieve a sprite graphic based on an ID */ export interface SpriteProducer { getSprite(spriteID: string): StyleImage; } -/** - * Store a sprite graphic based on an ID. - */ +/** Store a sprite graphic based on an ID */ export interface SpriteConsumer { putSprite(spriteID: string, image: ImageData, pixelRatio: number): void; } -/** - * Respository that can store and retrieve sprite graphics. - */ +/** Respository that can store and retrieve sprite graphics */ export type SpriteRepository = SpriteProducer & SpriteConsumer; -/** - * A map of shield definitions that associates a network name to its rendering - */ +/** Map of shield definitions that associates a network name to its rendering */ export interface ShieldDefinitions { shield: { [key: string]: ShieldDefinition; }; } -export interface DebugOptions {} +/** Additional debugging-only options */ +export interface DebugOptions { + /** If set, draw a colored box around shield text constraint */ + shieldTextBboxColor?: string; +} -/** - * Global options for shield rendering - */ +/** Global options for shield rendering */ export interface ShieldOptions { + /** Height of each specified banner above the shield */ bannerHeight: number; + /** Padding between each banner */ bannerPadding: number; + /** Color of text on the banner */ bannerTextColor: string; + /** Color of halo on text on the banner */ bannerTextHaloColor: string; + /** Browser font for banner text */ shieldFont: string; + /** Default shield size in pixels at 1x */ shieldSize: number; } -/** - * A user-supplied specification for rendering shields - */ +/** A user-supplied specification for rendering shields */ export interface ShieldSpecification { networks: ShieldDefinitions; options: ShieldOptions; From bfc27cd6fb755bdc830519aba5320f5db8e350e3 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Sat, 9 Sep 2023 08:09:45 -0400 Subject: [PATCH 15/25] Add documentation --- shieldlib/src/shield_helper.ts | 11 +++++++ shieldlib/src/types.ts | 55 ++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 8d1967dfe..92acabf39 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -1,5 +1,11 @@ import { ShieldDefinition, TextLayout } from "./types"; +/** + * Constrain the text to a rounded rectangle + * + * @param radius 1x pixel radius of the constraint corners + * @returns a constraint definition + */ export function roundedRectTextConstraint(radius: number): TextLayout { return { constraintFunc: "roundedRect", @@ -9,6 +15,11 @@ export function roundedRectTextConstraint(radius: number): TextLayout { }; } +/** + * Constrain the text to a specified constraint type + * + * @returns a constraint definition + */ export function textConstraint(fxn: string): TextLayout { return { constraintFunc: fxn, diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 19314de5c..1966b0935 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -29,7 +29,23 @@ export interface ShieldDefinitionBase { notext?: boolean; } -/** Define how the renderer should draw the shield for a particular route */ +/** + * Define how the renderer should draw the shield for various routes + * + * @example + * const shieldsDefinition = { + * "US:I": { + * textColor: Color.shields.white, + * spriteBlank: ["shield_us_interstate_2", "shield_us_interstate_3"], + * textLayout: textConstraint("southHalfEllipse"), + * padding: { + * left: 4, + * right: 4, + * top: 6, + * bottom: 5, + * } + * }; + */ export type ShieldDefinition = Exclusive< { spriteBlank: string[] }, { shapeBlank: ShapeDefinition } @@ -145,15 +161,44 @@ export interface ShieldOptions { shieldSize: number; } -/** A user-supplied specification for rendering shields */ +/** + * A user-supplied specification for rendering shields + * + * @example + * + * const shieldsSpecification = { + * shields: { + * "US:I": { + * textColor: Color.shields.white, + * spriteBlank: ["shield_us_interstate_2", "shield_us_interstate_3"], + * textLayout: textConstraint("southHalfEllipse"), + * padding: { + * left: 4, + * right: 4, + * top: 6, + * bottom: 5, + * }, + * } + * }, + * options: { + * bannerTextColor: "#000", + * bannerTextHaloColor: "#FFF", + * bannerHeight: 9, + * bannerPadding: 1, + * shieldFont: '"sans-serif-condensed", "Arial Narrow", sans-serif', + * shieldSize: 20, + * } + * }; + * + */ export interface ShieldSpecification { + /** Shield definitions */ networks: ShieldDefinitions; + /** Shield options */ options: ShieldOptions; } -/** - * Rectangular bounds, in scaled pixels - */ +/** Rectangular bounds, in scaled pixels */ export type Bounds = { width: number; height: number; From fc2ff41f97a0a8f1fca6ab9e13c1ede2c50e244a Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 20:07:26 -0400 Subject: [PATCH 16/25] Update shieldlib/src/types.ts Co-authored-by: Clay Smalley --- shieldlib/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 1966b0935..4450705da 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -94,7 +94,7 @@ export interface ShapeBlankParams { pointUp?: boolean; /** Specify whether the short side of the shape is on top */ shortSideUp?: boolean; - /** Specify the angle of the defining vertex of the shape */ + /** Specify the angle at which the sides of the shape deviate from vertical. Higher number means pointier sides */ angle?: number; } From a275df328a346f3da93450ceeea4c9bc4cd36c7e Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 20:07:44 -0400 Subject: [PATCH 17/25] Update shieldlib/src/types.ts Co-authored-by: Clay Smalley --- shieldlib/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 4450705da..637538f55 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -82,9 +82,9 @@ export interface ShapeBlankParams { rectWidth?: number; /** Radius of the shape's corners */ radius?: number; - /** Radius of the shapes's first corner. This is used for shapes that can specify multiple raidus values */ + /** Radius of the shape's first corner. This is used for shapes that can specify multiple radius values */ radius1?: number; - /** Radius of the shapes's second corner. This is used for shapes that can specify multiple raidus values */ + /** Radius of the shape's second corner. This is used for shapes that can specify multiple radius values */ radius2?: number; /** Height of diagonal edges */ offset?: number; From b3e5800b993b005d1f0ab4596c1662037933cd0b Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 20:07:56 -0400 Subject: [PATCH 18/25] Update shieldlib/src/types.ts Co-authored-by: Clay Smalley --- shieldlib/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 637538f55..e9d244721 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -86,7 +86,7 @@ export interface ShapeBlankParams { radius1?: number; /** Radius of the shape's second corner. This is used for shapes that can specify multiple radius values */ radius2?: number; - /** Height of diagonal edges */ + /** Distance from top or bottom edge to vertices. Higher number means pointier top and/or bottom */ offset?: number; /** Width of the shape's outline */ outlineWidth?: number; From 5ddb252410b0f9c14d10bcdf3788bd647b9340a3 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 21:31:21 -0400 Subject: [PATCH 19/25] Convert shield_canvas_draw to typescript --- shieldlib/src/custom_shields.mjs | 2 +- shieldlib/src/shield.js | 2 +- ..._canvas_draw.mjs => shield_canvas_draw.ts} | 107 +++++++++++++++--- shieldlib/src/shield_renderer.d.ts | 7 ++ shieldlib/src/shield_renderer.ts | 8 ++ 5 files changed, 107 insertions(+), 19 deletions(-) rename shieldlib/src/{shield_canvas_draw.mjs => shield_canvas_draw.ts} (90%) diff --git a/shieldlib/src/custom_shields.mjs b/shieldlib/src/custom_shields.mjs index c390e60a6..939091488 100644 --- a/shieldlib/src/custom_shields.mjs +++ b/shieldlib/src/custom_shields.mjs @@ -1,6 +1,6 @@ "use strict"; -import * as ShieldDraw from "./shield_canvas_draw.mjs"; +import * as ShieldDraw from "./shield_canvas_draw"; // Special case for Allegheny, PA Belt System export function paBelt(r, ctx, params) { diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index 7255caf2d..6594d4a95 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -1,7 +1,7 @@ "use strict"; import * as ShieldText from "./shield_text.mjs"; -import * as ShieldDraw from "./shield_canvas_draw.mjs"; +import * as ShieldDraw from "./shield_canvas_draw"; import * as Gfx from "./screen_gfx.js"; function drawBannerPart(r, ctx, shieldDef, drawFunc) { diff --git a/shieldlib/src/shield_canvas_draw.mjs b/shieldlib/src/shield_canvas_draw.ts similarity index 90% rename from shieldlib/src/shield_canvas_draw.mjs rename to shieldlib/src/shield_canvas_draw.ts index 5b8236d6c..0147e79fb 100644 --- a/shieldlib/src/shield_canvas_draw.mjs +++ b/shieldlib/src/shield_canvas_draw.ts @@ -6,12 +6,19 @@ import * as ShieldText from "./shield_text.mjs"; import { loadCustomShields } from "./custom_shields.mjs"; +import { ShapeDrawFunction, ShieldRenderingContext } from "./shield_renderer"; +import { ShapeBlankParams } from "./types"; const minGenericShieldWidth = 20; const maxGenericShieldWidth = 34; const genericShieldFontSize = 18; -export function computeWidth(r, params, ref, shape) { +export function computeWidth( + r: ShieldRenderingContext, + params: ShapeBlankParams, + ref: string, + shape?: string +) { if (fixedWidthDefinitions[shape] !== undefined) { return r.px(fixedWidthDefinitions[shape]); } @@ -53,7 +60,12 @@ export function computeWidth(r, params, ref, shape) { } } -function ellipse(r, ctx, params, ref) { +function ellipse( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -71,6 +83,7 @@ function ellipse(r, ctx, params, ref) { radiusX, radiusY, 0, + 0, 2 * Math.PI, false ); @@ -84,7 +97,7 @@ function ellipse(r, ctx, params, ref) { return width; } -export function blank(r, ref) { +export function blank(r: ShieldRenderingContext, ref: string) { var shieldWidth = ShieldText.calculateTextWidth(r, ref, r.px(genericShieldFontSize)) + r.px(2); @@ -98,7 +111,12 @@ export function blank(r, ref) { }); } -export function roundedRectangle(r, ctx, params, ref) { +export function roundedRectangle( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -140,7 +158,12 @@ export function roundedRectangle(r, ctx, params, ref) { return width; } -function escutcheon(r, ctx, params, ref) { +function escutcheon( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let offset = params.offset == undefined ? 0 : params.offset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -192,7 +215,12 @@ function escutcheon(r, ctx, params, ref) { return width; } -function fishhead(r, ctx, params, ref) { +function fishhead( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let pointUp = params.pointUp == undefined ? false : params.pointUp; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -244,7 +272,12 @@ function fishhead(r, ctx, params, ref) { } } -function triangle(r, ctx, params, ref) { +function triangle( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let pointUp = params.pointUp == undefined ? false : params.pointUp; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -305,7 +338,12 @@ function triangle(r, ctx, params, ref) { return width; } -function trapezoid(r, ctx, params, ref) { +function trapezoid( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let shortSideUp = params.shortSideUp == undefined ? false : params.shortSideUp; let angle = params.angle == undefined ? 0 : params.angle; @@ -362,7 +400,12 @@ function trapezoid(r, ctx, params, ref) { return width; } -function diamond(r, ctx, params, ref) { +function diamond( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -426,7 +469,12 @@ function diamond(r, ctx, params, ref) { return width; } -function pentagon(r, ctx, params, ref) { +function pentagon( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let pointUp = params.pointUp == undefined ? true : params.pointUp; let offset = params.offset == undefined ? 0 : params.offset; let angle = params.angle == undefined ? 0 : params.angle; @@ -494,7 +542,12 @@ function pentagon(r, ctx, params, ref) { return width; } -function hexagonVertical(r, ctx, params, ref) { +function hexagonVertical( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let offset = params.offset == undefined ? 0 : params.offset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -543,7 +596,12 @@ function hexagonVertical(r, ctx, params, ref) { return width; } -function hexagonHorizontal(r, ctx, params, ref) { +function hexagonHorizontal( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let angle = params.angle == undefined ? 0 : params.angle; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -604,7 +662,12 @@ function hexagonHorizontal(r, ctx, params, ref) { return width; } -function octagonVertical(r, ctx, params, ref) { +function octagonVertical( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { let offset = params.offset == undefined ? 0 : params.offset; let angle = params.angle == undefined ? 0 : params.angle; let fill = params.fillColor == undefined ? "white" : params.fillColor; @@ -684,7 +747,7 @@ function octagonVertical(r, ctx, params, ref) { return width; } -export function shapeHeight(r, name) { +export function shapeHeight(r: ShieldRenderingContext, name: string) { switch (name) { case "diamond": return r.shieldSize() + r.px(4); @@ -693,8 +756,14 @@ export function shapeHeight(r, name) { } } -export function draw(r, name, ctx, options, ref) { - return drawFunctions[name](r, ctx, options, ref); +export function draw( + r: ShieldRenderingContext, + name: string, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) { + return drawFunctions[name](r, ctx, params, ref); } //Register draw functions @@ -710,7 +779,11 @@ const fixedWidthDefinitions = {}; * @param {*} fxn callback to the implementing function. Takes two parameters, ref and options * @param {*} fixedWidth if set, indicates that this function draws to a fixed width */ -export function registerDrawFunction(name, fxn, fixedWidth) { +export function registerDrawFunction( + name: string, + fxn: ShapeDrawFunction, + fixedWidth?: boolean +) { drawFunctions[name] = fxn; if (fixedWidth !== undefined) { fixedWidthDefinitions[name] = fixedWidth; diff --git a/shieldlib/src/shield_renderer.d.ts b/shieldlib/src/shield_renderer.d.ts index a4b0ddbf6..fc9d9ac19 100644 --- a/shieldlib/src/shield_renderer.d.ts +++ b/shieldlib/src/shield_renderer.d.ts @@ -4,6 +4,7 @@ import { DebugOptions, GraphicsFactory, RouteParser, + ShapeBlankParams, ShieldDefinitions, ShieldOptions, ShieldSpecification, @@ -21,6 +22,12 @@ export declare class ShieldRenderingContext { px(pixels: number): number; shieldSize(): number; } +export declare type ShapeDrawFunction = ( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) => void; export declare class AbstractShieldRenderer { private _shieldPredicate; private _networkPredicate; diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 33846b9fe..570dc3aa6 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -5,6 +5,7 @@ import { GraphicsFactory, RouteDefinition, RouteParser, + ShapeBlankParams, ShieldDefinitions, ShieldOptions, ShieldSpecification, @@ -43,6 +44,13 @@ export class ShieldRenderingContext { } } +export type ShapeDrawFunction = ( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams, + ref: string +) => void; + class MaplibreGLSpriteRepository implements SpriteRepository { map: Map; constructor(map: Map) { From c33f257d46d3f911b89cf2e42574bdc90b067d8f Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 21:51:46 -0400 Subject: [PATCH 20/25] Rename sideAngle and yOffset --- shieldlib/src/shield_canvas_draw.ts | 20 +++--- shieldlib/src/shield_helper.ts | 96 ++++++++++++++--------------- shieldlib/src/types.ts | 4 +- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/shieldlib/src/shield_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 0147e79fb..1d094477c 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -24,7 +24,7 @@ export function computeWidth( } let rectWidth = params.rectWidth == undefined ? null : params.rectWidth; - let angle = params.angle == undefined ? 0 : params.angle; + let angle = params.sideAngle == undefined ? 0 : params.sideAngle; let tangent = Math.tan(angle); if (rectWidth == null) { @@ -36,7 +36,7 @@ export function computeWidth( //Shape-specific width adjustments switch (shape) { case "pentagon": - let offset = params.offset == undefined ? 0 : params.offset; + let offset = params.yOffset == undefined ? 0 : params.yOffset; shieldWidth += ((r.shieldSize() - r.px(offset)) * tangent) / 2; break; case "trapezoid": @@ -164,7 +164,7 @@ function escutcheon( params: ShapeBlankParams, ref: string ) { - let offset = params.offset == undefined ? 0 : params.offset; + let offset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -346,7 +346,7 @@ function trapezoid( ) { let shortSideUp = params.shortSideUp == undefined ? false : params.shortSideUp; - let angle = params.angle == undefined ? 0 : params.angle; + let angle = 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; @@ -476,8 +476,8 @@ function pentagon( ref: string ) { let pointUp = params.pointUp == undefined ? true : params.pointUp; - let offset = params.offset == undefined ? 0 : params.offset; - let angle = params.angle == undefined ? 0 : params.angle; + let offset = params.yOffset == undefined ? 0 : params.yOffset; + let angle = params.sideAngle == undefined ? 0 : params.sideAngle; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius1 = params.radius1 == undefined ? 0 : params.radius1; @@ -548,7 +548,7 @@ function hexagonVertical( params: ShapeBlankParams, ref: string ) { - let offset = params.offset == undefined ? 0 : params.offset; + let offset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -602,7 +602,7 @@ function hexagonHorizontal( params: ShapeBlankParams, ref: string ) { - let angle = params.angle == undefined ? 0 : params.angle; + let angle = 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; @@ -668,8 +668,8 @@ function octagonVertical( params: ShapeBlankParams, ref: string ) { - let offset = params.offset == undefined ? 0 : params.offset; - let angle = params.angle == undefined ? 0 : params.angle; + let offset = params.yOffset == undefined ? 0 : params.yOffset; + let angle = 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; diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 92acabf39..d8e6aebe0 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -121,7 +121,7 @@ export function roundedRectShield( /** * Draws a shield with an escutcheon background, pointed downward * - * @param {*} offset - Height of curved portion + * @param {*} yOffset - Height of curved portion * @param {*} fillColor - Color of escutcheon background fill * @param {*} strokeColor - Color of escutcheon outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -130,7 +130,7 @@ export function roundedRectShield( * @returns a shield definition object */ export function escutcheonDownShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -143,7 +143,7 @@ export function escutcheonDownShield( shapeBlank: { drawFunc: "escutcheon", params: { - offset, + yOffset, fillColor, strokeColor, rectWidth, @@ -156,7 +156,7 @@ export function escutcheonDownShield( left: 2, right: 2, top: 2, - bottom: 0 + offset / 2, + bottom: 0 + yOffset / 2, }, textColor, }; @@ -244,7 +244,7 @@ export function triangleDownShield( /** * Draws a shield with a trapezoid background, with the short side on bottom * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} sideAngle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of trapezoid background fill * @param {*} strokeColor - Color of trapezoid outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -253,14 +253,14 @@ export function triangleDownShield( * @returns a shield definition object */ export function trapezoidDownShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, radius: number, rectWidth: number ): ShieldDefinition { - let angleInRadians = (angle * Math.PI) / 180; + let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 0; @@ -268,7 +268,7 @@ export function trapezoidDownShield( shapeBlank: { drawFunc: "trapezoid", params: { - angle: angleInRadians, + sideAngle: angleInRadians, fillColor, strokeColor, rectWidth, @@ -289,7 +289,7 @@ export function trapezoidDownShield( /** * Draws a shield with a trapezoid background, with the short side on top * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} sideAngle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of trapezoid background fill * @param {*} strokeColor - Color of trapezoid outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -298,14 +298,14 @@ export function trapezoidDownShield( * @returns a shield definition object */ export function trapezoidUpShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, radius: number, rectWidth: number ): ShieldDefinition { - let angleInRadians = (angle * Math.PI) / 180; + let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 0; return { @@ -313,7 +313,7 @@ export function trapezoidUpShield( drawFunc: "trapezoid", params: { shortSideUp: true, - angle: angleInRadians, + sideAngle: angleInRadians, fillColor, strokeColor, rectWidth, @@ -374,8 +374,8 @@ export function diamondShield( /** * Draws a shield with a pentagon background, pointed upward * - * @param {*} offset - Height of diagonal edges - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} yOffset - Height of diagonal edges + * @param {*} sideAngle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of pentagon background fill * @param {*} strokeColor - Color of pentagon outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -385,8 +385,8 @@ export function diamondShield( * @returns a shield definition object */ export function pentagonUpShield( - offset: number, - angle: number, + yOffset: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, @@ -394,7 +394,7 @@ export function pentagonUpShield( radius2: number, rectWidth: number ): ShieldDefinition { - let angleInRadians = (angle * Math.PI) / 180; + let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius1 = radius1 ?? 2; radius2 = radius2 ?? 0; @@ -402,10 +402,10 @@ export function pentagonUpShield( shapeBlank: { drawFunc: "pentagon", params: { - offset: offset, - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, + yOffset, + sideAngle: angleInRadians, + fillColor, + strokeColor, radius1, radius2, rectWidth, @@ -415,9 +415,9 @@ export function pentagonUpShield( constraintFunc: "rect", }, padding: { - left: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, - right: 2 + ((20 - offset) * Math.tan(angleInRadians)) / 2, - top: 1 + offset / 2, + left: 2 + ((20 - yOffset) * Math.tan(angleInRadians)) / 2, + right: 2 + ((20 - yOffset) * Math.tan(angleInRadians)) / 2, + top: 1 + yOffset / 2, bottom: 3, }, textColor, @@ -427,7 +427,7 @@ export function pentagonUpShield( /** * Draws a shield with a home plate background, pointed downward * - * @param {*} offset - Height of diagonal edges + * @param {*} yOffset - Height of diagonal edges * @param {*} fillColor - Color of home plate background fill * @param {*} strokeColor - Color of home plate outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -437,7 +437,7 @@ export function pentagonUpShield( * @returns a shield definition object */ export function homePlateDownShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -453,8 +453,8 @@ export function homePlateDownShield( drawFunc: "pentagon", params: { pointUp: false, - offset, - angle: 0, + yOffset, + sideAngle: 0, fillColor, strokeColor, radius1, @@ -467,7 +467,7 @@ export function homePlateDownShield( left: 2, right: 2, top: 2, - bottom: 1 + offset, + bottom: 1 + yOffset, }, textColor, }; @@ -476,7 +476,7 @@ export function homePlateDownShield( /** * Draws a shield with a home plate background, pointed upward * - * @param {*} offset - Height of diagonal edges + * @param {*} yOffset - Height of diagonal edges * @param {*} fillColor - Color of home plate background fill * @param {*} strokeColor - Color of home plate outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -486,7 +486,7 @@ export function homePlateDownShield( * @returns a shield definition object */ export function homePlateUpShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -502,8 +502,8 @@ export function homePlateUpShield( drawFunc: "pentagon", params: { pointUp: true, - offset: offset, - angle: 0, + yOffset, + sideAngle: 0, fillColor, strokeColor, radius1, @@ -515,7 +515,7 @@ export function homePlateUpShield( padding: { left: 2, right: 2, - top: 1 + offset, + top: 1 + yOffset, bottom: 2, }, textColor, @@ -525,7 +525,7 @@ export function homePlateUpShield( /** * Draws a shield with a vertically-aligned hexagon background * - * @param {*} offset - Height of diagonal edges + * @param {*} yOffset - Height of diagonal edges * @param {*} fillColor - Color of hexagon background fill * @param {*} strokeColor - Color of hexagon outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -534,7 +534,7 @@ export function homePlateUpShield( * @returns a shield definition object */ export function hexagonVerticalShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -547,7 +547,7 @@ export function hexagonVerticalShield( shapeBlank: { drawFunc: "hexagonVertical", params: { - offset, + yOffset, fillColor, strokeColor, radius, @@ -558,8 +558,8 @@ export function hexagonVerticalShield( padding: { left: 2, right: 2, - top: 1 + offset, - bottom: 1 + offset, + top: 1 + yOffset, + bottom: 1 + yOffset, }, textColor, }; @@ -591,11 +591,11 @@ export function hexagonHorizontalShield( shapeBlank: { drawFunc: "hexagonHorizontal", params: { - angle: angleInRadians, - fillColor: fillColor, - strokeColor: strokeColor, - radius: radius, - rectWidth: rectWidth, + sideAngle: angleInRadians, + fillColor, + strokeColor, + radius, + rectWidth, }, }, textLayout: textConstraint("ellipse"), @@ -612,7 +612,7 @@ export function hexagonHorizontalShield( /** * Draws a shield with an octagon background * - * @param {*} offset - Height of diagonal edges + * @param {*} yOffset - Height of diagonal edges * @param {*} angle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of octagon background fill * @param {*} strokeColor - Color of octagon outline stroke @@ -622,7 +622,7 @@ export function hexagonHorizontalShield( * @returns a shield definition object */ export function octagonVerticalShield( - offset: number, + yOffset: number, angle: number, fillColor: string, strokeColor: string, @@ -637,8 +637,8 @@ export function octagonVerticalShield( shapeBlank: { drawFunc: "octagonVertical", params: { - offset, - angle: angleInRadians, + yOffset, + sideAngle: angleInRadians, fillColor, strokeColor, radius, diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index e9d244721..5912be5de 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -87,7 +87,7 @@ export interface ShapeBlankParams { /** Radius of the shape's second corner. This is used for shapes that can specify multiple radius values */ radius2?: number; /** Distance from top or bottom edge to vertices. Higher number means pointier top and/or bottom */ - offset?: number; + yOffset?: number; /** Width of the shape's outline */ outlineWidth?: number; /** Specify whether the pointy end of the shape is on top */ @@ -95,7 +95,7 @@ export interface ShapeBlankParams { /** Specify whether the short side of the shape is on top */ shortSideUp?: boolean; /** Specify the angle at which the sides of the shape deviate from vertical. Higher number means pointier sides */ - angle?: number; + sideAngle?: number; } /** Parameters for laying out text on a shield background */ From e41b15155deddd71a667df50e7ee2608ec910ef3 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 27 Sep 2023 22:14:33 -0400 Subject: [PATCH 21/25] Convert custom shields to typescript --- .../src/{custom_shields.mjs => custom_shields.ts} | 14 ++++++++++++-- shieldlib/src/shield_canvas_draw.ts | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) rename shieldlib/src/{custom_shields.mjs => custom_shields.ts} (81%) diff --git a/shieldlib/src/custom_shields.mjs b/shieldlib/src/custom_shields.ts similarity index 81% rename from shieldlib/src/custom_shields.mjs rename to shieldlib/src/custom_shields.ts index 939091488..8eccf845b 100644 --- a/shieldlib/src/custom_shields.mjs +++ b/shieldlib/src/custom_shields.ts @@ -1,9 +1,15 @@ "use strict"; import * as ShieldDraw from "./shield_canvas_draw"; +import { ShieldRenderingContext } from "./shield_renderer"; +import { ShapeBlankParams } from "./types"; // Special case for Allegheny, PA Belt System -export function paBelt(r, ctx, params) { +export function paBelt( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams +) { ShieldDraw.roundedRectangle(r, ctx, { fillColor: "white", strokeColor: "black", @@ -36,7 +42,11 @@ export function paBelt(r, ctx, params) { } // Special case for Branson color-coded routes -export function bransonRoute(r, ctx, params) { +export function bransonRoute( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + params: ShapeBlankParams +) { ShieldDraw.roundedRectangle(r, ctx, { fillColor: "#006747", strokeColor: "white", diff --git a/shieldlib/src/shield_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 1d094477c..11057815c 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -5,7 +5,7 @@ */ import * as ShieldText from "./shield_text.mjs"; -import { loadCustomShields } from "./custom_shields.mjs"; +import { loadCustomShields } from "./custom_shields"; import { ShapeDrawFunction, ShieldRenderingContext } from "./shield_renderer"; import { ShapeBlankParams } from "./types"; @@ -115,7 +115,7 @@ export function roundedRectangle( r: ShieldRenderingContext, ctx: CanvasRenderingContext2D, params: ShapeBlankParams, - ref: string + ref?: string ) { let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; @@ -782,7 +782,7 @@ const fixedWidthDefinitions = {}; export function registerDrawFunction( name: string, fxn: ShapeDrawFunction, - fixedWidth?: boolean + fixedWidth?: number ) { drawFunctions[name] = fxn; if (fixedWidth !== undefined) { From 8978a4d968ad4c17eede92a8d5549ba0eb17ad6d Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 2 Oct 2023 22:37:26 -0400 Subject: [PATCH 22/25] Propagate new naming convention --- shieldlib/README.md | 8 +- shieldlib/src/screen_gfx.ts | 2 +- shieldlib/src/shield_canvas_draw.ts | 60 ++-- shieldlib/src/shield_helper.d.ts | 35 +- shieldlib/src/shield_helper.ts | 6 +- shieldlib/src/shield_text.ts | 473 ++++++++++++++++++++++++++++ shieldlib/src/types.ts | 13 +- 7 files changed, 533 insertions(+), 64 deletions(-) create mode 100644 shieldlib/src/shield_text.ts diff --git a/shieldlib/README.md b/shieldlib/README.md index c1f4e6379..1a85ff789 100644 --- a/shieldlib/README.md +++ b/shieldlib/README.md @@ -127,8 +127,8 @@ You should create one definition entry for each network. The entry key must matc "drawFunc": "pentagon", "params": { "pointUp": false, - "offset": 5, - "angle": 0, + "yOffset": 5, + "sideAngle": 0, "fillColor": "white", "strokeColor": "black", "radius1": 2, @@ -299,9 +299,9 @@ If `shapeBlank` is specified, the shield will be drawn as a shape. This needs to The following `params` options can be specified: -- `angle` - indicates angle (in degrees) at which side edges deviate from vertical. Applies to `trapezoid`, `pentagon`, `hexagonHorizontal`, `octagonVertical`. +- `sideAngle` - indicates angle (in degrees) at which side edges deviate from vertical. Applies to `trapezoid`, `pentagon`, `hexagonHorizontal`, `octagonVertical`. - `fill` - specifies the internal fill color. -- `offset` - indicates height (in pixels) at which the bottom and/or top edges deviate from horizontal. Applies to `escutcheon`, `pentagon`, `hexagonVertical`, `octagonVertical`. +- `yOffset` - indicates height (in pixels) at which the bottom and/or top edges deviate from horizontal. Applies to `escutcheon`, `pentagon`, `hexagonVertical`, `octagonVertical`. - `outline` - specifies the outline color. - `outlineWidth` - specifies the width of the outline. - `pointUp` - applies to several shape types and specifies whether the pointy side is up. 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_canvas_draw.ts b/shieldlib/src/shield_canvas_draw.ts index 11057815c..6cfa8154f 100644 --- a/shieldlib/src/shield_canvas_draw.ts +++ b/shieldlib/src/shield_canvas_draw.ts @@ -24,8 +24,8 @@ export function computeWidth( } let rectWidth = params.rectWidth == undefined ? null : params.rectWidth; - let angle = params.sideAngle == undefined ? 0 : params.sideAngle; - let tangent = Math.tan(angle); + let sideAngle = params.sideAngle == undefined ? 0 : params.sideAngle; + let tangent = Math.tan(sideAngle); if (rectWidth == null) { let shieldWidth = @@ -36,8 +36,8 @@ export function computeWidth( //Shape-specific width adjustments switch (shape) { case "pentagon": - let offset = params.yOffset == undefined ? 0 : params.yOffset; - shieldWidth += ((r.shieldSize() - r.px(offset)) * tangent) / 2; + let yOffset = params.yOffset == undefined ? 0 : params.yOffset; + shieldWidth += ((r.shieldSize() - r.px(yOffset)) * tangent) / 2; break; case "trapezoid": shieldWidth += (r.shieldSize() * tangent) / 2; @@ -164,7 +164,7 @@ function escutcheon( params: ShapeBlankParams, ref: string ) { - let offset = params.yOffset == undefined ? 0 : params.yOffset; + let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -175,7 +175,7 @@ function escutcheon( let lineThick = r.px(outlineWidth); let lineWidth = lineThick / 2; let drawRadius = r.px(radius); - let drawOffset = r.px(offset); + let drawOffset = r.px(yOffset); let x0 = lineWidth; let x5 = width - lineWidth; @@ -346,16 +346,16 @@ function trapezoid( ) { let shortSideUp = params.shortSideUp == undefined ? false : params.shortSideUp; - let angle = params.sideAngle == undefined ? 0 : params.sideAngle; + 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 angleSign = shortSideUp ? -1 : 1; - let sine = Math.sin(angle); - let cosine = Math.cos(angle); - let tangent = Math.tan(angle); + let sine = Math.sin(sideAngle); + let cosine = Math.cos(sideAngle); + let tangent = Math.tan(sideAngle); let width = computeWidth(r, params, ref, "trapezoid"); @@ -476,8 +476,8 @@ function pentagon( ref: string ) { let pointUp = params.pointUp == undefined ? true : params.pointUp; - let offset = params.yOffset == undefined ? 0 : params.yOffset; - let angle = params.sideAngle == undefined ? 0 : params.sideAngle; + let yOffset = params.yOffset == undefined ? 0 : params.yOffset; + 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 radius1 = params.radius1 == undefined ? 0 : params.radius1; @@ -485,9 +485,9 @@ function pentagon( let outlineWidth = params.outlineWidth == undefined ? 1 : params.outlineWidth; let angleSign = pointUp ? -1 : 1; - let sine = Math.sin(angle); - let cosine = Math.cos(angle); - let tangent = Math.tan(angle); + let sine = Math.sin(sideAngle); + let cosine = Math.cos(sideAngle); + let tangent = Math.tan(sideAngle); let width = computeWidth(r, params, ref, "pentagon"); @@ -495,7 +495,7 @@ function pentagon( let lineWidth = lineThick / 2; let drawRadius1 = r.px(radius1); let drawRadius2 = r.px(radius2); - let drawOffset = r.px(offset); + let drawOffset = r.px(yOffset); let x0 = lineWidth; let x8 = width - lineWidth; @@ -510,10 +510,10 @@ function pentagon( let offsetAngle = Math.atan(drawOffset / (x4 - x0)); - let halfComplementAngle1 = (Math.PI / 2 - offsetAngle + angle) / 2; + let halfComplementAngle1 = (Math.PI / 2 - offsetAngle + sideAngle) / 2; let halfComplementTangent1 = Math.tan(halfComplementAngle1); - let halfComplementAngle2 = (Math.PI / 2 - angle) / 2; + let halfComplementAngle2 = (Math.PI / 2 - sideAngle) / 2; let halfComplementTangent2 = Math.tan(halfComplementAngle2); let x1 = x0 + drawRadius1 * halfComplementTangent1 * sine; @@ -548,7 +548,7 @@ function hexagonVertical( params: ShapeBlankParams, ref: string ) { - let offset = params.yOffset == undefined ? 0 : params.yOffset; + let yOffset = params.yOffset == undefined ? 0 : params.yOffset; let fill = params.fillColor == undefined ? "white" : params.fillColor; let outline = params.strokeColor == undefined ? "black" : params.strokeColor; let radius = params.radius == undefined ? 0 : params.radius; @@ -559,7 +559,7 @@ function hexagonVertical( let lineThick = r.px(outlineWidth); let lineWidth = lineThick / 2; let drawRadius = r.px(radius); - let drawOffset = r.px(offset); + let drawOffset = r.px(yOffset); let x0 = lineWidth; let x2 = width - lineWidth; @@ -668,23 +668,23 @@ function octagonVertical( params: ShapeBlankParams, ref: string ) { - let offset = params.yOffset == undefined ? 0 : params.yOffset; - let angle = params.sideAngle == undefined ? 0 : params.sideAngle; + let yOffset = params.yOffset == undefined ? 0 : params.yOffset; + 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 sine = Math.sin(sideAngle); + let cosine = Math.cos(sideAngle); + let tangent = Math.tan(sideAngle); let width = computeWidth(r, params, ref); let lineThick = r.px(outlineWidth); let lineWidth = lineThick / 2; let drawRadius = r.px(radius); - let drawOffset = r.px(offset); + let drawOffset = r.px(yOffset); let x0 = lineWidth; let x10 = width - lineWidth; @@ -707,13 +707,15 @@ function octagonVertical( let offsetSine = Math.sin(offsetAngle); let offsetCosine = Math.cos(offsetAngle); - let halfComplementAngle = (Math.PI / 2 - angle - offsetAngle) / 2; + let halfComplementAngle = (Math.PI / 2 - sideAngle - offsetAngle) / 2; let halfComplementCosine = Math.cos(halfComplementAngle); let dx = - (drawRadius * Math.cos(angle + halfComplementAngle)) / halfComplementCosine; + (drawRadius * Math.cos(sideAngle + halfComplementAngle)) / + halfComplementCosine; let dy = - (drawRadius * Math.sin(angle + halfComplementAngle)) / halfComplementCosine; + (drawRadius * Math.sin(sideAngle + halfComplementAngle)) / + halfComplementCosine; let x2 = x3 + dx - drawRadius * cosine; let x4 = x3 + dx - drawRadius * offsetSine; diff --git a/shieldlib/src/shield_helper.d.ts b/shieldlib/src/shield_helper.d.ts index 64bc9f72e..85c942bee 100644 --- a/shieldlib/src/shield_helper.d.ts +++ b/shieldlib/src/shield_helper.d.ts @@ -24,7 +24,7 @@ export declare function roundedRectShield( ): ShieldDefinition; export declare function escutcheonDownShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -48,16 +48,16 @@ export declare function triangleDownShield( ): ShieldDefinition; export declare function trapezoidDownShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, radius: number, rectWidth: number -); +): ShieldDefinition; export declare function trapezoidUpShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, @@ -74,8 +74,8 @@ export declare function diamondShield( ): ShieldDefinition; export declare function pentagonUpShield( - offset: number, - angle: number, + yOffset: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, @@ -85,7 +85,7 @@ export declare function pentagonUpShield( ): ShieldDefinition; export declare function homePlateDownShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -95,7 +95,7 @@ export declare function homePlateDownShield( ): ShieldDefinition; export declare function homePlateUpShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -105,7 +105,7 @@ export declare function homePlateUpShield( ): ShieldDefinition; export declare function hexagonVerticalShield( - offset: number, + yOffset: number, fillColor: string, strokeColor: string, textColor: string, @@ -113,19 +113,8 @@ export declare function hexagonVerticalShield( rectWidth: number ): ShieldDefinition; -/** - * Draws a shield with a horizontally-aligned hexagon background - * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical - * @param {*} fillColor - Color of hexagon background fill - * @param {*} strokeColor - Color of hexagon outline stroke - * @param {*} textColor - Color of text (defaults to strokeColor) - * @param {*} radius - Corner radius of hexagon (defaults to 2) - * @param {*} rectWidth - Width of hexagon (defaults to variable-width) - * @returns a shield definition object - */ export declare function hexagonHorizontalShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, @@ -134,8 +123,8 @@ export declare function hexagonHorizontalShield( ): ShieldDefinition; export declare function octagonVerticalShield( - offset: number, - angle: number, + yOffset: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index d8e6aebe0..8b34c9e9d 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -568,7 +568,7 @@ export function hexagonVerticalShield( /** * Draws a shield with a horizontally-aligned hexagon background * - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} sideAngle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of hexagon background fill * @param {*} strokeColor - Color of hexagon outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -577,14 +577,14 @@ export function hexagonVerticalShield( * @returns a shield definition object */ export function hexagonHorizontalShield( - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, radius: number, rectWidth: number ): ShieldDefinition { - let angleInRadians = (angle * Math.PI) / 180; + let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 2; return { diff --git a/shieldlib/src/shield_text.ts b/shieldlib/src/shield_text.ts new file mode 100644 index 000000000..e9dc755ef --- /dev/null +++ b/shieldlib/src/shield_text.ts @@ -0,0 +1,473 @@ +"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, +}; + +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; + + var x0 = textBounds.width; + var y0 = textBounds.height; + + return (a * b) / Math.sqrt(a * a * y0 * y0 + b * b * x0 * x0); +} + +function ellipseTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { + return { + scale: ellipseScale(spaceBounds, textBounds), + valign: VerticalAlignment.Middle, + }; +} + +function southHalfEllipseTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { + return { + scale: ellipseScale(spaceBounds, { + //Turn ellipse 90 degrees + height: textBounds.width / 2, + width: textBounds.height, + }), + valign: VerticalAlignment.Top, + }; +} + +function rectTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { + var scaleHeight = spaceBounds.height / textBounds.height; + var scaleWidth = spaceBounds.width / textBounds.width; + + return { + scale: Math.min(scaleWidth, scaleHeight), + valign: VerticalAlignment.Middle, + }; +} + +function roundedRectTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension, + options: TextLayoutParameters +): TextTransform { + //Shrink space bounds so that corners hit the arcs + let constraintRadius = 2; + if (options !== undefined && options.radius !== undefined) { + constraintRadius = options.radius; + } + + return rectTextConstraint( + { + width: spaceBounds.width - constraintRadius * (2 - Math.sqrt(2)), + height: spaceBounds.height - constraintRadius * (2 - Math.sqrt(2)), + }, + textBounds + ); +} + +function diamondTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { + let a = spaceBounds.width; + let b = spaceBounds.height; + + let x0 = textBounds.width; + let y0 = textBounds.height; + + return { + scale: (a * b) / (b * x0 + a * y0), + valign: VerticalAlignment.Middle, + }; +} + +function triangleDownTextConstraint( + spaceBounds: Dimension, + textBounds: Dimension +): TextTransform { + return { + scale: diamondTextConstraint(spaceBounds, textBounds).scale, + valign: VerticalAlignment.Top, + }; +} + +/** + * Determines the position and font size to draw text so that it fits within + * a bounding box. + * + * @param {*} r - rendering context + * @param {*} text - text to draw + * @param {*} padding - top/bottom/left/right padding around text + * @param {*} bounds - size of the overall graphics area + * @param {*} textLayoutDef - algorithm definition for text scaling + * @param {*} maxFontSize - maximum font size + * @returns JOSN object containing (X,Y) draw position and font size + */ +function layoutShieldText( + 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; + var padRight = r.px(padding.right) || 0; + + var maxFont = r.px(maxFontSize); + //Temporary canvas for text measurment + var ctx = r.gfxFactory.createGraphics(bounds); + + ctx.font = Gfx.shieldFont(Gfx.fontSizeThreshold, r.options.shieldFont); + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + + var metrics = ctx.measureText(text); + + var textWidth = metrics.width; + var textHeight = metrics.actualBoundingBoxDescent; + + var availHeight = bounds.height - padTop - padBot; + var availWidth = bounds.width - padLeft - padRight; + + var xBaseline = padLeft + availWidth / 2; + + let textLayoutFunc = drawTextFunctions[textLayoutDef.constraintFunc]; + + let textConstraint = textLayoutFunc( + { height: availHeight, width: availWidth }, + { height: textHeight, width: textWidth }, + textLayoutDef.options + ); + + //If size-to-fill shield text is too big, shrink it + var fontSize = Math.min( + maxFont, + Gfx.fontSizeThreshold * textConstraint.scale + ); + + ctx.font = Gfx.shieldFont(fontSize, r.options.shieldFont); + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + + metrics = ctx.measureText(text); + textHeight = metrics.actualBoundingBoxDescent; + + let yBaseline: number; + + switch (textConstraint.valign) { + case VerticalAlignment.Top: + yBaseline = padTop; + break; + case VerticalAlignment.Bottom: + yBaseline = padTop + availHeight - textHeight; + break; + case VerticalAlignment.Middle: + default: + yBaseline = padTop + (availHeight - textHeight) / 2; + break; + } + + return { + xBaseline, + yBaseline, + fontPx: fontSize, + }; +} + +const defaultDefForLayout: ShieldDefinition = { + padding: { + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + shapeBlank: { + drawFunc: "rectangle", + params: { + fillColor: "white", + strokeColor: "black", + }, + }, +}; + +/** + * Determines the position and font size to draw text so that it fits within + * a bounding box. + * + * @param {*} r - rendering context + * @param {*} text - text to draw + * @param {*} def - shield definition + * @param {*} bounds - size of the overall graphics area + * @returns JOSN object containing (X,Y) draw position and font size + */ +export function layoutShieldTextFromDef( + r: ShieldRenderingContext, + text: string, + def: ShieldDefinition, + bounds: Dimension +): TextPlacement { + //FIX + if (def == null) { + def = defaultDefForLayout; + } + + var padding = def.padding || noPadding; + + var textLayoutDef = { + constraintFunc: "rect", + }; + + var maxFontSize = 14; // default max size + + if (typeof def.textLayout != "undefined") { + textLayoutDef = def.textLayout; + } + + if (typeof def.maxFontSize != "undefined") { + maxFontSize = Math.min(maxFontSize, def.maxFontSize); // shield definition cannot set max size higher than default + } + + return layoutShieldText(r, text, padding, bounds, textLayoutDef, maxFontSize); +} + +/** + * Draw text on a shield + * + * @param {*} r - rendering context + * @param {*} ctx - graphics context to draw to + * @param {*} text - text to draw + * @param {*} textLayout - location to draw text + */ +export function renderShieldText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + text: string, + textLayout: TextPlacement +): void { + //Text color is set by fillStyle + configureShieldText(r, ctx, textLayout); + + ctx.fillText(text, textLayout.xBaseline, textLayout.yBaseline); +} + +/** + * Draw drop shadow for text on a shield + * + * @param {*} r - rendering context + * @param {*} ctx - graphics context to draw to + * @param {*} text - text to draw + * @param {*} textLayout - location to draw text + */ +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.toString(); + ctx.shadowBlur = 0; + ctx.lineWidth = r.px(2); + + ctx.strokeText(text, textLayout.xBaseline, textLayout.yBaseline); + ctx.shadowColor = null; + ctx.shadowBlur = null; +} + +function configureShieldText( + r: ShieldRenderingContext, + ctx: CanvasRenderingContext2D, + textLayout: TextPlacement +): void { + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + 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, + fontSize: number +): number { + var ctx = r.emptySprite(); //dummy canvas + ctx.font = Gfx.shieldFont(fontSize, r.options.shieldFont); + return Math.ceil(ctx.measureText(text).width); +} + +//Register text draw functions +const drawTextFunctions = {}; + +/** + * Invoked by a style to implement a custom draw function + * + * @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: string, fxn: TextLayoutScaler): void { + drawTextFunctions[name] = fxn; +} + +//Built-in draw functions (standard shapes) +registerDrawTextFunction("diamond", diamondTextConstraint); +registerDrawTextFunction("ellipse", ellipseTextConstraint); +registerDrawTextFunction("rect", rectTextConstraint); +registerDrawTextFunction("roundedRect", roundedRectTextConstraint); +registerDrawTextFunction("southHalfEllipse", southHalfEllipseTextConstraint); +registerDrawTextFunction("triangleDown", triangleDownTextConstraint); diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 5912be5de..2c1038dbb 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; + /** Maximum size of shield text */ + maxFontSize?: number; } /** @@ -98,12 +100,15 @@ export interface ShapeBlankParams { sideAngle?: number; } -/** Parameters for laying out text on a shield background */ +/** Definition for laying out text on a shield background */ export interface TextLayout { constraintFunc: string; - options?: { - radius: number; - }; + options?: TextLayoutParameters; +} + +/** Options for text layout on a shield */ +export interface TextLayoutParameters { + radius: number; } /** From 592a3e9ea5a9fab4b15d00a1bb77ae1366de0573 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Tue, 3 Oct 2023 17:34:39 -0400 Subject: [PATCH 23/25] Update shieldlib/src/shield_helper.ts Co-authored-by: Clay Smalley --- shieldlib/src/shield_helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shieldlib/src/shield_helper.ts b/shieldlib/src/shield_helper.ts index 8b34c9e9d..64b141e59 100644 --- a/shieldlib/src/shield_helper.ts +++ b/shieldlib/src/shield_helper.ts @@ -613,7 +613,7 @@ export function hexagonHorizontalShield( * Draws a shield with an octagon background * * @param {*} yOffset - Height of diagonal edges - * @param {*} angle - Angle (in degrees) at which sides deviate from vertical + * @param {*} sideAngle - Angle (in degrees) at which sides deviate from vertical * @param {*} fillColor - Color of octagon background fill * @param {*} strokeColor - Color of octagon outline stroke * @param {*} textColor - Color of text (defaults to strokeColor) @@ -623,14 +623,14 @@ export function hexagonHorizontalShield( */ export function octagonVerticalShield( yOffset: number, - angle: number, + sideAngle: number, fillColor: string, strokeColor: string, textColor: string, radius: number, rectWidth: number ): ShieldDefinition { - let angleInRadians = (angle * Math.PI) / 180; + let angleInRadians = (sideAngle * Math.PI) / 180; textColor = textColor ?? strokeColor; radius = radius ?? 2; return { From 747bd234645789dc3080e7b498937c689e956fc7 Mon Sep 17 00:00:00 2001 From: Clay Smalley Date: Tue, 3 Oct 2023 17:37:08 -0400 Subject: [PATCH 24/25] npm run code_format on Github workflows --- .github/workflows/deploy-pr-checks.yml | 2 +- .github/workflows/test-build-mac.yml | 6 +++--- .github/workflows/test-build-ubuntu.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-pr-checks.yml b/.github/workflows/deploy-pr-checks.yml index 57babd8e2..19e11ef17 100644 --- a/.github/workflows/deploy-pr-checks.yml +++ b/.github/workflows/deploy-pr-checks.yml @@ -112,7 +112,7 @@ jobs: " > pr_preview.md - uses: tibdex/github-app-token@v1 id: checks_token - with: + with: app_id: 396440 #osm-americana checks app private_key: ${{ secrets.CHECKS_WRITER_SECRET }} - name: Print Preview Links to GitHub Checks diff --git a/.github/workflows/test-build-mac.yml b/.github/workflows/test-build-mac.yml index dbb8781e2..48b473012 100644 --- a/.github/workflows/test-build-mac.yml +++ b/.github/workflows/test-build-mac.yml @@ -10,9 +10,9 @@ jobs: steps: - name: Checkout 🛎️ uses: actions/checkout@v3 -# Node v18.17.0, introduced July 18, 2023, introduces an error in unicode processing that breaks test cases on Ubuntu. -# See PR #905 and #908 for more details. -# If this bug is resolved in node, these lines can revert to 18.x rather than 18.16.0. + # Node v18.17.0, introduced July 18, 2023, introduces an error in unicode processing that breaks test cases on Ubuntu. + # See PR #905 and #908 for more details. + # If this bug is resolved in node, these lines can revert to 18.x rather than 18.16.0. - name: Use Node.js 18.16.1 uses: actions/setup-node@v3 with: diff --git a/.github/workflows/test-build-ubuntu.yml b/.github/workflows/test-build-ubuntu.yml index c0515a215..90656f40b 100644 --- a/.github/workflows/test-build-ubuntu.yml +++ b/.github/workflows/test-build-ubuntu.yml @@ -10,9 +10,9 @@ jobs: steps: - name: Checkout 🛎️ uses: actions/checkout@v3 -# Node v18.17.0, introduced July 18, 2023, introduces an error in unicode processing that breaks test cases on Ubuntu. -# See PR #905 and #908 for more details. -# If this bug is resolved in node, these lines can revert to 18.x rather than 18.16.1. + # Node v18.17.0, introduced July 18, 2023, introduces an error in unicode processing that breaks test cases on Ubuntu. + # See PR #905 and #908 for more details. + # If this bug is resolved in node, these lines can revert to 18.x rather than 18.16.1. - name: Use Node.js 18.16.1 uses: actions/setup-node@v3 with: From e8addda1c181070f1ef9079180a6962a91a28c51 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Tue, 3 Oct 2023 20:28:35 -0400 Subject: [PATCH 25/25] Update package-lock --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dbab6f75c..6220c67ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1981,9 +1981,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*"