// Stroked vector font for Geometry Wars — the glyph table is the generic // 4x6-cell set from TempestVectorFont.js / DefenderVectorFont.js (copied, per // this repo's convention), extended with a few symbols, plus italic skew, // left/right alignment, letter spacing and a separate glow colour so titles // and HUD numbers can read as neon tubes. const GLYPH_W = 4; const GLYPH_H = 6; const GLYPH_GAP = 1.2; const SPACE_W = 2.6; const GLYPHS = { A: [[[0, 6], [2, 0], [4, 6]], [[1, 3.4], [3, 3.4]]], B: [[[0, 0], [0, 6]], [[0, 0], [3, 0], [4, 1], [4, 2], [3, 2.8], [0, 2.8]], [[3, 2.8], [4, 3.6], [4, 5], [3, 6], [0, 6]]], C: [[[3.6, 1], [1.2, 0], [0, 1.6], [0, 4.4], [1.2, 6], [3.6, 5]]], D: [[[0, 0], [2.2, 0], [4, 1.6], [4, 4.4], [2.2, 6], [0, 6], [0, 0]]], E: [[[3.4, 0], [0, 0], [0, 6], [3.4, 6]], [[0, 3], [2.4, 3]]], F: [[[3.4, 0], [0, 0], [0, 6]], [[0, 3], [2.4, 3]]], G: [[[3.6, 1], [1.2, 0], [0, 1.6], [0, 4.4], [1.2, 6], [3, 6], [4, 4.6], [4, 3.2], [2.2, 3.2]]], H: [[[0, 0], [0, 6]], [[4, 0], [4, 6]], [[0, 3], [4, 3]]], I: [[[1, 0], [3, 0]], [[2, 0], [2, 6]], [[1, 6], [3, 6]]], J: [[[4, 0], [4, 5], [3, 6], [1, 6], [0, 5]]], K: [[[0, 0], [0, 6]], [[4, 0], [0, 3.2]], [[1.4, 2.2], [4, 6]]], L: [[[0, 0], [0, 6], [3.4, 6]]], M: [[[0, 6], [0, 0], [2, 2.6], [4, 0], [4, 6]]], N: [[[0, 6], [0, 0], [4, 6], [4, 0]]], O: [[[1.2, 0], [2.8, 0], [4, 1.6], [4, 4.4], [2.8, 6], [1.2, 6], [0, 4.4], [0, 1.6], [1.2, 0]]], P: [[[0, 6], [0, 0], [3, 0], [4, 1.2], [3, 2.6], [0, 2.6]]], Q: [[[1.2, 0], [2.8, 0], [4, 1.6], [4, 4.4], [2.8, 6], [1.2, 6], [0, 4.4], [0, 1.6], [1.2, 0]], [[2.5, 4.4], [4, 6]]], R: [[[0, 6], [0, 0], [3, 0], [4, 1.2], [3, 2.4], [0, 2.4]], [[1.6, 2.4], [4, 6]]], S: [[[4, 1], [3, 0], [1, 0], [0, 1], [0, 2], [1, 2.8], [3, 3.2], [4, 4], [4, 5], [3, 6], [1, 6], [0, 5]]], T: [[[0, 0], [4, 0]], [[2, 0], [2, 6]]], U: [[[0, 0], [0, 5], [1, 6], [3, 6], [4, 5], [4, 0]]], V: [[[0, 0], [2, 6], [4, 0]]], W: [[[0, 0], [0.8, 6], [2, 3.2], [3.2, 6], [4, 0]]], X: [[[0, 0], [4, 6]], [[4, 0], [0, 6]]], Y: [[[0, 0], [2, 2.8], [4, 0]], [[2, 2.8], [2, 6]]], Z: [[[0, 0], [4, 0], [0, 6], [4, 6]]], 0: [[[1.2, 0], [2.8, 0], [4, 1.6], [4, 4.4], [2.8, 6], [1.2, 6], [0, 4.4], [0, 1.6], [1.2, 0]]], 1: [[[1, 1], [2, 0], [2, 6]], [[1, 6], [3, 6]]], 2: [[[0, 1], [1, 0], [3, 0], [4, 1], [4, 2.4], [0, 6], [4, 6]]], 3: [[[0, 0], [4, 0], [2.4, 2.4], [4, 3.4], [4, 5], [3, 6], [1, 6], [0, 5]]], 4: [[[3, 6], [3, 0], [0, 4], [4, 4]]], 5: [[[4, 0], [0, 0], [0, 2.6], [3, 2.6], [4, 3.6], [4, 5], [3, 6], [1, 6], [0, 5]]], 6: [[[3.5, 0], [1, 0], [0, 1.5], [0, 5], [1, 6], [3, 6], [4, 5], [4, 3.6], [3, 2.6], [0, 2.6]]], 7: [[[0, 0], [4, 0], [1.5, 6]]], 8: [[[1, 0], [3, 0], [4, 1], [4, 2], [3, 2.8], [1, 2.8], [0, 2], [0, 1], [1, 0]], [[3, 2.8], [4, 3.6], [4, 5], [3, 6], [1, 6], [0, 5], [0, 3.6], [1, 2.8]]], 9: [[[0.5, 6], [3, 6], [4, 4.5], [4, 1], [3, 0], [1, 0], [0, 1], [0, 2.4], [1, 3.4], [4, 3.4]]], '-': [[[0.8, 3], [3.2, 3]]], '+': [[[0.6, 3], [3.4, 3]], [[2, 1.6], [2, 4.4]]], '.': [[[1.8, 5.4], [2.2, 5.4], [2.2, 6], [1.8, 6], [1.8, 5.4]]], ',': [[[2.2, 5.2], [2.2, 6], [1.6, 7]]], ':': [[[1.8, 1.4], [2.2, 1.4], [2.2, 2], [1.8, 2], [1.8, 1.4]], [[1.8, 5.4], [2.2, 5.4], [2.2, 6], [1.8, 6], [1.8, 5.4]]], '!': [[[2, 0], [2, 4]], [[1.8, 5.4], [2.2, 5.4], [2.2, 6], [1.8, 6], [1.8, 5.4]]], '?': [[[0, 1], [1, 0], [3, 0], [4, 1], [4, 2], [2, 3.4], [2, 4.2]], [[1.8, 5.4], [2.2, 5.4], [2.2, 6], [1.8, 6], [1.8, 5.4]]], '/': [[[3.6, 0], [0.4, 6]]], "'": [[[2, 0], [2, 1.6]]], '%': [[[3.6, 0], [0.4, 6]], [[0.4, 0.4], [1.4, 0.4], [1.4, 1.6], [0.4, 1.6], [0.4, 0.4]], [[2.6, 4.4], [3.6, 4.4], [3.6, 5.6], [2.6, 5.6], [2.6, 4.4]]], '×': [[[0.6, 1.6], [3.4, 4.6]], [[3.4, 1.6], [0.6, 4.6]]], }; function advance(ch, spacing) { return ch === ' ' ? SPACE_W + spacing : GLYPH_W + GLYPH_GAP + spacing; } export function measureVectorText(text, scale, spacing = 0) { let w = 0; for (const ch of String(text).toUpperCase()) w += advance(ch, spacing) * scale; return Math.max(0, w - (GLYPH_GAP + spacing) * scale); } export function vectorTextHeight(scale) { return GLYPH_H * scale; } function strokeGlyphs(g, text, x0, y0, scale, skew, spacing) { let x = x0; for (const ch of text) { const strokes = GLYPHS[ch]; if (strokes) { for (const poly of strokes) { g.beginPath(); for (let i = 0; i < poly.length; i += 1) { const [ux, uy] = poly[i]; const px = x + (ux + (GLYPH_H - uy) * skew) * scale; const py = y0 + uy * scale; if (i === 0) g.moveTo(px, py); else g.lineTo(px, py); } g.strokePath(); } } x += advance(ch, spacing) * scale; } } // (x, y) is the anchor: centre/left/right edge per `align`, vertical centre. export function drawVectorText(g, text, x, y, scale, color, options = {}) { const { lineWidth = 3, glowWidth = 9, glowAlpha = 0.2, alpha = 1, align = 'center', skew = 0, spacing = 0, glowColor = color, glow = true, } = options; const str = String(text).toUpperCase(); const w = measureVectorText(str, scale, spacing); let x0 = x - w / 2; if (align === 'left') x0 = x; else if (align === 'right') x0 = x - w; const y0 = y - (GLYPH_H * scale) / 2; if (glow && glowWidth > 0 && glowAlpha > 0) { g.lineStyle(glowWidth, glowColor, glowAlpha * alpha); strokeGlyphs(g, str, x0, y0, scale, skew, spacing); } g.lineStyle(lineWidth, color, alpha); strokeGlyphs(g, str, x0, y0, scale, skew, spacing); return w; } export function formatScore(n) { return Math.floor(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); }