import { describe, expect, it } from "vitest"; import { decodeEmbedding, extractCollectionFace, inspectCollection, inspectDirectory, inspectWoff2Directory, inspectWoff2Header, } from "../../src/core/sfnt"; describe("SFNT directory validation", () => { it("inventories bounded TrueType tables", () => { const report = inspectDirectory(sfnt(["head", "cmap", "OS/2"])); expect(report.container).toBe("truetype"); expect(report.tables.map((table) => table.tag)).toEqual([ "head", "cmap", "OS/2", ]); expect(report.tables[2]?.description).toContain("embedding"); }); it("rejects unsupported and out-of-bounds containers before library parsing", () => { expect(() => inspectDirectory(signature("wOF2"))).toThrow(/WOFF2/u); const invalid = sfnt(["head"]), view = new DataView(invalid); view.setUint32(12 + 8, invalid.byteLength - 2, false); view.setUint32(12 + 12, 12, false); expect(() => inspectDirectory(invalid)).toThrow(/file boundary/u); }); it("validates WOFF2 container sizes before decompression", () => { const buffer = new ArrayBuffer(52), bytes = new Uint8Array(buffer), view = new DataView(buffer); bytes.set(new TextEncoder().encode("wOF2"), 0); bytes.set(new TextEncoder().encode("\u0000\u0001\u0000\u0000"), 4); view.setUint32(8, buffer.byteLength, false); view.setUint16(12, 1, false); view.setUint32(16, 32, false); view.setUint32(20, 4, false); expect(inspectWoff2Header(buffer)).toEqual({ declaredSize: 52, expandedSize: 32, count: 1, }); view.setUint32(16, 70 * 1024 * 1024, false); expect(() => inspectWoff2Header(buffer)).toThrow(/64 MiB/u); }); it("rejects reserved WOFF2 transform versions before decoding", () => { expect(() => inspectWoff2Directory(woff2Table(5, 1))).toThrow( /reserved transform version/u, ); expect(() => inspectWoff2Directory(woff2Table(3, 2))).toThrow( /invalid transform version/u, ); expect(inspectWoff2Directory(woff2Table(5, 0)).tables[0]).toMatchObject({ tag: "name", storedLength: 4, }); expect(inspectWoff2Directory(woff2Table(3, 1)).tables[0]).toMatchObject({ tag: "hmtx", storedLength: 4, }); }); it("validates TTC face offsets and reconstructs a standalone selected face", () => { const source = ttc([ sfnt(["head", "cmap"]), sfnt(["head", "name", "CFF "]), ]), collection = inspectCollection(source); expect(collection).toMatchObject({ version: "1.0", hasDigitalSignature: false, }); expect(collection.faces.map((face) => face.directory.flavor)).toEqual([ "TrueType outlines", "TrueType outlines", ]); const extracted = extractCollectionFace(source, 1), report = inspectDirectory(extracted); expect(report.tables.map((table) => table.tag)).toEqual([ "head", "name", "CFF ", ]); expect( report.tables.every((table) => table.offset < extracted.byteLength), ).toBe(true); }); it("rejects malformed TTC versions, counts, duplicate and unaligned offsets", () => { const invalidVersion = ttc([sfnt(["head"])]), invalidCount = ttc([sfnt(["head"])]), duplicate = ttc([sfnt(["head"]), sfnt(["head"])]), unaligned = ttc([sfnt(["head"])]); new DataView(invalidVersion).setUint32(4, 0x0003_0000, false); expect(() => inspectCollection(invalidVersion)).toThrow(/version/u); new DataView(invalidCount).setUint32(8, 65, false); expect(() => inspectCollection(invalidCount)).toThrow(/64-face/u); const duplicateView = new DataView(duplicate); duplicateView.setUint32(16, duplicateView.getUint32(12, false), false); expect(() => inspectCollection(duplicate)).toThrow(/duplicate/u); new DataView(unaligned).setUint32(12, 17, false); expect(() => inspectCollection(unaligned)).toThrow(/aligned/u); }); it("validates the optional TTC 2.0 DSIG boundary", () => { const source = ttc([sfnt(["head"])], 0x0002_0000), headerEnd = 12 + 4; expect(inspectCollection(source).version).toBe("2.0"); const view = new DataView(source); view.setUint32(headerEnd, 0x4453_4947, false); expect(() => inspectCollection(source)).toThrow(/incomplete/u); }); }); describe("embedding flags", () => { it("decodes and enforces subsetting restrictions", () => { expect(decodeEmbedding(0).subsetAllowed).toBe(true); expect(decodeEmbedding(0x0002).level).toBe("restricted"); expect(decodeEmbedding(0x0100)).toMatchObject({ noSubsetting: true, subsetAllowed: false, }); expect(decodeEmbedding(0x0208)).toMatchObject({ level: "editable", bitmapOnly: true, subsetAllowed: false, }); }); }); function signature(value: string) { const bytes = new Uint8Array(4); [...value].forEach( (character, index) => (bytes[index] = character.charCodeAt(0)), ); return bytes.buffer; } function woff2Table(tagIndex: number, transformVersion: number) { const glyfOrLoca = tagIndex === 10 || tagIndex === 11, declaresTransform = glyfOrLoca ? transformVersion === 0 : transformVersion !== 0, directoryBytes = declaresTransform ? 3 : 2, buffer = new ArrayBuffer(48 + directoryBytes + 1), bytes = new Uint8Array(buffer), view = new DataView(buffer); bytes.set(new TextEncoder().encode("wOF2"), 0); view.setUint32(4, 0x0001_0000, false); view.setUint32(8, buffer.byteLength, false); view.setUint16(12, 1, false); view.setUint32(16, 32, false); view.setUint32(20, 1, false); bytes[48] = (transformVersion << 6) | tagIndex; bytes[49] = 4; if (declaresTransform) bytes[50] = 4; return buffer; } function sfnt(tags: string[]) { const directoryBytes = 12 + tags.length * 16, tableBytes = 12, buffer = new ArrayBuffer(directoryBytes + tags.length * tableBytes), view = new DataView(buffer); view.setUint32(0, 0x0001_0000, false); view.setUint16(4, tags.length, false); tags.forEach((tag, index) => { const base = 12 + index * 16; [...tag].forEach((character, offset) => view.setUint8(base + offset, character.charCodeAt(0)), ); view.setUint32(base + 4, index + 1, false); view.setUint32(base + 8, directoryBytes + index * tableBytes, false); view.setUint32(base + 12, tableBytes, false); }); return buffer; } function ttc(fonts: ArrayBuffer[], version = 0x0001_0000) { const extra = version === 0x0002_0000 ? 12 : 0, headerSize = 12 + fonts.length * 4 + extra, offsets: number[] = []; let size = align4(headerSize); for (const font of fonts) { offsets.push(size); size += align4(font.byteLength); } const buffer = new ArrayBuffer(size), output = new Uint8Array(buffer), view = new DataView(buffer); output.set(new TextEncoder().encode("ttcf"), 0); view.setUint32(4, version, false); view.setUint32(8, fonts.length, false); fonts.forEach((font, index) => { const faceOffset = offsets[index]!, source = new Uint8Array(font), sourceView = new DataView(font); view.setUint32(12 + index * 4, faceOffset, false); output.set(source, faceOffset); const tableCount = sourceView.getUint16(4, false); for (let tableIndex = 0; tableIndex < tableCount; tableIndex += 1) { const recordOffset = faceOffset + 12 + tableIndex * 16, sourceOffset = sourceView.getUint32(12 + tableIndex * 16 + 8, false); view.setUint32(recordOffset + 8, faceOffset + sourceOffset, false); } }); return buffer; } function align4(value: number) { return (value + 3) & ~3; }