From 534a7b298e71e729970e7603ca9df052896ad91d Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Tue, 3 Mar 2026 23:56:21 +0300 Subject: [PATCH] Add DEFAULT_FONT_FAMILY constant, load fonts on paste and .fig import Replace all 'Inter' string literals with DEFAULT_FONT_FAMILY from constants. Add loadFontsForNodes() that collects font families from text nodes and loads them into CanvasKit, called after clipboard paste and .fig file import. --- packages/core/src/constants.ts | 1 + packages/core/src/kiwi/kiwi-convert.ts | 4 ++-- packages/core/src/render/export-jsx.ts | 3 ++- packages/core/src/renderer.ts | 11 ++++++----- packages/core/src/scene-graph.ts | 4 ++-- src/composables/use-font-status.ts | 4 ++-- src/constants.ts | 1 + src/stores/editor.ts | 26 +++++++++++++++++++++++++- 8 files changed, 41 insertions(+), 13 deletions(-) diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index a09bbee90..370d2b804 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -32,6 +32,7 @@ export const PEN_CLOSE_RADIUS_BOOST = 2 export const PEN_PATH_STROKE_WIDTH = 2 export const PARENT_OUTLINE_ALPHA = 0.5 export const PARENT_OUTLINE_DASH = 4 +export const DEFAULT_FONT_FAMILY = 'Inter' export const DEFAULT_FONT_SIZE = 14 export const DEFAULT_STROKE_MITER_LIMIT = 4 export const LABEL_FONT_SIZE = 11 diff --git a/packages/core/src/kiwi/kiwi-convert.ts b/packages/core/src/kiwi/kiwi-convert.ts index a1e478550..188a30b20 100644 --- a/packages/core/src/kiwi/kiwi-convert.ts +++ b/packages/core/src/kiwi/kiwi-convert.ts @@ -1,4 +1,4 @@ -import { BLACK, DEFAULT_STROKE_MITER_LIMIT } from '../constants' +import { BLACK, DEFAULT_FONT_FAMILY, DEFAULT_STROKE_MITER_LIMIT } from '../constants' import { styleToWeight } from '../fonts' import { decodeVectorNetworkBlob } from '../vector' @@ -449,7 +449,7 @@ export function nodeChangeToProps( cornerSmoothing: nc.cornerSmoothing ?? 0, text: nc.textData?.characters ?? '', fontSize: nc.fontSize ?? 14, - fontFamily: nc.fontName?.family ?? 'Inter', + fontFamily: nc.fontName?.family ?? DEFAULT_FONT_FAMILY, fontWeight: styleToWeight(nc.fontName?.style ?? ''), italic: nc.fontName?.style?.toLowerCase().includes('italic') ?? false, textAlignHorizontal: diff --git a/packages/core/src/render/export-jsx.ts b/packages/core/src/render/export-jsx.ts index a80d4a3a0..8ae3fc78b 100644 --- a/packages/core/src/render/export-jsx.ts +++ b/packages/core/src/render/export-jsx.ts @@ -1,4 +1,5 @@ import { colorToHex } from '../color' +import { DEFAULT_FONT_FAMILY } from '../constants' import type { SceneGraph, SceneNode, Fill, Stroke, Effect, NodeType, Color } from '../scene-graph' @@ -189,7 +190,7 @@ function collectProps(node: SceneNode, graph: SceneGraph): [string, unknown][] { if (node.type === 'TEXT') { if (node.fontSize !== 14) props.push(['size', node.fontSize]) - if (node.fontFamily && node.fontFamily !== 'Inter') props.push(['font', node.fontFamily]) + if (node.fontFamily && node.fontFamily !== DEFAULT_FONT_FAMILY) props.push(['font', node.fontFamily]) if (node.fontWeight !== 400) { if (node.fontWeight === 700) props.push(['weight', 'bold']) else if (node.fontWeight === 500) props.push(['weight', 'medium']) diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 41ef58856..45cb3e934 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -55,7 +55,8 @@ import { RULER_MAJOR_TOLERANCE, TEXT_SELECTION_COLOR, TEXT_CARET_COLOR, - TEXT_CARET_WIDTH + TEXT_CARET_WIDTH, + DEFAULT_FONT_FAMILY } from './constants' import { isFontLoaded } from './fonts' import { vectorNetworkToPath } from './vector' @@ -306,7 +307,7 @@ export class SkiaRenderer { const { initFontService, loadFont } = await import('./fonts') initFontService(this.ck, this.fontProvider) - const fontData = await loadFont('Inter', 'Regular') + const fontData = await loadFont(DEFAULT_FONT_FAMILY, 'Regular') if (fontData) { const typeface = this.ck.Typeface.MakeFreeTypeFaceFromData(fontData) if (typeface) { @@ -1964,7 +1965,7 @@ export class SkiaRenderer { isNodeFontLoaded(node: SceneNode): boolean { const families = new Set() - families.add(node.fontFamily || 'Inter') + families.add(node.fontFamily || DEFAULT_FONT_FAMILY) for (const run of node.styleRuns) { if (run.style.fontFamily) families.add(run.style.fontFamily) } @@ -2001,7 +2002,7 @@ export class SkiaRenderer { textAlign: this.getTextAlign(node.textAlignHorizontal), textStyle: { color: baseColor, - fontFamilies: [node.fontFamily || 'Inter'], + fontFamilies: [node.fontFamily || DEFAULT_FONT_FAMILY], fontSize: baseFontSize, fontStyle: { weight: { value: node.fontWeight || 400 } as FontWeight, @@ -2029,7 +2030,7 @@ export class SkiaRenderer { builder.pushStyle( new ck.TextStyle({ color: baseColor, - fontFamilies: [s.fontFamily ?? (node.fontFamily || 'Inter')], + fontFamilies: [s.fontFamily ?? (node.fontFamily || DEFAULT_FONT_FAMILY)], fontSize: s.fontSize ?? baseFontSize, fontStyle: { weight: { value: (s.fontWeight ?? node.fontWeight) || 400 } as FontWeight, diff --git a/packages/core/src/scene-graph.ts b/packages/core/src/scene-graph.ts index 5923fe88c..2f21edbad 100644 --- a/packages/core/src/scene-graph.ts +++ b/packages/core/src/scene-graph.ts @@ -1,4 +1,4 @@ -import { BLACK, DEFAULT_STROKE_MITER_LIMIT } from './constants' +import { BLACK, DEFAULT_FONT_FAMILY, DEFAULT_STROKE_MITER_LIMIT } from './constants' export type { GUID, Color } from './types' @@ -334,7 +334,7 @@ function createDefaultNode(type: NodeType, overrides: Partial = {}): clipsContent: false, text: '', fontSize: 14, - fontFamily: 'Inter', + fontFamily: DEFAULT_FONT_FAMILY, fontWeight: 400, italic: false, textAlignHorizontal: 'LEFT', diff --git a/src/composables/use-font-status.ts b/src/composables/use-font-status.ts index 8628c32a0..21bafb6bd 100644 --- a/src/composables/use-font-status.ts +++ b/src/composables/use-font-status.ts @@ -1,4 +1,4 @@ -import { isFontLoaded } from '@open-pencil/core' +import { isFontLoaded, DEFAULT_FONT_FAMILY } from '@open-pencil/core' import { computed } from 'vue' import type { SceneNode } from '@open-pencil/core' @@ -9,7 +9,7 @@ export function useNodeFontStatus(node: () => SceneNode) { if (n.type !== 'TEXT') return [] const families = new Set() - families.add(n.fontFamily || 'Inter') + families.add(n.fontFamily || DEFAULT_FONT_FAMILY) for (const run of n.styleRuns) { if (run.style.fontFamily) families.add(run.style.fontFamily) } diff --git a/src/constants.ts b/src/constants.ts index 540aed325..b6c08ac37 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,6 +24,7 @@ export { PEN_PATH_STROKE_WIDTH, PARENT_OUTLINE_ALPHA, PARENT_OUTLINE_DASH, + DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, LABEL_FONT_SIZE, SIZE_FONT_SIZE, diff --git a/src/stores/editor.ts b/src/stores/editor.ts index 69732609c..f119f9612 100644 --- a/src/stores/editor.ts +++ b/src/stores/editor.ts @@ -9,7 +9,8 @@ import { CANVAS_BG_COLOR, ZOOM_DIVISOR, ZOOM_SCALE_MIN, - ZOOM_SCALE_MAX + ZOOM_SCALE_MAX, + DEFAULT_FONT_FAMILY } from '@/constants' import { parseFigmaClipboard, @@ -27,6 +28,7 @@ import { SceneGraph } from '@/engine/scene-graph' import { TextEditor } from '@/engine/text-editor' import { UndoManager } from '@/engine/undo' import { computeVectorBounds } from '@/engine/vector' +import { loadFont } from '@/engine/fonts' import { readFigFile } from '@/kiwi/fig-file' import type { ExportFormat } from '@/engine/render-image' @@ -584,6 +586,7 @@ export function createEditorStore() { state.panY = 0 state.zoom = 1 state.pageColor = { ...CANVAS_BG_COLOR } + loadFontsForNodes(graph.getChildren(firstPage?.id ?? graph.rootId).map((n) => n.id)) requestRender() startWatchingFile() } catch (e) { @@ -1691,6 +1694,26 @@ export function createEditorStore() { return result } + function loadFontsForNodes(nodeIds: string[]) { + const families = new Set() + const collect = (id: string) => { + const node = graph.getNode(id) + if (!node) return + if (node.type === 'TEXT') { + families.add(node.fontFamily || DEFAULT_FONT_FAMILY) + for (const run of node.styleRuns) { + if (run.style.fontFamily) families.add(run.style.fontFamily) + } + } + for (const childId of node.childIds) collect(childId) + } + for (const id of nodeIds) collect(id) + families.delete(DEFAULT_FONT_FAMILY) + if (families.size === 0) return + const promises = [...families].map((f) => loadFont(f)) + Promise.all(promises).then(() => requestRender()) + } + function pasteFromHTML(html: string) { const ownNodes = parseOpenPencilClipboard(html) if (ownNodes) { @@ -1741,6 +1764,7 @@ export function createEditorStore() { requestRender() } }) + loadFontsForNodes(created) requestRender() } }