Refactor/reacrhitecture to fsd+ #49
@@ -9,43 +9,6 @@ import {
|
|||||||
*/
|
*/
|
||||||
const DEFAULT_RENDER_SIZE_PX = 16;
|
const DEFAULT_RENDER_SIZE_PX = 16;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts pretext's runtime shape. The public TS type exposes only `segments`;
|
|
||||||
* the numeric arrays exist at runtime but are absent from the published signature.
|
|
||||||
* Centralizing the cast keeps the engine body free of `as any`.
|
|
||||||
*/
|
|
||||||
function asPretextInternals(prepared: PreparedTextWithSegments): PretextInternals {
|
|
||||||
return prepared as unknown as PretextInternals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-grapheme data computed during dual-font layout. Internal to the engine;
|
* Per-grapheme data computed during dual-font layout. Internal to the engine;
|
||||||
* consumed by computeLineRenderModel to derive the per-frame render model.
|
* consumed by computeLineRenderModel to derive the per-frame render model.
|
||||||
@@ -114,6 +77,10 @@ export interface ComparisonResult {
|
|||||||
* of font A and font B. This guarantees that both fonts wrap at exactly the same
|
* of font A and font B. This guarantees that both fonts wrap at exactly the same
|
||||||
* positions, making side-by-side or slider comparison visually coherent.
|
* positions, making side-by-side or slider comparison visually coherent.
|
||||||
*
|
*
|
||||||
|
* Relies on pretext's published structural fields on `PreparedTextWithSegments`
|
||||||
|
* (`widths`, `breakableFitAdvances`, `lineEndFitAdvances`, `lineEndPaintAdvances`)
|
||||||
|
* which are exposed via the `PreparedCore` intersection in `@chenglou/pretext@0.0.6`.
|
||||||
|
*
|
||||||
* **Two-level caching strategy**
|
* **Two-level caching strategy**
|
||||||
* 1. Font-change cache (`#preparedA`, `#preparedB`, `#unifiedPrepared`): rebuilt only
|
* 1. Font-change cache (`#preparedA`, `#preparedB`, `#unifiedPrepared`): rebuilt only
|
||||||
* when `text`, `fontA`, or `fontB` changes. `prepareWithSegments` is expensive
|
* when `text`, `fontA`, or `fontB` changes. `prepareWithSegments` is expensive
|
||||||
@@ -129,9 +96,9 @@ export class DualFontLayout {
|
|||||||
#segmenter: Intl.Segmenter;
|
#segmenter: Intl.Segmenter;
|
||||||
|
|
||||||
// Cached prepared data
|
// Cached prepared data
|
||||||
#preparedA: PretextInternals | null = null;
|
#preparedA: PreparedTextWithSegments | null = null;
|
||||||
#preparedB: PretextInternals | null = null;
|
#preparedB: PreparedTextWithSegments | null = null;
|
||||||
#unifiedPrepared: PretextInternals | null = null;
|
#unifiedPrepared: PreparedTextWithSegments | null = null;
|
||||||
|
|
||||||
#lastText = '';
|
#lastText = '';
|
||||||
#lastFontA = '';
|
#lastFontA = '';
|
||||||
@@ -192,8 +159,8 @@ export class DualFontLayout {
|
|||||||
|
|
||||||
// 1. Prepare (or use cache)
|
// 1. Prepare (or use cache)
|
||||||
if (isFontChange) {
|
if (isFontChange) {
|
||||||
this.#preparedA = asPretextInternals(prepareWithSegments(text, fontA));
|
this.#preparedA = prepareWithSegments(text, fontA);
|
||||||
this.#preparedB = asPretextInternals(prepareWithSegments(text, fontB));
|
this.#preparedB = prepareWithSegments(text, fontB);
|
||||||
this.#unifiedPrepared = this.#createUnifiedPrepared(this.#preparedA, this.#preparedB, spacingPx);
|
this.#unifiedPrepared = this.#createUnifiedPrepared(this.#preparedA, this.#preparedB, spacingPx);
|
||||||
|
|
||||||
this.#lastText = text;
|
this.#lastText = text;
|
||||||
@@ -207,13 +174,7 @@ export class DualFontLayout {
|
|||||||
return { lines: [], totalHeight: 0 };
|
return { lines: [], totalHeight: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// pretext's `layoutWithLines` is typed against its public surface; pass the
|
const { lines, height } = layoutWithLines(this.#unifiedPrepared, width, lineHeight);
|
||||||
// runtime-internal shape through with one boundary cast.
|
|
||||||
const { lines, height } = layoutWithLines(
|
|
||||||
this.#unifiedPrepared as unknown as PreparedTextWithSegments,
|
|
||||||
width,
|
|
||||||
lineHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Map results back to both fonts
|
// 3. Map results back to both fonts
|
||||||
const preparedA = this.#preparedA;
|
const preparedA = this.#preparedA;
|
||||||
@@ -284,11 +245,11 @@ export class DualFontLayout {
|
|||||||
* across both fonts, with `spacingPx` added to model letter-spacing.
|
* across both fonts, with `spacingPx` added to model letter-spacing.
|
||||||
*/
|
*/
|
||||||
#createUnifiedPrepared(
|
#createUnifiedPrepared(
|
||||||
a: PretextInternals,
|
a: PreparedTextWithSegments,
|
||||||
b: PretextInternals,
|
b: PreparedTextWithSegments,
|
||||||
spacingPx: number = 0,
|
spacingPx: number = 0,
|
||||||
): PretextInternals {
|
): PreparedTextWithSegments {
|
||||||
const unified: PretextInternals = { ...a };
|
const unified: PreparedTextWithSegments = { ...a };
|
||||||
|
|
||||||
unified.widths = a.widths.map((w, i) => Math.max(w, b.widths[i]) + spacingPx);
|
unified.widths = a.widths.map((w, i) => Math.max(w, b.widths[i]) + spacingPx);
|
||||||
unified.lineEndFitAdvances = a.lineEndFitAdvances.map((w, i) =>
|
unified.lineEndFitAdvances = a.lineEndFitAdvances.map((w, i) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user