diff --git a/packages/core/src/canvaskit.ts b/packages/core/src/canvaskit.ts index 134c8fd38..af7926025 100644 --- a/packages/core/src/canvaskit.ts +++ b/packages/core/src/canvaskit.ts @@ -1,3 +1,4 @@ +/// import CanvasKitInit, { type CanvasKit } from 'canvaskit-wasm' import { IS_BROWSER } from './constants' @@ -12,8 +13,10 @@ export async function getCanvasKit(options?: CanvasKitOptions): Promise { - if (IS_BROWSER) return `/${file}` - return file + if (!IS_BROWSER) return file + const base = 'env' in import.meta ? import.meta.env.BASE_URL : '/' + const prefix = base === '/' ? '' : base.replace(/\/$/, '') + return `${prefix}/${file}` } instance = await CanvasKitInit({ diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 13ed5daf9..690904438 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -25,9 +25,8 @@ export const CANVAS_BG_COLOR_DARK = { r: 0.173, g: 0.173, b: 0.173, a: 1 } satis */ export function getDefaultCanvasBgColor(): Color { if (IS_BROWSER) { - const env = (import.meta as ImportMeta & { env?: { DEV?: boolean } }).env const params = new URLSearchParams(window.location.search) - if (env?.DEV === true && params.has('test')) { + if ('env' in import.meta && import.meta.env.DEV && params.has('test')) { return CANVAS_BG_COLOR } } @@ -331,10 +330,12 @@ export const CJK_FALLBACK_FAMILIES_MACOS = [ export const CJK_FALLBACK_FAMILIES_WINDOWS = [ 'Microsoft YaHei', + 'Microsoft YaHei UI', 'Microsoft JhengHei', 'Yu Gothic', 'Malgun Gothic', - 'SimHei' + 'SimHei', + 'SimSun' ] export const CJK_FALLBACK_FAMILIES_LINUX = [ diff --git a/packages/core/src/text/fonts.ts b/packages/core/src/text/fonts.ts index 76ad18997..c615b59d1 100644 --- a/packages/core/src/text/fonts.ts +++ b/packages/core/src/text/fonts.ts @@ -105,7 +105,9 @@ async function fetchGoogleFontFiles(family: string): Promise }> } + const data = (await response.json()) as { + items?: Array<{ files?: Record }> + } const files = data.items?.[0]?.files if (!files) return retryWithNormalizedFamily(family) @@ -135,7 +137,13 @@ async function fetchGoogleFont(family: string, style: string): Promise { +type FindLocalFontOptions = { allowVariable?: boolean } + +async function findLocalFont( + family: string, + style?: string, + options: FindLocalFontOptions = {} +): Promise { if (!IS_BROWSER || !window.queryLocalFonts) return null try { const fonts = await window.queryLocalFonts() @@ -153,9 +161,10 @@ async function findLocalFont(family: string, style?: string): Promise { if (cjkFallbackPromise) return cjkFallbackPromise cjkFallbackPromise = (async () => { - // Try local system fonts first + // Try local system fonts first (allow variable fonts — common for 微软雅黑 / PingFang). for (const family of getCJKCandidates()) { - const buffer = await findLocalFont(family) + const buffer = await findLocalFont(family, undefined, { + allowVariable: true + }) if (buffer && registerAndCache(family, 'Regular', buffer)) { cjkFallbackFamilies.push(family) } } - // Load all CJK Google Fonts in parallel for full coverage if (cjkFallbackFamilies.length === 0) { const results = await Promise.allSettled( CJK_GOOGLE_FONTS.map(async (family) => { diff --git a/public/_headers b/public/_headers new file mode 100644 index 000000000..f5d558447 --- /dev/null +++ b/public/_headers @@ -0,0 +1,5 @@ +/canvaskit.wasm + Content-Type: application/wasm + +/canvaskit-webgpu/canvaskit.wasm + Content-Type: application/wasm diff --git a/vite/canvaskit-assets.ts b/vite/canvaskit-assets.ts index d69d2fb73..dfaffc578 100644 --- a/vite/canvaskit-assets.ts +++ b/vite/canvaskit-assets.ts @@ -1,27 +1,73 @@ -import { copyFileSync, existsSync, mkdirSync } from 'fs' +import { copyFileSync, createReadStream, existsSync, mkdirSync } from 'fs' +import { dirname, resolve } from 'node:path' +import type { Connect, ResolvedConfig } from 'vite' import type { Plugin } from 'vite' -function copyIfMissing(source: string, destination: string) { - if (!existsSync(source) || existsSync(destination)) return - const directory = destination.slice(0, destination.lastIndexOf('/')) - if (directory) mkdirSync(directory, { recursive: true }) - copyFileSync(source, destination) +function syncWasmFromNodeModules(root: string, source: string, destination: string) { + const sourcePath = resolve(root, source) + const destinationPath = resolve(root, destination) + if (!existsSync(sourcePath)) { + console.warn(`[copy-canvaskit-wasm] Missing source (run \`bun install\`): ${sourcePath}`) + return + } + mkdirSync(dirname(destinationPath), { recursive: true }) + copyFileSync(sourcePath, destinationPath) +} + +function serveCanvasKitWasm(root: string): Connect.NextHandleFunction { + return (req, res, next) => { + const pathname = req.url?.split('?')[0] ?? '' + if (pathname !== '/canvaskit.wasm' && pathname !== '/canvaskit-webgpu/canvaskit.wasm') { + next() + return + } + + const publicPath = resolve(root, pathname.slice(1)) + const fallbackPath = + pathname === '/canvaskit.wasm' + ? resolve(root, 'node_modules/canvaskit-wasm/bin/canvaskit.wasm') + : resolve(root, 'packages/core/vendor/canvaskit-webgpu/canvaskit.wasm') + + const file = existsSync(publicPath) ? publicPath : fallbackPath + if (!existsSync(file)) { + next() + return + } + + res.setHeader('Content-Type', 'application/wasm') + res.setHeader('Cache-Control', 'no-cache') + createReadStream(file).on('error', next).pipe(res) + } } export function copyCanvasKitAssetsPlugin(): Plugin { + let root = process.cwd() + return { name: 'copy-canvaskit-wasm', + enforce: 'pre', + configResolved(config: ResolvedConfig) { + root = config.root + }, buildStart() { - copyIfMissing('node_modules/canvaskit-wasm/bin/canvaskit.wasm', 'public/canvaskit.wasm') - copyIfMissing( + syncWasmFromNodeModules(root, 'node_modules/canvaskit-wasm/bin/canvaskit.wasm', 'public/canvaskit.wasm') + syncWasmFromNodeModules( + root, 'packages/core/vendor/canvaskit-webgpu/canvaskit.wasm', - 'public/canvaskit-webgpu/canvaskit.wasm', + 'public/canvaskit-webgpu/canvaskit.wasm' ) - copyIfMissing( + syncWasmFromNodeModules( + root, 'packages/core/vendor/canvaskit-webgpu/canvaskit.js', - 'public/canvaskit-webgpu/canvaskit.js', + 'public/canvaskit-webgpu/canvaskit.js' ) }, + configureServer(server) { + server.middlewares.use(serveCanvasKitWasm(server.config.root)) + }, + configurePreviewServer(server) { + server.middlewares.use(serveCanvasKitWasm(server.config.root)) + } } }