feat(appliedFontsStore): incorporate implemented font weight logic

This commit is contained in:
Ilia Mashkov
2026-01-20 14:21:07 +03:00
parent 55a560b785
commit d4d2d68d9a
4 changed files with 32 additions and 18 deletions

View File

@@ -20,6 +20,10 @@ interface Props {
* Font id to load * Font id to load
*/ */
id: string; id: string;
/**
* Font weight
*/
weight?: number;
/** /**
* Additional classes * Additional classes
*/ */
@@ -30,7 +34,7 @@ interface Props {
children?: Snippet; children?: Snippet;
} }
let { name, id, className, children }: Props = $props(); let { name, id, weight = 400, className, children }: Props = $props();
let element: Element; let element: Element;
// Track if the user has actually scrolled this into view // Track if the user has actually scrolled this into view
@@ -40,7 +44,7 @@ $effect(() => {
const observer = new IntersectionObserver(entries => { const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) { if (entries[0].isIntersecting) {
hasEnteredViewport = true; hasEnteredViewport = true;
appliedFontsManager.touch([id]); appliedFontsManager.touch([{ slug: id, weight }]);
// Once it has entered, we can stop observing to save CPU // Once it has entered, we can stop observing to save CPU
observer.unobserve(element); observer.unobserve(element);
@@ -50,7 +54,7 @@ $effect(() => {
return () => observer.disconnect(); return () => observer.disconnect();
}); });
const status = $derived(appliedFontsManager.getFontStatus(id)); const status = $derived(appliedFontsManager.getFontStatus(id, weight, false));
// The "Show" condition: Element is in view AND (Font is ready OR it errored out) // The "Show" condition: Element is in view AND (Font is ready OR it errored out)
const shouldReveal = $derived(hasEnteredViewport && (status === 'loaded' || status === 'error')); const shouldReveal = $derived(hasEnteredViewport && (status === 'loaded' || status === 'error'));

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { appliedFontsManager } from '$entities/Font'; import { appliedFontsManager } from '$entities/Font';
import { controlManager } from '$features/SetupFont';
import { Input } from '$shared/shadcn/ui/input'; import { Input } from '$shared/shadcn/ui/input';
import { ComparisonSlider } from '$shared/ui'; import { ComparisonSlider } from '$shared/ui';
import { displayedFontsStore } from '../../model'; import { displayedFontsStore } from '../../model';
@@ -10,7 +11,7 @@ const [fontA, fontB] = $derived(displayedFontsStore.selectedPair);
const hasAnyPairs = $derived(displayedFontsStore.fonts.length > 0); const hasAnyPairs = $derived(displayedFontsStore.fonts.length > 0);
$effect(() => { $effect(() => {
appliedFontsManager.touch(displayedFontsStore.fonts.map(font => font.id)); appliedFontsManager.touch(displayedFontsStore.fonts.map(font => ({ slug: font.id, weight })));
}); });
</script> </script>
@@ -22,7 +23,13 @@ $effect(() => {
</div> </div>
{#if fontA && fontB} {#if fontA && fontB}
<ComparisonSlider fontA={fontA} fontB={fontB} text={displayedText} /> <ComparisonSlider
fontA={fontA}
fontB={fontB}
text={displayedText}
weight={weight}
/>
</div>
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@@ -7,6 +7,7 @@ import {
FontApplicator, FontApplicator,
type UnifiedFont, type UnifiedFont,
} from '$entities/Font'; } from '$entities/Font';
import { controlManager } from '$features/SetupFont';
import { ContentEditable } from '$shared/ui'; import { ContentEditable } from '$shared/ui';
interface Props { interface Props {
@@ -31,6 +32,8 @@ let {
text = $bindable(), text = $bindable(),
...restProps ...restProps
}: Props = $props(); }: Props = $props();
const weight = $derived(controlManager.weight ?? 400);
</script> </script>
<div <div
@@ -39,6 +42,7 @@ let {
bg-white p-6 border border-slate-200 bg-white p-6 border border-slate-200
shadow-sm dark:border-slate-800 dark:bg-slate-950 shadow-sm dark:border-slate-800 dark:bg-slate-950
" "
style:font-weight={weight}
> >
<FontApplicator id={font.id} name={font.name}> <FontApplicator id={font.id} name={font.name}>
<ContentEditable bind:text={text} {...restProps} /> <ContentEditable bind:text={text} {...restProps} />

View File

@@ -19,6 +19,7 @@ export function createCharacterComparison(
text: () => string, text: () => string,
fontA: () => { name: string; id: string }, fontA: () => { name: string; id: string },
fontB: () => { name: string; id: string }, fontB: () => { name: string; id: string },
weight: () => number,
) { ) {
let lines = $state<LineData[]>([]); let lines = $state<LineData[]>([]);
let containerWidth = $state(0); let containerWidth = $state(0);
@@ -29,14 +30,16 @@ export function createCharacterComparison(
* @param text - Text string to measure * @param text - Text string to measure
* @param fontFamily - Font family name * @param fontFamily - Font family name
* @param fontSize - Font size in pixels * @param fontSize - Font size in pixels
* @param fontWeight - Font weight
*/ */
function measureText( function measureText(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
text: string, text: string,
fontFamily: string, fontFamily: string,
fontSize: number, fontSize: number,
fontWeight: number,
): number { ): number {
ctx.font = `bold ${fontSize}px ${fontFamily}`; ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
return ctx.measureText(text).width; return ctx.measureText(text).width;
} }
@@ -62,6 +65,7 @@ export function createCharacterComparison(
* @param container - The container element to measure width from * @param container - The container element to measure width from
* @param measureCanvas - The canvas element used for text measurement * @param measureCanvas - The canvas element used for text measurement
*/ */
function breakIntoLines( function breakIntoLines(
container: HTMLElement | undefined, container: HTMLElement | undefined,
measureCanvas: HTMLCanvasElement | undefined, measureCanvas: HTMLCanvasElement | undefined,
@@ -70,15 +74,14 @@ export function createCharacterComparison(
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
containerWidth = rect.width; containerWidth = rect.width;
// Padding considerations - matches the container padding // Padding considerations - matches the container padding
const padding = window.innerWidth < 640 ? 48 : 96; const padding = window.innerWidth < 640 ? 48 : 96;
const availableWidth = rect.width - padding; const availableWidth = rect.width - padding;
const ctx = measureCanvas.getContext('2d'); const ctx = measureCanvas.getContext('2d');
if (!ctx) return; if (!ctx) return;
const fontSize = getFontSize(); const fontSize = getFontSize();
const currentWeight = weight(); // Get current weight
const words = text().split(' '); const words = text().split(' ');
const newLines: LineData[] = []; const newLines: LineData[] = [];
let currentLineWords: string[] = []; let currentLineWords: string[] = [];
@@ -86,9 +89,9 @@ export function createCharacterComparison(
function pushLine(words: string[]) { function pushLine(words: string[]) {
if (words.length === 0) return; if (words.length === 0) return;
const lineText = words.join(' '); const lineText = words.join(' ');
// Measure width to ensure we know exactly how wide this line renders // Measure both fonts at the CURRENT weight
const widthA = measureText(ctx!, lineText, fontA().name, fontSize); const widthA = measureText(ctx!, lineText, fontA().name, fontSize, currentWeight);
const widthB = measureText(ctx!, lineText, fontB().name, fontSize); const widthB = measureText(ctx!, lineText, fontB().name, fontSize, currentWeight);
const maxWidth = Math.max(widthA, widthB); const maxWidth = Math.max(widthA, widthB);
newLines.push({ text: lineText, width: maxWidth }); newLines.push({ text: lineText, width: maxWidth });
} }
@@ -97,10 +100,9 @@ export function createCharacterComparison(
const testLine = currentLineWords.length > 0 const testLine = currentLineWords.length > 0
? currentLineWords.join(' ') + ' ' + word ? currentLineWords.join(' ') + ' ' + word
: word; : word;
// Measure with both fonts and use the wider one to prevent layout shifts // Measure with both fonts and use the wider one to prevent layout shifts
const widthA = measureText(ctx, testLine, fontA().name, fontSize); const widthA = measureText(ctx, testLine, fontA().name, fontSize, currentWeight);
const widthB = measureText(ctx, testLine, fontB().name, fontSize); const widthB = measureText(ctx, testLine, fontB().name, fontSize, currentWeight);
const maxWidth = Math.max(widthA, widthB); const maxWidth = Math.max(widthA, widthB);
if (maxWidth > availableWidth && currentLineWords.length > 0) { if (maxWidth > availableWidth && currentLineWords.length > 0) {
@@ -111,10 +113,7 @@ export function createCharacterComparison(
} }
} }
if (currentLineWords.length > 0) { if (currentLineWords.length > 0) pushLine(currentLineWords);
pushLine(currentLineWords);
}
lines = newLines; lines = newLines;
} }