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
|
extracts box glyphs directly through cmap (no programming ligatures or GSUB
|
||||||
shaping), and normalizes the vertical cell to the 24px tree row. Direct glyph
|
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
|
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 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
|
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 vertical = font.charToGlyph('│').getPath(0, 0, fontSize);
|
||||||
const bounds = vertical.getBoundingBox();
|
const bounds = vertical.getBoundingBox();
|
||||||
const yScale = rowHeight / (bounds.y2 - bounds.y1);
|
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();
|
const horizontal = font.charToGlyph('─').getPath(0, 0, fontSize).getBoundingBox();
|
||||||
export const branchY = ((horizontal.y1 + horizontal.y2) / 2 - bounds.y1) * yScale;
|
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. */
|
/** Retain the font's advance and normalize its full vertical cell to one row. */
|
||||||
export function glyphPath(text) {
|
export function glyphPath(text) {
|
||||||
@@ -26,13 +38,17 @@ export function glyphPath(text) {
|
|||||||
// programming ligatures (Lyth Mono's GSUB includes lookups unsupported by OT2).
|
// programming ligatures (Lyth Mono's GSUB includes lookups unsupported by OT2).
|
||||||
const path = new opentype.Path();
|
const path = new opentype.Path();
|
||||||
[...text].forEach((char, index) => {
|
[...text].forEach((char, index) => {
|
||||||
path.extend(font.charToGlyph(char).getPath(index * cellWidth, 0, fontSize).commands);
|
const glyph = font.charToGlyph(char).getPath(0, 0, fontSize);
|
||||||
});
|
for (const command of glyph.commands) {
|
||||||
for (const command of path.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']) {
|
for (const key of ['y', 'y1', 'y2']) {
|
||||||
if (key in command) command[key] = (command[key] - bounds.y1) * yScale;
|
if (key in command) command[key] = thickenEdge((command[key] - bounds.y1) * yScale, branchY, nativeBranchThickness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path.extend(glyph.commands);
|
||||||
|
});
|
||||||
return path.toPathData(5);
|
return path.toPathData(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +56,7 @@ export function glyphPath(text) {
|
|||||||
// Normalize length independently of thickness; a mask exposes the growing tip.
|
// Normalize length independently of thickness; a mask exposes the growing tip.
|
||||||
for (const command of vertical.commands) {
|
for (const command of vertical.commands) {
|
||||||
for (const key of ['x', 'x1', 'x2']) {
|
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']) {
|
for (const key of ['y', 'y1', 'y2']) {
|
||||||
if (key in command) command[key] = (command[key] - bounds.y1) / (bounds.y2 - bounds.y1);
|
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);
|
await page.clock.runFor(time - previousTime);
|
||||||
const ink = await trunkGeometry(page);
|
const ink = await trunkGeometry(page);
|
||||||
expect(ink.opacity).toBeGreaterThan(0);
|
expect(ink.opacity).toBeGreaterThan(0);
|
||||||
// At rest: the Lyth Mono filled stem is 1.064px, slash ink is 21px.
|
// At rest: tree ink is weighted 25% above the font's native 1.064px stem.
|
||||||
expect(ink.stemWidth).toBeCloseTo(1.064, 5);
|
expect(ink.stemWidth).toBeCloseTo(1.33, 5);
|
||||||
expect(ink.width / ink.slashHeight).toBeCloseTo(ink.stemWidth / 21, 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.
|
// The foot is at x=12 within ink bounds x=7…41, not their centre.
|
||||||
expect(ink.anchorX).toBeCloseTo(5 / 34, 5);
|
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);
|
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);
|
expect(await page.locator('.branch-prefix').evaluateAll((prefixes) => prefixes.every((prefix) => !prefix.textContent!.trim()))).toBe(true);
|
||||||
const trunk = await trunkGeometry(page);
|
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(trunk.length).toBeGreaterThan(0);
|
||||||
expect(Math.abs(trunk.axis)).toBeLessThan(.1);
|
expect(Math.abs(trunk.axis)).toBeLessThan(.1);
|
||||||
expect(trunk.gapRatio).toBeCloseTo(1 / 14, 5);
|
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', () => {
|
test('Lyth Mono paths retain exact advance, row bounds and stroke dimensions', () => {
|
||||||
close(cellWidth, 8.456);
|
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);
|
close(branchY, rowHeight / 2);
|
||||||
const vertical = box(glyphPath('│'));
|
const vertical = box(glyphPath('│'));
|
||||||
close(vertical.y1, 0);
|
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.x1, 0);
|
||||||
close(horizontal.x2, cellWidth);
|
close(horizontal.x2, cellWidth);
|
||||||
close(horizontal.y2 - horizontal.y1, branchThickness);
|
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);
|
const trunk = box(trunkPath);
|
||||||
close(trunk.x1 + trunk.x2, 0);
|
close(trunk.x1 + trunk.x2, 0);
|
||||||
close(trunk.x2 - trunk.x1, stemWidth);
|
close(trunk.x2 - trunk.x1, stemWidth);
|
||||||
|
|||||||
Reference in New Issue
Block a user