Release Font Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 12:18:37 +02:00
parent 25ff321f2a
commit 873cf3c04b
39 changed files with 2431 additions and 181 deletions
+94 -3
View File
@@ -1,6 +1,7 @@
import { expect, test, type Page } from "@playwright/test";
import { readFile } from "node:fs/promises";
import opentype from "opentype.js";
import compressWoff2 from "wawoff2/compress.js";
const ORIGIN = "http://127.0.0.1:4202";
const { Font, Glyph, Path } = opentype;
@@ -82,6 +83,65 @@ test("enforces the no-subsetting fsType bit", async ({ page }) => {
).toBeVisible();
});
test("decompresses and inspects a local WOFF2 font", async ({ page }) => {
await page.goto("/font/");
const compressed = await compressWoff2(fixtureFont(0));
await page.locator('input[type="file"]').setInputFiles({
name: "fixture.woff2",
mimeType: "font/woff2",
buffer: Buffer.from(compressed),
});
await expect(page.locator('p[role="status"]')).toContainText("Inspected");
await expect(page.getByText("woff2", { exact: false }).first()).toBeVisible();
await expect(
page.getByText("Fixture Sans", { exact: true }).first(),
).toBeVisible();
});
test("validates a real TTC container and switches its selected face", async ({
page,
}) => {
const external = await blockExternal(page);
await page.goto("/font/");
await page.locator('input[type="file"]').setInputFiles({
name: "fixture.ttc",
mimeType: "font/collection",
buffer: fixtureCollection([
fixtureFont(0, "Fixture Sans"),
fixtureFont(0, "Fixture Serif"),
]),
});
await expect(page.locator('p[role="status"]')).toContainText(
"collection face 1 of 2",
);
await expect(page.getByLabel("Selected collection face")).toHaveValue("0");
await expect(
page.getByText("Fixture Sans", { exact: true }).first(),
).toBeVisible();
await page.getByLabel("Selected collection face").selectOption("1");
await expect(page.locator('p[role="status"]')).toContainText(
"Selected collection face 2 of 2",
);
await expect(
page.getByText("Fixture Serif", { exact: true }).first(),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Build & download .otf subset" }),
).toBeDisabled();
await expect(
page.getByText(/does not export or subset collections/u),
).toBeVisible();
await expect(page.getByLabel("Generated CSS")).toContainText(
'format("collection")',
);
await expect(page.getByText(/cannot portably identify/u)).toBeVisible();
await expect(page.locator("iframe.font-preview-frame")).toHaveAttribute(
"srcdoc",
/blob:/u,
);
expect(external).toEqual([]);
});
test("shell, CSP, manifest and nested offline PWA", async ({
page,
context,
@@ -116,7 +176,7 @@ test("shell, CSP, manifest and nested offline PWA", async ({
const manifest = await request.get("/font/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.font-tools",
version: "0.1.0",
version: "0.2.0",
requirements: { workers: true },
});
});
@@ -133,7 +193,7 @@ async function blockExternal(page: Page) {
return requests;
}
function fixtureFont(fsType: number) {
function fixtureFont(fsType: number, familyName = "Fixture Sans") {
const notdef = new Glyph({
name: ".notdef",
advanceWidth: 600,
@@ -158,7 +218,7 @@ function fixtureFont(fsType: number) {
path: rectanglePath(60, 0, 530, 700),
}),
font = new Font({
familyName: "Fixture Sans",
familyName,
styleName: "Regular",
unitsPerEm: 1_000,
ascender: 800,
@@ -172,6 +232,37 @@ function fixtureFont(fsType: number) {
return Buffer.from(bytes);
}
function fixtureCollection(fonts: Buffer[]) {
const headerSize = 12 + fonts.length * 4,
offsets: number[] = [];
let size = align4(headerSize);
for (const font of fonts) {
offsets.push(size);
size += align4(font.byteLength);
}
const output = Buffer.alloc(size);
output.write("ttcf", 0, "ascii");
output.writeUInt32BE(0x0001_0000, 4);
output.writeUInt32BE(fonts.length, 8);
fonts.forEach((font, index) => {
const faceOffset = offsets[index]!;
output.writeUInt32BE(faceOffset, 12 + index * 4);
font.copy(output, faceOffset);
const tableCount = font.readUInt16BE(4);
for (let tableIndex = 0; tableIndex < tableCount; tableIndex += 1) {
const sourceRecord = 12 + tableIndex * 16,
targetRecord = faceOffset + sourceRecord,
sourceOffset = font.readUInt32BE(sourceRecord + 8);
output.writeUInt32BE(faceOffset + sourceOffset, targetRecord + 8);
}
});
return output;
}
function align4(value: number) {
return (value + 3) & ~3;
}
function patchFsType(bytes: Uint8Array, fsType: number) {
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength),
count = view.getUint16(4, false);
+18
View File
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";
test("keeps the primary workspace inside a narrow viewport", async ({
page,
}) => {
await page.goto("/font/");
await expect(page.locator("main").first()).toBeVisible();
await expect(
page.locator("main .loading, main .workbench-loading"),
).toHaveCount(0);
const widths = await page.evaluate(() => ({
content: document.documentElement.scrollWidth,
viewport: document.documentElement.clientWidth,
}));
expect(widths.viewport).toBeLessThanOrEqual(430);
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
});