fix: increase tree glyph stroke weight
Reference site / build (push) Canceled after 0s

This commit is contained in:
2026-10-11 01:15:32 +09:00
parent ef5ea5e528
commit 6897b2368a
4 changed files with 45 additions and 14 deletions
+4 -1
View File
@@ -90,7 +90,10 @@ It reads `public/fonts/LythMono-Regular.ttf` from the site working directory,
extracts box glyphs directly through cmap (no programming ligatures or GSUB
shaping), and normalizes the vertical cell to the 24px tree row. Direct glyph
lookup also avoids Lyth Mono's contextual GSUB lookups that OpenType.js 2.0.0
does not support. The 0.604em advance and glyph stroke width are retained.
does not support. The 0.604em advance and connection centers are retained;
tree stroke edges are expanded symmetrically by 25% for legibility (the resting
vertical stem is 1.33px). Cell boundaries stay fixed so neighboring glyphs join.
The distributed font binaries and ordinary text weights are unchanged.
The resulting SVG paths are emitted into static HTML; neither the parser nor
the TTF is fetched by the browser. Anime.js controls the shared tree transform
+25 -9
View File
@@ -12,10 +12,22 @@ export const cellWidth = font.charToGlyph('0').advanceWidth * fontSize / font.un
const vertical = font.charToGlyph('│').getPath(0, 0, fontSize);
const bounds = vertical.getBoundingBox();
const yScale = rowHeight / (bounds.y2 - bounds.y1);
export const stemWidth = bounds.x2 - bounds.x1;
// Increase only tree ink, not text size, cell advance or connection positions.
const treeWeight = 1.25;
const nativeStemWidth = bounds.x2 - bounds.x1;
export const stemWidth = nativeStemWidth * treeWeight;
const horizontal = font.charToGlyph('─').getPath(0, 0, fontSize).getBoundingBox();
export const branchY = ((horizontal.y1 + horizontal.y2) / 2 - bounds.y1) * yScale;
export const branchThickness = (horizontal.y2 - horizontal.y1) * yScale;
const nativeBranchThickness = (horizontal.y2 - horizontal.y1) * yScale;
export const branchThickness = nativeBranchThickness * treeWeight;
// These box glyphs have straight edges. Move the two stroke edges symmetrically
// while keeping cell boundaries fixed, so neighboring cells still meet exactly.
function thickenEdge(value, center, width) {
return Math.abs(Math.abs(value - center) - width / 2) < 1e-6
? center + (value - center) * treeWeight
: value;
}
/** Retain the font's advance and normalize its full vertical cell to one row. */
export function glyphPath(text) {
@@ -26,13 +38,17 @@ export function glyphPath(text) {
// programming ligatures (Lyth Mono's GSUB includes lookups unsupported by OT2).
const path = new opentype.Path();
[...text].forEach((char, index) => {
path.extend(font.charToGlyph(char).getPath(index * cellWidth, 0, fontSize).commands);
});
for (const command of path.commands) {
for (const key of ['y', 'y1', 'y2']) {
if (key in command) command[key] = (command[key] - bounds.y1) * yScale;
const glyph = font.charToGlyph(char).getPath(0, 0, fontSize);
for (const command of glyph.commands) {
for (const key of ['x', 'x1', 'x2']) {
if (key in command) command[key] = thickenEdge(command[key], cellWidth / 2, nativeStemWidth) + index * cellWidth;
}
for (const key of ['y', 'y1', 'y2']) {
if (key in command) command[key] = thickenEdge((command[key] - bounds.y1) * yScale, branchY, nativeBranchThickness);
}
}
}
path.extend(glyph.commands);
});
return path.toPathData(5);
}
@@ -40,7 +56,7 @@ export function glyphPath(text) {
// Normalize length independently of thickness; a mask exposes the growing tip.
for (const command of vertical.commands) {
for (const key of ['x', 'x1', 'x2']) {
if (key in command) command[key] -= (bounds.x1 + bounds.x2) / 2;
if (key in command) command[key] = (command[key] - (bounds.x1 + bounds.x2) / 2) * treeWeight;
}
for (const key of ['y', 'y1', 'y2']) {
if (key in command) command[key] = (command[key] - bounds.y1) / (bounds.y2 - bounds.y1);
+3 -3
View File
@@ -219,8 +219,8 @@ test('trunk thickness and origin stay proportional to the slash throughout scali
await page.clock.runFor(time - previousTime);
const ink = await trunkGeometry(page);
expect(ink.opacity).toBeGreaterThan(0);
// At rest: the Lyth Mono filled stem is 1.064px, slash ink is 21px.
expect(ink.stemWidth).toBeCloseTo(1.064, 5);
// At rest: tree ink is weighted 25% above the font's native 1.064px stem.
expect(ink.stemWidth).toBeCloseTo(1.33, 5);
expect(ink.width / ink.slashHeight).toBeCloseTo(ink.stemWidth / 21, 5);
// The foot is at x=12 within ink bounds x=7…41, not their centre.
expect(ink.anchorX).toBeCloseTo(5 / 34, 5);
@@ -601,7 +601,7 @@ test.describe('JavaScript-free glyph tree', () => {
await expect(page.locator('.branch-prefix svg')).toHaveCount(8);
expect(await page.locator('.branch-prefix').evaluateAll((prefixes) => prefixes.every((prefix) => !prefix.textContent!.trim()))).toBe(true);
const trunk = await trunkGeometry(page);
expect(trunk.width).toBeCloseTo(1.064, 5);
expect(trunk.width).toBeCloseTo(1.33, 5);
expect(trunk.length).toBeGreaterThan(0);
expect(Math.abs(trunk.axis)).toBeLessThan(.1);
expect(trunk.gapRatio).toBeCloseTo(1 / 14, 5);
+13 -1
View File
@@ -20,7 +20,8 @@ test('pinned font binaries and upstream licenses match their integrity manifest'
test('Lyth Mono paths retain exact advance, row bounds and stroke dimensions', () => {
close(cellWidth, 8.456);
close(stemWidth, 1.064);
close(stemWidth, 1.064 * 1.25);
close(branchThickness, (68 / 1170) * 24 * 1.25);
close(branchY, rowHeight / 2);
const vertical = box(glyphPath('│'));
close(vertical.y1, 0);
@@ -31,6 +32,17 @@ test('Lyth Mono paths retain exact advance, row bounds and stroke dimensions', (
close(horizontal.x1, 0);
close(horizontal.x2, cellWidth);
close(horizontal.y2 - horizontal.y1, branchThickness);
close((horizontal.y1 + horizontal.y2) / 2, branchY);
const tee = box(glyphPath('├'));
close(tee.x1, cellWidth / 2 - stemWidth / 2);
close(tee.x2, cellWidth);
close(tee.y1, 0);
close(tee.y2, rowHeight);
const elbow = box(glyphPath('└'));
close(elbow.x1, tee.x1);
close(elbow.x2, cellWidth);
close(elbow.y1, 0);
close(elbow.y2, branchY + branchThickness / 2);
const trunk = box(trunkPath);
close(trunk.x1 + trunk.x2, 0);
close(trunk.x2 - trunk.x1, stemWidth);