import { BinaryFontPacker } from "@suaveplan/font-compress"; import type { TypefaceFont } from "@suaveplan/font-convert"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { BinaryFontUnpacker } from "../src/index.js"; function makeFont(overrides: Partial = {}): TypefaceFont { return { familyName: "TestFont", cssFontWeight: "400", cssFontStyle: "normal", reversed: false, ascender: 800, descender: -200, underlinePosition: -100, underlineThickness: 50, resolution: 1000, boundingBox: { yMin: -200, xMin: 0, yMax: 800, xMax: 1000 }, original_font_information: { source: "test" }, glyphs: { A: { ha: 600, x_min: 10, x_max: 590, o: "m 0 0 l 100 0 z " }, B: { ha: 560, x_min: 20, x_max: 540, o: "m 0 0 b 10 20 30 40 50 60 z ", }, Q: { ha: 700, x_min: 5, x_max: 695, o: "m 0 0 q 50 100 100 0 z " }, }, ...overrides, }; } describe("roundtrip: BinaryFontPacker ↔ BinaryFontUnpacker", () => { it("preserves all scalar font metrics", { timeout: 5000 }, () => { const font = makeFont(); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(result.ascender).toBe(font.ascender); expect(result.descender).toBe(font.descender); expect(result.underlinePosition).toBe(font.underlinePosition); expect(result.underlineThickness).toBe(font.underlineThickness); expect(result.resolution).toBe(font.resolution); }); it("preserves bounding box", { timeout: 5000 }, () => { const font = makeFont(); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(result.boundingBox).toEqual(font.boundingBox); }); it("preserves all glyph outline strings", { timeout: 5000 }, () => { const font = makeFont(); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); for (const [char, glyph] of Object.entries(font.glyphs)) { expect(result.glyphs[char]?.o).toBe(glyph.o); } }); it("preserves reversed=true flag", { timeout: 5000 }, () => { const font = makeFont({ reversed: true }); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(result.reversed).toBe(true); }); it("preserves reversed=false flag", { timeout: 5000 }, () => { const font = makeFont({ reversed: false }); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(result.reversed).toBe(false); }); it("handles empty glyph set", { timeout: 5000 }, () => { const font = makeFont({ glyphs: {} }); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(Object.keys(result.glyphs)).toHaveLength(0); }); it("handles large glyph with all command types", { timeout: 5000 }, () => { const font = makeFont({ glyphs: { X: { ha: 800, x_min: -50, x_max: 850, o: "m 0 0 l 100 200 b -10 -20 30 40 50 60 q 5 -10 15 20 z m 300 400 l 500 400 z ", }, }, }); const result = new BinaryFontUnpacker().unpack( new BinaryFontPacker().pack(font) ); expect(result.glyphs["X"]!.o).toBe(font.glyphs["X"]!.o); }); it("produces a smaller buffer than naive JSON", { timeout: 5000 }, () => { const font = makeFont(); const packed = new BinaryFontPacker().pack(font); const jsonBytes = new TextEncoder().encode( JSON.stringify(font) ).byteLength; expect(packed.byteLength).toBeLessThan(jsonBytes); }); });