118 lines
5.5 KiB
JavaScript
118 lines
5.5 KiB
JavaScript
// 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, ',');
|
||
}
|