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.
This commit is contained in:
Danila Poyarkov 2026-03-03 23:56:21 +03:00
parent da2df1296b
commit 534a7b298e
8 changed files with 41 additions and 13 deletions

View file

@ -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

View file

@ -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:

View file

@ -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'])

View file

@ -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<string>()
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,

View file

@ -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<SceneNode> = {}):
clipsContent: false,
text: '',
fontSize: 14,
fontFamily: 'Inter',
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: 400,
italic: false,
textAlignHorizontal: 'LEFT',

View file

@ -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<string>()
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)
}

View file

@ -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,

View file

@ -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<string>()
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()
}
}