fix: correct sprite font vertical offset and use avg char width fallback

This commit is contained in:
openhands committed 2026-07-03 18:37:05 +02:00
1 parent 77ae4838c0
commit a1950e5b65
5 files changed
+16 -13

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+2 -2
View File
@@ -47,7 +47,7 @@ export default function LogoGenerator() {
getFontInfo(styleName) getFontInfo(styleName)
.then((info) => { .then((info) => {
if (cancelled) return; 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) { if (canvas && canvasRef.current) {
canvasRef.current.width = canvas.width; canvasRef.current.width = canvas.width;
canvasRef.current.height = canvas.height; canvasRef.current.height = canvas.height;
@@ -108,7 +108,7 @@ export default function LogoGenerator() {
for (const font of allFonts) { for (const font of allFonts) {
try { try {
const info = await getFontInfo(font.styleName); 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; } if (!canvas) { fail++; continue; }
const blob = await new Promise<Blob | null>((r) => canvas.toBlob((b) => r(b), "image/png")); const blob = await new Promise<Blob | null>((r) => canvas.toBlob((b) => r(b), "image/png"));
if (blob) folder.file(`${font.styleName}.png`, blob); if (blob) folder.file(`${font.styleName}.png`, blob);
+14 -11
View File
@@ -6,7 +6,7 @@ interface CharInfo {
w: number; w: number;
} }
const fontCache = new Map<string, { chars: CharInfo[]; h: number; sheet: HTMLImageElement }>(); const fontCache = new Map<string, { chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }>();
function hexPixel(data: Uint8ClampedArray, x: number, y: number, w: number): number { function hexPixel(data: Uint8ClampedArray, x: number, y: number, w: number): number {
const i = (y * w + x) * 4; const i = (y * w + x) * 4;
@@ -49,7 +49,7 @@ function scanCharWidths(data: Uint8ClampedArray, sw: number, sh: number): CharIn
return chars; 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); const cached = fontCache.get(font);
if (cached) return cached; if (cached) return cached;
@@ -83,20 +83,22 @@ export async function getFontInfo(font: string): Promise<{ chars: CharInfo[]; h:
} }
const h = bottom - top + 1; 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); fontCache.set(font, result);
return 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])); const map = new Map(chars.map((c) => [c.char, c]));
let totalW = 0; let totalW = 0;
for (const ch of text.toLowerCase()) { for (const ch of text.toLowerCase()) {
if (ch === " ") { if (ch === " ") {
totalW += spaceW; totalW += avgW;
} else { } else {
const info = map.get(ch); const info = map.get(ch);
totalW += info?.w ?? spaceW; totalW += info?.w ?? avgW;
} }
} }
return { totalW }; return { totalW };
@@ -105,9 +107,10 @@ export function measureText(chars: CharInfo[], h: number, text: string, spaceW:
export function renderToCanvas( export function renderToCanvas(
chars: CharInfo[], chars: CharInfo[],
h: number, h: number,
top: number,
sheet: HTMLImageElement, sheet: HTMLImageElement,
text: string, text: string,
spaceW: number, avgW: number,
dpr = 1, dpr = 1,
): HTMLCanvasElement | null { ): HTMLCanvasElement | null {
const map = new Map(chars.map((c) => [c.char, c])); const map = new Map(chars.map((c) => [c.char, c]));
@@ -115,7 +118,7 @@ export function renderToCanvas(
let totalW = 0; let totalW = 0;
for (const ch of safeText) { 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; if (totalW === 0) return null;
@@ -130,14 +133,14 @@ export function renderToCanvas(
let x = 0; let x = 0;
for (const ch of safeText) { for (const ch of safeText) {
if (ch === " ") { if (ch === " ") {
x += spaceW; x += avgW;
} else { } else {
const info = map.get(ch); const info = map.get(ch);
if (info) { 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; x += info.w;
} else { } else {
x += spaceW; x += avgW;
} }
} }
} }