Skip to content

Commit

Permalink
Merge pull request #941 from ZeLonewolf/zlw-color-banners
Browse files Browse the repository at this point in the history
Per-shield banner colors
  • Loading branch information
ZeLonewolf authored Oct 6, 2023
2 parents 6e287a4 + 2733b53 commit 38268fb
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 557 deletions.
4 changes: 4 additions & 0 deletions shieldlib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ You should create one definition entry for each network. The entry key must matc
}
},
"banners": ["ALT"],
"bannerTextColor": "#000",
"bannerTextHaloColor": "#FFF",
"textLayout": {
"constraintFunc": "roundedRect",
"options": {
Expand Down Expand Up @@ -173,6 +175,8 @@ You should create one definition entry for each network. The entry key must matc

![Bannered routes near Downington, PA](https://wiki.openstreetmap.org/w/images/f/f8/Downington_bannered_routes_Americana.png)

- **`bannerTextColor`**: specify the color of the banner text.
- **`bannerTextHaloColor`**: specify the color of the banner knockout halo.
- **`textLayout`**: specify how text should be inscribed within the padded bounds of the shield. The text will be drawn at the maximum size allowed by this constraint. See the [text layout functions](#text-layout-functions) section for text layout options.
- **`colorLighten`**: specify that the shield artwork should be lightened (multiplied) by the specified color. This means that black areas will be recolor with this color and white areas will remain the same. Alpha values will remain unmodified.
- **`colorDarken`**: specify that the shield artwork should be darkened by the specified color. This means that white areas will be recolor with this color and black areas will remain the same. Alpha values will remain unmodified.
Expand Down
8 changes: 4 additions & 4 deletions shieldlib/src/custom_shields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function paBelt(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
params: ShapeBlankParams
) {
): number {
ShieldDraw.roundedRectangle(r, ctx, {
fillColor: "white",
strokeColor: "black",
Expand Down Expand Up @@ -38,15 +38,15 @@ export function paBelt(

ctx.lineWidth = lineWidth;
ctx.stroke();
return ctx;
return 20;
}

// Special case for Branson color-coded routes
export function bransonRoute(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
params: ShapeBlankParams
) {
): number {
ShieldDraw.roundedRectangle(r, ctx, {
fillColor: "#006747",
strokeColor: "white",
Expand All @@ -73,7 +73,7 @@ export function bransonRoute(

ctx.lineWidth = lineWidth;
ctx.stroke();
return ctx;
return 20;
}

export function loadCustomShields() {
Expand Down
38 changes: 9 additions & 29 deletions shieldlib/src/shield.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
"use strict";

import * as ShieldText from "./shield_text.mjs";
import * as ShieldDraw from "./shield_canvas_draw";
import * as ShieldText from "./shield_text.js";
import * as ShieldDraw from "./shield_canvas_draw.js";
import * as Gfx from "./screen_gfx.js";

function drawBannerPart(r, ctx, shieldDef, drawFunc) {
if (shieldDef == null || typeof shieldDef.banners == "undefined") {
return ctx; //Unadorned shield
}

for (var i = 0; i < shieldDef.banners.length; i++) {
drawFunc(r, ctx, shieldDef.banners[i], i);
}

return ctx;
}
import {
drawBanners,
drawBannerHalos,
getBannerCount,
} from "./shield_banner.js";

function compoundShieldSize(r, dimension, bannerCount) {
return {
Expand All @@ -29,19 +22,6 @@ export function isValidRef(ref) {
return ref !== null && ref.length !== 0 && ref.length <= 6;
}

/**
* Get the number of banner placards associated with this shield
*
* @param {*} shield - Shield definition
* @returns the number of banner placards that need to be drawn
*/
function getBannerCount(shield) {
if (shield == null || typeof shield.banners == "undefined") {
return 0; //Unadorned shield
}
return shield.banners.length;
}

/**
* Retrieve the shield blank that goes with a particular route. If there are
* multiple shields for a route (different widths), it picks the best shield.
Expand Down Expand Up @@ -359,7 +339,7 @@ export function generateShieldCtx(r, routeDef) {
}

// Add the halo around modifier plaque text
drawBannerPart(r, ctx, shieldDef, ShieldText.drawBannerHaloText);
drawBannerHalos(r, ctx, shieldDef);

if (sourceSprite == null) {
drawShield(r, ctx, shieldDef, routeDef);
Expand All @@ -378,7 +358,7 @@ export function generateShieldCtx(r, routeDef) {
drawShieldText(r, ctx, shieldDef, routeDef);

// Add modifier plaque text
drawBannerPart(r, ctx, shieldDef, ShieldText.drawBannerText);
drawBanners(r, ctx, shieldDef);

return ctx;
}
179 changes: 179 additions & 0 deletions shieldlib/src/shield_banner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { shieldFont } from "./screen_gfx";
import { ShieldRenderingContext } from "./shield_renderer";
import { TextPlacement, layoutShieldText } from "./shield_text";
import { Dimension, ShieldDefinition, TextLayout } from "./types";

let bannerLayout: TextLayout = {
constraintFunc: "rect",
};

/**
* Add modifier plaque text
*
* @param r - Shield rendering context
* @param ctx - Canvas drawing context
* @param shieldDef - Shield definition
*/
export function drawBanners(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
shieldDef: ShieldDefinition
) {
if (shieldDef.bannerTextColor) {
ctx.fillStyle = shieldDef.bannerTextColor;
} else {
ctx.fillStyle = r.options.bannerTextColor;
}
drawBannerPart(r, ctx, shieldDef, drawBannerText);
}

/**
* Add the halo around modifier plaque text
*
* @param r - Shield rendering context
* @param ctx - Canvas drawing context
* @param shieldDef - Shield definition
*/
export function drawBannerHalos(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
shieldDef: ShieldDefinition
) {
if (shieldDef.bannerTextHaloColor) {
ctx.strokeStyle = ctx.shadowColor = shieldDef.bannerTextHaloColor;
} else {
ctx.strokeStyle = ctx.shadowColor = r.options.bannerTextHaloColor;
}
drawBannerPart(r, ctx, shieldDef, drawBannerHaloText);
}

type BannerDrawComponentFunction = (
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
text: string,
bannerIndex: number
) => void;

function drawBannerPart(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
shieldDef: ShieldDefinition,
drawFunc: BannerDrawComponentFunction
): void {
if (shieldDef == null || typeof shieldDef.banners == "undefined") {
return; //Unadorned shield
}

for (var i = 0; i < shieldDef.banners.length; i++) {
drawFunc(r, ctx, shieldDef.banners[i], i);
}
}

/**
* Get the number of banner placards associated with this shield
*
* @param shield - Shield definition
* @returns the number of banner placards that need to be drawn
*/
export function getBannerCount(shield: ShieldDefinition): number {
if (shield == null || typeof shield.banners == "undefined") {
return 0; //Unadorned shield
}
return shield.banners.length;
}

/**
* Draw text on a modifier plate above a shield
*
* @param {*} r - rendering context
* @param {*} ctx - graphics context to draw to
* @param {*} text - text to draw
* @param {*} bannerIndex - plate position to draw, 0=top, incrementing
*/
export function drawBannerText(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
text: string,
bannerIndex: number
): void {
drawBannerTextComponent(r, ctx, text, bannerIndex, true);
}

/**
* Draw drop shadow for text on a modifier plate above a shield
*
* @param {*} r - rendering context
* @param {*} ctx - graphics context to draw to
* @param {*} text - text to draw
* @param {*} bannerIndex - plate position to draw, 0=top, incrementing
*/
export function drawBannerHaloText(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
text: string,
bannerIndex: number
): void {
drawBannerTextComponent(r, ctx, text, bannerIndex, false);
}

/**
* Banners are composed of two components: text on top, and a shadow beneath.
*
* @param {*} r - rendering context
* @param {*} ctx - graphics context to draw to
* @param {*} text - text to draw
* @param {*} bannerIndex - plate position to draw, 0=top, incrementing
* @param {*} textComponent - if true, draw the text. If false, draw the halo
*/
function drawBannerTextComponent(
r: ShieldRenderingContext,
ctx: CanvasRenderingContext2D,
text: string,
bannerIndex: number,
textComponent: boolean
): void {
const bannerPadding = {
top: r.options.bannerPadding,
bottom: 0,
left: 0,
right: 0,
};

let bannerBounds: Dimension = {
width: ctx.canvas.width,
height: r.px(r.options.bannerHeight - r.options.bannerPadding),
};

let textLayout: TextPlacement = layoutShieldText(
r,
text,
bannerPadding,
bannerBounds,
bannerLayout
);

ctx.font = shieldFont(textLayout.fontPx, r.options.shieldFont);
ctx.textBaseline = "top";
ctx.textAlign = "center";

if (textComponent) {
ctx.fillText(
text,
textLayout.xBaseline,
textLayout.yBaseline +
bannerIndex * r.px(r.options.bannerHeight - r.options.bannerPadding)
);
} else {
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;
}
}
Loading

0 comments on commit 38268fb

Please sign in to comment.