Fonts: carry uploaded faces through export/import + embed in SVG export

This commit is contained in:
2026-06-16 23:52:16 +03:00
parent 2ed9db3792
commit d8a7bcb7c1
16 changed files with 810 additions and 90 deletions
+21 -1
View File
@@ -18,6 +18,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useShallow } from 'zustand/react/shallow';
import type { VisualizationSpec } from 'vega-embed';
import type { Config } from 'vega-lite';
import { referencedUploadedFonts } from '@core/chart-export';
import type { FitMode } from '@core/rendering';
import { DatasetNotFoundError, prepareSpecForRender } from '@core/rendering';
import {
@@ -30,6 +32,7 @@ import { renderSpec, type RenderHandle } from '../services/chart-renderer';
import { useAppStore } from '../stores/AppStore';
import { useCustomThemeStore } from '../stores/CustomThemeStore';
import { useDatasetStore } from '../stores/DatasetStore';
import { useFontStore } from '../stores/FontStore';
import { usePreviewStore } from '../stores/PreviewStore';
import { selectShownText, useSnippetStore } from '../stores/SnippetStore';
import { useUserSettingsStore } from '../stores/UserSettingsStore';
@@ -159,6 +162,11 @@ function PreviewSettings() {
export function LivePreview() {
const hostRef = useRef<HTMLDivElement>(null);
const handleRef = useRef<RenderHandle | null>(null);
// The config the live view was last rendered with — read at SVG export to find
// which uploaded fonts the chart references (a font may live only in the theme
// config, not the spec). Tracks `handleRef`: set together, valid whenever a
// handle is.
const configRef = useRef<Config | null>(null);
const generationRef = useRef(0);
// Serializes node mutations across overlapping renders: each render chains onto
// the previous one's promise so only one vega-embed ever touches the shared host
@@ -296,6 +304,7 @@ export function LivePreview() {
return;
}
handleRef.current = handle;
configRef.current = config;
setChartReady(true);
setError(null);
clearBusy();
@@ -354,7 +363,18 @@ export function LivePreview() {
): Promise<string | null> => {
const handle = handleRef.current;
if (!handle) return null;
return await handle.toImageURL(format, options);
// Embed the referenced uploaded faces into an SVG so it renders off-app
// (spec §08). Read fresh: a font can be uploaded after the last render, and
// configRef holds the config that render used (a font may be theme-only).
const embedFonts =
format === 'svg'
? referencedUploadedFonts(
selectShownText(useSnippetStore.getState()),
configRef.current,
useFontStore.getState().fonts,
)
: undefined;
return await handle.toImageURL(format, { ...options, embedFonts });
},
[],
);