From a1950e5b65217fcb6b2b9c929ba91852eef4ce35 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 3 Jul 2026 18:37:05 +0200 Subject: [PATCH] fix: correct sprite font vertical offset and use avg char width fallback --- public/assets/fonts/cache/habbo-at.gif | Bin 0 -> 156 bytes public/assets/fonts/cache/habbo-ato.gif | Bin 0 -> 191 bytes .../media/logo/logo-1783095831701-q7stvw.png | Bin 0 -> 1307 bytes src/components/public/logo-generator.tsx | 4 +-- src/components/public/sprite-font.ts | 25 ++++++++++-------- 5 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 public/assets/fonts/cache/habbo-at.gif create mode 100644 public/assets/fonts/cache/habbo-ato.gif create mode 100644 public/assets/images/media/logo/logo-1783095831701-q7stvw.png diff --git a/public/assets/fonts/cache/habbo-at.gif b/public/assets/fonts/cache/habbo-at.gif new file mode 100644 index 0000000000000000000000000000000000000000..d624ae413c734fc68723512aabb6b28bc5edb6d9 GIT binary patch literal 156 zcmZ?wbhEHblwuHNn8?7u@E?d7{-0y`pUj~6lZ6q)(*Y46c?PCjmL|#Ny==`hLKbwr zH8MK?F7hy2+EU9N_fV~zNqV}{mp57kJFZ>6@yw>JH5;e;Wk}olors$#EYv$mdU3jW z>8b_p&p%{tHL>59)i=jA|M30yw=VwBJT-w?uv+@=!8x^&JHwAMUOx~J(LQ6vj0gq> FYXH;xJr@7~ literal 0 HcmV?d00001 diff --git a/public/assets/fonts/cache/habbo-ato.gif b/public/assets/fonts/cache/habbo-ato.gif new file mode 100644 index 0000000000000000000000000000000000000000..be511c2bfb7d73fcf5f13f671ab14eaf698dc819 GIT binary patch literal 191 zcmZ?wbhEHbRACThn8?7u@E?d7{-0y`pUj~6lZ6q)(*Y46c?PBlEKQQj-)1K9EQ)Ge zcTC#bxpa<1S$9a@%58BRuXc6LxpuhnNSMK$b^Cwcb=bwlpW)IsX|JnqM)kWYXHDa% zBLQ=j)=o^9D6HAHZF7F!oout*t8Oo9<~}>tc1x2teHavBh rTK3>a*=3QHFDfiqf_9cFbFy{Ll-g^Mt zp1HRf;t?}M+#0ncry7Pr%e0K%COrO3$zzR%;n2p`dVaIDrT*#6(=puYv#8Tb-)X%% z2-yD-jZlTTS?qJ1Bkr^&?c4*o#>A;sai_+da-`gNuKUzhd~vRJ6xwW0R5 zqM+@(UiTo}_yb5`&ThU89DfiuIX_NhO}Z?|KuN>fl{RLLKK|;oO@6hwI&GVMISQ?N z5Efqo-gv7DEoSR)^}zKP!+1UPNhfU*3R7uLJ`8D2hCo9nJ}KgJsJ1>e#?Z>0{Fc@? zM%|{G^3}Gkdk~c1!V(Zev5VWl=3lz%hO1|R(~sgR)u)M3TAvzyv|5bx9JM|U?VEI6 z_hl%pdl1e&3w-on6UOrlqw3*(j0kJR<4(MMZaPM>}0qb+&T zJzKAP5I%n&NFmX;e*(`x258jWgLGbe=-9?LK#ZaD51lXG7xUe=MoHT?M-$II2z8>% z&j8!}ZoV{M$H?RBp91Hf%;S9{+rQFF1m#=*Mg4d<|H?4A&AYN4)^i`N>eazL2x}h$ zDJ;RIr-57l+AJSa-7WqTkS1sLi(Gmeh@ouqwf3{F#)0Z}&8&Sft?~$(xAELZs~TqM_6f29Otd?!&oec#d81${h^skODZ2jR7|Kne-$ z7lTI4+hF7Oz}jGM;AOuiZ~ZbHy}FO*qtLnsp>CVb@ePALOQ_?F5I=L~&2m=VpZ*>$ z6YrwCeLNSmo%bpngj35v6KoIp1S~vpw0Wwtc$=}Mc60L@PNm#ne z#u$ouh&j3zM`H}dJj5LD`Y@hTeL>rKuXGR=xA>Fm!^eRV^jv%K%gK`YOnq_zWZIZ# zq?*yy)^NAzG2@%x$WJQIH1fw>m9;8nYl{*Lsx_}R9>%k2Y%FMHztdjnAl#sO;f2lc zR#9&jYwL5tDv-1z;=RW~TIPn!gUf@VHMW(IcP3e^()5i5H%*mw4 zH!VHt*{8h`$uzM))5Nh!snAOs*6Z=SSWrzmX&d*{ax{51o;N1iNp*fyihKMxCJqHV zb`W@ty}fsn6002ovPDHLkV1mm2etQ4_ literal 0 HcmV?d00001 diff --git a/src/components/public/logo-generator.tsx b/src/components/public/logo-generator.tsx index bc3f8eb4..62d71fdd 100644 --- a/src/components/public/logo-generator.tsx +++ b/src/components/public/logo-generator.tsx @@ -47,7 +47,7 @@ export default function LogoGenerator() { getFontInfo(styleName) .then((info) => { if (cancelled) return; - const canvas = renderToCanvas(info.chars, info.h, info.sheet, safeText, 12); + const canvas = renderToCanvas(info.chars, info.h, info.top, info.sheet, safeText, info.avgW); if (canvas && canvasRef.current) { canvasRef.current.width = canvas.width; canvasRef.current.height = canvas.height; @@ -108,7 +108,7 @@ export default function LogoGenerator() { for (const font of allFonts) { try { const info = await getFontInfo(font.styleName); - const canvas = renderToCanvas(info.chars, info.h, info.sheet, safeText, 12); + const canvas = renderToCanvas(info.chars, info.h, info.top, info.sheet, safeText, info.avgW); if (!canvas) { fail++; continue; } const blob = await new Promise((r) => canvas.toBlob((b) => r(b), "image/png")); if (blob) folder.file(`${font.styleName}.png`, blob); diff --git a/src/components/public/sprite-font.ts b/src/components/public/sprite-font.ts index 7f3e4e2b..51653ed1 100644 --- a/src/components/public/sprite-font.ts +++ b/src/components/public/sprite-font.ts @@ -6,7 +6,7 @@ interface CharInfo { w: number; } -const fontCache = new Map(); +const fontCache = new Map(); function hexPixel(data: Uint8ClampedArray, x: number, y: number, w: number): number { const i = (y * w + x) * 4; @@ -49,7 +49,7 @@ function scanCharWidths(data: Uint8ClampedArray, sw: number, sh: number): CharIn return chars; } -export async function getFontInfo(font: string): Promise<{ chars: CharInfo[]; h: number; sheet: HTMLImageElement }> { +export async function getFontInfo(font: string): Promise<{ chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }> { const cached = fontCache.get(font); if (cached) return cached; @@ -83,20 +83,22 @@ export async function getFontInfo(font: string): Promise<{ chars: CharInfo[]; h: } const h = bottom - top + 1; - const result = { chars, h, sheet: img }; + const avgW = chars.length > 0 ? Math.round(chars.reduce((s, c) => s + c.w, 0) / chars.length) : 12; + + const result = { chars, h, top, sheet: img, avgW }; fontCache.set(font, result); return result; } -export function measureText(chars: CharInfo[], h: number, text: string, spaceW: number): { totalW: number } { +export function measureText(chars: CharInfo[], text: string, avgW: number): { totalW: number } { const map = new Map(chars.map((c) => [c.char, c])); let totalW = 0; for (const ch of text.toLowerCase()) { if (ch === " ") { - totalW += spaceW; + totalW += avgW; } else { const info = map.get(ch); - totalW += info?.w ?? spaceW; + totalW += info?.w ?? avgW; } } return { totalW }; @@ -105,9 +107,10 @@ export function measureText(chars: CharInfo[], h: number, text: string, spaceW: export function renderToCanvas( chars: CharInfo[], h: number, + top: number, sheet: HTMLImageElement, text: string, - spaceW: number, + avgW: number, dpr = 1, ): HTMLCanvasElement | null { const map = new Map(chars.map((c) => [c.char, c])); @@ -115,7 +118,7 @@ export function renderToCanvas( let totalW = 0; for (const ch of safeText) { - totalW += ch === " " ? spaceW : (map.get(ch)?.w ?? spaceW); + totalW += ch === " " ? avgW : (map.get(ch)?.w ?? avgW); } if (totalW === 0) return null; @@ -130,14 +133,14 @@ export function renderToCanvas( let x = 0; for (const ch of safeText) { if (ch === " ") { - x += spaceW; + x += avgW; } else { const info = map.get(ch); if (info) { - ctx.drawImage(sheet, info.x, 0, info.w, sheet.naturalHeight, x, 0, info.w, h); + ctx.drawImage(sheet, info.x, top, info.w, h, x, 0, info.w, h); x += info.w; } else { - x += spaceW; + x += avgW; } } }