diff --git a/web/src/components/map/canvasDraw.ts b/web/src/components/map/canvasDraw.ts index 9915893..f2d16f0 100644 --- a/web/src/components/map/canvasDraw.ts +++ b/web/src/components/map/canvasDraw.ts @@ -49,7 +49,7 @@ export function drawOnMap( ctx.fillStyle = "#FFFFFF"; ctx.font = `normal ${props.fontSize * 5}px Daira by LTV grafika`; const iconSize = ctx.measureText(icon); - ctx.fillText(icon, localX + props.boxSize/2 + 10 + iconSize.width/2, localY); + drawOpticallyCenteredGlyph(ctx, icon, localX + props.boxSize/2 + 10 + iconSize.width/2, localY); } }); @@ -81,6 +81,15 @@ export function drawOnMap( ctx.fillText("M/S", windProp.offsetX + boxMiddleX + gustsWidth / 2 - 22, (windProp.offsetY + 805)*windProp.scaleY); } +function drawOpticallyCenteredGlyph(ctx: CanvasRenderingContext2D, glyph: string, centerX: number, centerY: number): void { + const metrics = ctx.measureText(glyph); + ctx.textAlign = "left"; + ctx.textBaseline = "alphabetic"; + const x = centerX + (metrics.actualBoundingBoxLeft - metrics.actualBoundingBoxRight) / 2; + const y = centerY + (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2; + ctx.fillText(glyph, x, y); +} + function drawTitleOverlay(ctx: CanvasRenderingContext2D, title: string, source: string, canvasWidth: number): void { if (!title.trim() && !source.trim()) return; diff --git a/web/src/components/weatherIcons/IconInputs.tsx b/web/src/components/weatherIcons/IconInputs.tsx index 8e26902..b71ef28 100644 --- a/web/src/components/weatherIcons/IconInputs.tsx +++ b/web/src/components/weatherIcons/IconInputs.tsx @@ -1,6 +1,7 @@ import { Component, createSignal, For, Show, Signal } from "solid-js"; import { weatherIcons } from "./iconConsts"; import "../../css/weatherIcons.css"; +import { WeatherGlyph } from "./WeatherGlyph"; export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal>}> = (props) => { const [icons, setIcons] = props.weatherIconsSignal; @@ -10,9 +11,9 @@ export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal const applyAll = () => setIcons(Object.fromEntries(props.cities.map(city => [city, selected()]))); return
-
Weather symbols

Choose one symbol, apply it broadly, then update the exceptions below.

{selected()}
-
{icon => }
+
Weather symbols

Choose one symbol, apply it broadly, then update the exceptions below.

+
{icon => }
City assignmentsUse the selected symbol for each exception.
-
{city =>
{city}{icons()[city]}
}
+
{city =>
{city}{icon => }
}
; }; diff --git a/web/src/components/weatherIcons/WeatherGlyph.tsx b/web/src/components/weatherIcons/WeatherGlyph.tsx new file mode 100644 index 0000000..6cea938 --- /dev/null +++ b/web/src/components/weatherIcons/WeatherGlyph.tsx @@ -0,0 +1,23 @@ +import { Component, createEffect, onMount } from "solid-js"; + +export const WeatherGlyph: Component<{glyph: string; size?: number; tone?: "default" | "inverse"}> = (props) => { + let canvas!: HTMLCanvasElement; + const boxSize = () => props.size ?? 40; + const draw = async () => { + const size = boxSize(); + await document.fonts.load(`${Math.round(size * 0.82)}px "Daira by LTV grafika"`); + canvas.width = size; canvas.height = size; + const ctx = canvas.getContext("2d")!; + ctx.clearRect(0, 0, size, size); + ctx.font = `normal ${Math.round(size * 0.82)}px "Daira by LTV grafika"`; + ctx.fillStyle = props.tone === "inverse" ? "#ffffff" : "#17212d"; + ctx.textAlign = "left"; ctx.textBaseline = "alphabetic"; + const metrics = ctx.measureText(props.glyph); + const x = size / 2 + (metrics.actualBoundingBoxLeft - metrics.actualBoundingBoxRight) / 2; + const y = size / 2 + (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2; + ctx.fillText(props.glyph, x, y); + }; + onMount(draw); + createEffect(() => { props.glyph; props.tone; boxSize(); if (canvas) void draw(); }); + return