Release Font Tools v0.1.0

This commit is contained in:
2026-09-01 14:35:39 +02:00
commit 25ff321f2a
65 changed files with 10590 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { generateFontFace, validateLocalPath } from "../../src/core/css";
import type { FontInspection } from "../../src/core/model";
const inspection: FontInspection = {
fileName: "Variable.woff",
fileSize: 1_000,
container: "woff",
flavor: "WOFF",
names: { family: "Fixture", subfamily: "Regular" },
unitsPerEm: 1_000,
ascender: 800,
descender: -200,
glyphCount: 3,
unicodeCount: 3,
coverageRanges: [{ start: 0x41, end: 0x43 }],
coverageRangesTruncated: false,
coverageBlocks: [],
axes: [{ tag: "wght", name: "Weight", min: 200, default: 400, max: 900 }],
embedding: {
raw: 0,
rawHex: "0x0000",
level: "installable",
label: "Installable embedding",
noSubsetting: false,
bitmapOnly: false,
subsetAllowed: true,
signals: [],
},
tables: [],
expandedSize: 2_000,
warnings: [],
};
describe("safe CSS generation", () => {
it("quotes metadata, emits axes and a complete compact range", () => {
const result = generateFontFace({
family: 'Fixture "Local"',
sourcePath: "./fonts/fixture.woff",
display: "swap",
inspection,
includeUnicodeRange: true,
});
expect(result.css).toContain('font-family: "Fixture \\"Local\\""');
expect(result.css).toContain("font-weight: 200 900");
expect(result.css).toContain("unicode-range: U+0041-0043");
});
it("rejects remote and control-bearing paths", () => {
expect(() => validateLocalPath("https://example.test/font.woff")).toThrow(
/local asset paths/u,
);
expect(() => validateLocalPath("./font\n.woff")).toThrow(/control/u);
});
});
+18
View File
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { previewDocument } from "../../src/core/preview";
describe("sandbox preview document", () => {
it("escapes text and allows no script capability", () => {
const html = previewDocument(
"blob:http://localhost/fixture",
'<script>alert("x")</script>',
42,
1.2,
{ wght: 550 },
);
expect(html).toContain("&lt;script&gt;");
expect(html).not.toContain('<script>alert("x")</script>');
expect(html).toContain("default-src 'none'");
expect(html).toContain("&quot;wght&quot; 550");
});
});
+67
View File
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";
import { decodeEmbedding, inspectDirectory } 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);
});
});
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 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;
}