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)
.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<Blob | null>((r) => canvas.toBlob((b) => r(b), "image/png"));
if (blob) folder.file(`${font.styleName}.png`, blob);
+14 -11
View File
@@ -6,7 +6,7 @@ interface CharInfo {
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 {
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;
}
}
}