This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user