diff --git a/site/public/fonts/README.md b/site/public/fonts/README.md index ad4c28b..cacff8b 100644 --- a/site/public/fonts/README.md +++ b/site/public/fonts/README.md @@ -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 diff --git a/site/src/lib/landing-glyphs.mjs b/site/src/lib/landing-glyphs.mjs index 3fab8a3..53522f6 100644 --- a/site/src/lib/landing-glyphs.mjs +++ b/site/src/lib/landing-glyphs.mjs @@ -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); diff --git a/site/tests/browser/landing.spec.ts b/site/tests/browser/landing.spec.ts index ea62173..a631c27 100644 --- a/site/tests/browser/landing.spec.ts +++ b/site/tests/browser/landing.spec.ts @@ -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); diff --git a/site/tests/landing-glyphs.test.mjs b/site/tests/landing-glyphs.test.mjs index 08e7fb5..9750ea6 100644 --- a/site/tests/landing-glyphs.test.mjs +++ b/site/tests/landing-glyphs.test.mjs @@ -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);