refactor(font): scaffold dualFontLayout module with shared types

This commit is contained in:
Ilia Mashkov
2026-05-30 21:48:56 +03:00
parent ddadac8686
commit c5092a488b
4 changed files with 145 additions and 0 deletions
@@ -0,0 +1,87 @@
/**
* Internal shape of pretext's PreparedTextWithSegments — only `segments` is in
* pretext's public TS type; the numeric arrays exist at runtime but are not in
* the published signature. Verified against node_modules/@chenglou/pretext/src/layout.ts.
*/
interface PretextInternals {
/**
* Per-segment text.
*/
segments: string[];
/**
* Per-segment full width in pixels.
*/
widths: number[];
/**
* Per-segment per-grapheme advance widths, or null when the segment is a single grapheme.
*/
breakableFitAdvances: (number[] | null)[];
/**
* Per-segment line-end fit advance.
*/
lineEndFitAdvances: number[];
/**
* Per-segment line-end paint advance.
*/
lineEndPaintAdvances: number[];
}
/**
* Per-grapheme data computed during dual-font layout. Internal to the engine;
* consumed by computeLineRenderModel to derive the per-frame render model.
*/
export interface ComparisonChar {
/**
* Grapheme cluster (may be >1 code unit for emoji, combining marks).
*/
char: string;
/**
* X offset from line start in fontA, pixels.
*/
xA: number;
/**
* Advance width of this grapheme in fontA, pixels.
*/
widthA: number;
/**
* X offset from line start in fontB, pixels.
*/
xB: number;
/**
* Advance width of this grapheme in fontB, pixels.
*/
widthB: number;
}
/**
* A single laid-out line. `chars` carries the per-grapheme data needed by
* computeLineRenderModel. Consumers should not iterate it directly.
*/
export interface ComparisonLine {
/**
* Full text of this line as returned by pretext.
*/
text: string;
/**
* Rendered width in pixels — maximum across fontA and fontB.
*/
width: number;
/**
* Per-grapheme metadata for both fonts.
*/
chars: ComparisonChar[];
}
/**
* Aggregated output of a dual-font layout pass.
*/
export interface ComparisonResult {
/**
* Per-line grapheme data. Empty when input text is empty.
*/
lines: ComparisonLine[];
/**
* Total height in pixels.
*/
totalHeight: number;
}
@@ -0,0 +1,5 @@
import { describe } from 'vitest';
describe('computeLineRenderModel', () => {
// tests added in subsequent tasks
});
@@ -0,0 +1,45 @@
import type {
ComparisonChar,
ComparisonLine,
} from '../DualFontLayout/DualFontLayout';
/**
* Per-line render slice consumed by Line.svelte. The window is centered on the
* slider's split index and clamps at line boundaries.
*/
export interface LineRenderModel {
/**
* Chars before the window joined into a single string, rendered as one fontA text run.
*/
leftText: string;
/**
* Window chars — each rendered as its own Character element with crossfade slots.
*/
windowChars: Array<{
/**
* Stable key for Svelte keyed each — survives slider movement within the same line.
*/
key: string;
/**
* Grapheme cluster to render.
*/
char: string;
/**
* True once the slider has crossed this char's threshold.
*/
isPast: boolean;
}>;
/**
* Chars after the window joined into a single string, rendered as one fontB text run.
*/
rightText: string;
}
export function computeLineRenderModel(
line: ComparisonLine,
sliderPos: number,
containerWidth: number,
windowSize: number,
): LineRenderModel {
throw new Error('not implemented');
}
@@ -0,0 +1,8 @@
export type {
ComparisonLine,
ComparisonResult,
} from './DualFontLayout/DualFontLayout';
export {
computeLineRenderModel,
type LineRenderModel,
} from './computeLineRenderModel/computeLineRenderModel';