1.2.5: text font picker + 7 bundled Google fonts + default text fill black

- New src/lib/fonts.ts: curated set (Inter, Roboto, Lora, Playfair
  Display, Bebas Neue, Source Code Pro, Permanent Marker) shipped as
  local .woff2 in public/fonts/. loadFonts() registers them via
  FontFace at app start; fontsReady ref re-triggers Konva text configs
  once fonts resolve so the first paint isn't stuck on the fallback.
- TextAsset gets optional fontFamily (id; undefined = system sans).
  PropertiesPanel adds a font dropdown next to font size.
- All five render sites read the family: CanvasStage textConfig +
  clipTextConfig (Konva), drawLayer + drawMaskShape (canvas2d),
  exportPlayer engine (uses an embedded FONT_FAMILIES map and fontFam
  helper; runtime version 27 → 28).
- Example HTML export now lists used bundled fonts and prints the
  @font-face CSS the user has to paste alongside the .woff2 files —
  fonts themselves are NOT bundled into the export to keep it small.
- Default text fill changed from yellow #f5d76e to black #000000 in
  both the main sidebar and the SVG-scope add-text path.
- 8 new tests under "text font picker — model + render + export".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-26 00:32:42 +02:00
1 parent b32b8a6a3d
commit 4c2b7c7c35
20 files changed
+339 -12

No files matched your search

+10
View File
@@ -20,6 +20,7 @@ import { isButtonLayerActiveAt, isLayerActiveAt, sampleLayer } from '@/lib/inter
import { importImageFile, loadImage } from '@/lib/fileLoader'
import { useClipartStore } from '@/stores/clipart'
import { useDocumentDrawer } from '@/composables/useDocumentDrawer'
import { fontCssFamily, fontsReady } from '@/lib/fonts'
import { drawButtonFrame, drawSceneFrame, loadSceneImages } from '@/lib/render'
import type {
Action,
@@ -1303,11 +1304,17 @@ function circleConfig(
function textConfig(layer: Layer, asset: Extract<Asset, { kind: 'text' }>, props: AnimatableProps) {
const fill = props.fill ?? asset.fill
const base = baseNodeConfig(layer, props)
// Read fontsReady so this config recomputes once bundled fonts
// resolve — otherwise Konva caches a measurement made against the
// fallback family and the text is rendered too narrow.
void fontsReady.value
const family = fontCssFamily(asset.fontFamily)
if (isLayerGhostLocked(layer)) {
return {
...base,
text: asset.text,
fontSize: asset.fontSize,
fontFamily: family,
offsetX: layer.offsetX ?? 0,
offsetY: layer.offsetY ?? 0,
fill: undefined,
@@ -1320,6 +1327,7 @@ function textConfig(layer: Layer, asset: Extract<Asset, { kind: 'text' }>, props
...base,
text: asset.text,
fontSize: asset.fontSize,
fontFamily: family,
offsetX: layer.offsetX ?? 0,
offsetY: layer.offsetY ?? 0,
...(layer.isMask ? {} : strokeCfg(props, asset)),
@@ -1611,12 +1619,14 @@ function clipCircleConfig(item: RenderedClipLayer) {
}
function clipTextConfig(item: RenderedClipLayer) {
void fontsReady.value
const asset = item.asset as Extract<Asset, { kind: 'text' }>
const fill = item.props.fill ?? asset.fill
const cfg: Record<string, unknown> = {
...clipBaseConfig(item.instanceId, item.layer, item.props),
text: asset.text,
fontSize: asset.fontSize,
fontFamily: fontCssFamily(asset.fontFamily),
offsetX: item.layer.offsetX ?? 0,
offsetY: item.layer.offsetY ?? 0,
...strokeCfg(item.props, asset),