openpencil/tests/e2e/fonts/cjk-rendering-visual.spec.ts
2026-07-01 23:22:14 +03:00

122 lines
4 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs'
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
const appleGothic = '/System/Library/Fonts/Supplemental/AppleGothic.ttf'
test.skip(
process.platform !== 'darwin' || !existsSync(appleGothic),
'CJK visual snapshot uses macOS AppleGothic for Hangul coverage'
)
async function expectCanvas(name: string) {
editor.canvas.assertNoErrors()
const buffer = await editor.canvas.canvas.screenshot()
expect(buffer).toMatchSnapshot(`${name}.png`)
}
test('renders Simplified Chinese, Traditional Chinese, Japanese, and Korean fallback text', async () => {
await editor.page.route('**/__test-fonts/apple-gothic.ttf', async (route) => {
await route.fulfill({ body: readFileSync(appleGothic), contentType: 'font/ttf' })
})
const smoke = await editor.page.evaluate(async () => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const pageId = store.state.currentPageId
const { fontManager } = await import('/packages/core/src/text/fonts.ts')
const [notoResponse, appleGothicResponse] = await Promise.all([
fetch('/tests/fixtures/fonts/NotoSansSC-Regular.ttf'),
fetch('/__test-fonts/apple-gothic.ttf')
])
if (!notoResponse.ok) throw new Error(`Failed to load Noto Sans SC: ${notoResponse.status}`)
if (!appleGothicResponse.ok) {
throw new Error(`Failed to load AppleGothic: ${appleGothicResponse.status}`)
}
fontManager.markLoaded('Noto Sans SC', 'Regular', await notoResponse.arrayBuffer())
fontManager.markLoaded('AppleGothic', 'Regular', await appleGothicResponse.arrayBuffer())
fontManager.setCJKFallbackFamily('Noto Sans SC')
fontManager.setCJKFallbackFamily('AppleGothic')
const lines = [
{ label: 'Simplified Chinese', text: '你好世界' },
{ label: 'Traditional Chinese', text: '繁體中文' },
{ label: 'Japanese', text: '日本語かなカナ' },
{ label: 'Korean', text: '안녕하세요' }
]
store.graph.createNode('FRAME', pageId, {
name: 'CJK rendering visual backdrop',
x: 64,
y: 52,
width: 720,
height: 312,
cornerRadius: 20,
fills: [
{ type: 'SOLID', color: { r: 0.97, g: 0.98, b: 1, a: 1 }, visible: true, opacity: 1 }
],
strokes: [
{
color: { r: 0.78, g: 0.84, b: 0.95, a: 1 },
weight: 1,
visible: true,
opacity: 1,
align: 'INSIDE'
}
]
})
for (const [index, line] of lines.entries()) {
const y = 84 + index * 64
store.graph.createNode('TEXT', pageId, {
name: `${line.label} label`,
x: 96,
y,
width: 210,
height: 28,
text: line.label,
fontFamily: 'Inter',
fontSize: 17,
fontWeight: 600,
fills: [
{ type: 'SOLID', color: { r: 0.25, g: 0.32, b: 0.45, a: 1 }, visible: true, opacity: 1 }
]
})
store.graph.createNode('TEXT', pageId, {
name: `${line.label} sample`,
x: 328,
y: y - 4,
width: 360,
height: 42,
text: line.text,
fontFamily: 'Inter',
fontSize: 32,
textAutoResize: 'HEIGHT',
fills: [
{ type: 'SOLID', color: { r: 0.04, g: 0.06, b: 0.12, a: 1 }, visible: true, opacity: 1 }
]
})
}
await store.loadFontsForNodes(store.graph.getPages().flatMap((page) => page.childIds))
store.clearSelection()
store.requestRender()
return {
loaded: {
noto: fontManager.isStyleLoaded('Noto Sans SC', 'Regular'),
appleGothic: fontManager.isStyleLoaded('AppleGothic', 'Regular')
},
fallbacks: fontManager.getCJKFallbackFamilies()
}
})
expect(smoke.loaded).toEqual({ noto: true, appleGothic: true })
expect(smoke.fallbacks).toEqual(expect.arrayContaining(['Noto Sans SC', 'AppleGothic']))
await editor.canvas.waitForRender()
await expectCanvas('cjk-fallback-text-rendering')
})