refactor(comparison): switch to 3-section render model via DualFontLayout
Rewrite Line.svelte to render leftText / windowChars / rightText regions from a LineRenderModel. Bulk regions render as native shaped text runs so the browser applies kerning and ligatures; per-char DOM is reserved for the N-char crossfade window straddling the slider. Slim Character.svelte: drop the unused proximity prop and the redundant font-size/font-weight/letter-spacing styles now inherited from the line container. Switch SliderArea.svelte to instantiate DualFontLayout and derive each line's render model via computeLineRenderModel(line, sliderPos, containerWidth, WINDOW_SIZE).
This commit is contained in:
@@ -1,42 +1,46 @@
|
||||
<!--
|
||||
Component: Line
|
||||
Renders a line of text in the SliderArea
|
||||
Renders one laid-out line of comparison text as three regions:
|
||||
a fontA bulk run (already past the slider), an N-char window of crossfade
|
||||
slots straddling the slider, and a fontB bulk run (not yet past).
|
||||
Bulk text is rendered as native shaped runs so the browser applies
|
||||
kerning and ligatures; per-char DOM is reserved for the window only.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { LineRenderModel } from '$entities/Font';
|
||||
import { typographySettingsStore } from '$features/AdjustTypography';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface LineChar {
|
||||
char: string;
|
||||
xA: number;
|
||||
widthA: number;
|
||||
xB: number;
|
||||
widthB: number;
|
||||
}
|
||||
import { comparisonStore } from '../../model';
|
||||
import Character from '../Character/Character.svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Pre-computed grapheme array from CharacterComparisonEngine.
|
||||
* Using the engine's chars array (rather than splitting line.text) ensures
|
||||
* correct grapheme-cluster boundaries for emoji and multi-codepoint characters.
|
||||
* Per-line render slice from computeLineRenderModel.
|
||||
*/
|
||||
chars: LineChar[];
|
||||
/**
|
||||
* Character render snippet
|
||||
*/
|
||||
character: Snippet<[{ char: string; index: number }]>;
|
||||
model: LineRenderModel;
|
||||
}
|
||||
const typography = $derived(typographySettingsStore);
|
||||
|
||||
let { chars, character }: Props = $props();
|
||||
let { model }: Props = $props();
|
||||
|
||||
const typography = $derived(typographySettingsStore);
|
||||
const fontA = $derived(comparisonStore.fontA);
|
||||
const fontB = $derived(comparisonStore.fontB);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative flex w-full justify-center items-center whitespace-nowrap"
|
||||
class="relative flex w-full justify-center items-center whitespace-pre"
|
||||
style:height="{typography.height * typography.renderedSize}px"
|
||||
style:line-height="{typography.height * typography.renderedSize}px"
|
||||
style:font-size="{typography.renderedSize}px"
|
||||
style:letter-spacing="{typography.spacing}em"
|
||||
style:font-weight={typography.weight}
|
||||
>
|
||||
{#each chars as c, index}
|
||||
{@render character?.({ char: c.char, index })}
|
||||
{#if model.leftText}
|
||||
<span style:font-family={fontA?.name}>{model.leftText}</span>
|
||||
{/if}
|
||||
{#each model.windowChars as wc (wc.key)}
|
||||
<Character char={wc.char} isPast={wc.isPast} />
|
||||
{/each}
|
||||
{#if model.rightText}
|
||||
<span style:font-family={fontB?.name}>{model.rightText}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user