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
+18 -5
View File
@@ -11,6 +11,8 @@ import vegaEmbed, { type Result as EmbedResult } from 'vega-embed';
import type { VisualizationSpec } from 'vega-embed';
import type { Config } from 'vega-lite';
import { collectFontFamilies } from '@core/custom-theme';
import { embedFontsInSvg } from '@core/chart-export';
import type { FontAsset } from '@core/font-asset';
/** Options for `RenderHandle.toImageURL` (spec §08 → Per-chart export). */
interface ImageExportOptions {
@@ -31,6 +33,14 @@ interface ImageExportOptions {
* it transparent. Default `null`.
*/
background?: string | null;
/**
* Uploaded font faces the chart references, embedded into the **SVG** as base64
* `@font-face` rules so it renders the right type off-app (spec §08; scope doc
* §4). Ignored for PNG (the raster already bakes in the glyphs). Omitted/empty
* leaves only the family name, which falls back to a system font elsewhere. The
* caller (which holds the font library) resolves which faces are referenced.
*/
embedFonts?: ReadonlyArray<FontAsset>;
}
export interface RenderHandle {
@@ -231,12 +241,15 @@ export async function renderSpec(
node.replaceChildren();
},
async toImageURL(format, options = {}) {
const { scale = 1, background = null } = options;
const { scale = 1, background = null, embedFonts = [] } = options;
if (format === 'svg') {
// Vector — resolution-independent, so dpr/scale don't apply. A background
// is added as a full-bleed rect rather than baked into the live view.
const svg = await result.view.toSVG();
return svgDataUrl(background ? withSvgBackground(svg, background) : svg);
// Vector — resolution-independent, so dpr/scale don't apply. The view
// writes only family names, so referenced uploaded faces are embedded as
// @font-face data-URIs; a background is added as a full-bleed rect. Both
// are injected into the serialized string, not the live view.
let svg = embedFontsInSvg(await result.view.toSVG(), embedFonts);
if (background) svg = withSvgBackground(svg, background);
return svgDataUrl(svg);
}
// Multiply the requested scale by the device pixel ratio so a "1×" export is
// as crisp as the chart on screen (the Retina fix — see ImageExportOptions).