test(canvas): record Figma visual oracle diffs
This commit is contained in:
parent
3722924026
commit
e02782d1b5
|
|
@ -162,6 +162,15 @@ function recordPatternSource(
|
|||
return picture
|
||||
}
|
||||
|
||||
function resolvePatternSource(graph: SceneGraph, sourceId: string): SceneNode | null {
|
||||
const direct = graph.getNode(sourceId)
|
||||
if (direct) return direct
|
||||
for (const node of graph.getAllNodes()) {
|
||||
if (node.source.id === sourceId) return node
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function applyPatternFill(
|
||||
r: SkiaRenderer,
|
||||
fill: Fill,
|
||||
|
|
@ -169,8 +178,8 @@ function applyPatternFill(
|
|||
graph: SceneGraph
|
||||
): boolean {
|
||||
const sourceId = fill.sourceNodeId
|
||||
if (!sourceId || sourceId === node.id) return false
|
||||
const source = graph.getNode(sourceId)
|
||||
if (!sourceId || sourceId === node.id || sourceId === node.source.id) return false
|
||||
const source = resolvePatternSource(graph, sourceId)
|
||||
if (!source || source.width <= 0 || source.height <= 0) return false
|
||||
|
||||
const layout = patternTileLayout(source, fill)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ OpenPencil is moving toward production-grade Figma compatibility while keeping d
|
|||
### Figma fidelity
|
||||
|
||||
- Preserve and round-trip more Figma metadata safely.
|
||||
- Add visual regression coverage for full multi-page `.fig` documents. `scripts/export-fixture-visuals.ts` exports current smoke fixture pages to `/tmp` for manual comparison without committing large images.
|
||||
- Add visual regression coverage for full multi-page `.fig` documents. `scripts/export-fixture-visuals.ts` exports current smoke fixture pages to `/tmp` for manual comparison without committing large images; `tests/fixtures/figma-oracles/visual-comparison-report.json` records the current Figma-vs-OpenPencil oracle diff findings.
|
||||
- Close high-impact renderer gaps: remaining mask edge cases, blend isolation, pattern fills, and broader variable-font fixtures.
|
||||
- Improve boolean operation editing/export now that imported Figma `BOOLEAN_OPERATION` nodes remain boolean operations.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { SkiaRenderer } from '@open-pencil/core/canvas'
|
|||
import { renderNodesToImage, initCanvasKit } from '@open-pencil/core/io'
|
||||
import { computeAllLayouts } from '@open-pencil/core/layout'
|
||||
import { SceneGraph } from '@open-pencil/core/scene-graph'
|
||||
import { loadFont } from '@open-pencil/core/text'
|
||||
import { fontManager } from '@open-pencil/core/text'
|
||||
|
||||
import { parseFigmaClipboard, importClipboardNodes } from '#core/clipboard'
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ async function runWithNodeId(nodeId: string) {
|
|||
|
||||
console.log('📋 Exporting clipboard data from Figma…')
|
||||
// Select the node, copy, read clipboard, render with our engine
|
||||
await $`figma-use eval ${`(() => { const n = figma.getNodeById('${nodeId}'); if (n) { figma.currentPage.selection = [n]; } })()`}`.quiet()
|
||||
await $`figma-use eval ${`(() => { const n = figma.getNodeById('${nodeId}'); if (!n) return; let page = n.parent; while (page && page.type !== 'PAGE') page = page.parent; if (page) { figma.currentPage = page; page.selection = [n]; } })()`}`.quiet()
|
||||
await Bun.sleep(200)
|
||||
await $`osascript -e 'tell application "Figma" to activate'`.quiet()
|
||||
await Bun.sleep(300)
|
||||
|
|
@ -118,7 +118,7 @@ async function renderOurs(html: string) {
|
|||
if (node.fontFamily) families.add(node.fontFamily)
|
||||
}
|
||||
for (const family of families) {
|
||||
await loadFont(family)
|
||||
await fontManager.loadFont(family)
|
||||
}
|
||||
|
||||
const ck = await initCanvasKit()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ function createRenderer() {
|
|||
} as SkiaRenderer
|
||||
}
|
||||
|
||||
const node = { id: '1:2', width: 100, height: 100 } as SceneNode
|
||||
const node = { id: '1:2', source: { id: '' }, width: 100, height: 100 } as SceneNode
|
||||
|
||||
describe('schema fill fallback rendering', () => {
|
||||
test('renders pattern fills from referenced source nodes', () => {
|
||||
|
|
@ -58,10 +58,11 @@ describe('schema fill fallback rendering', () => {
|
|||
}
|
||||
]
|
||||
})
|
||||
source.source.id = '12:34'
|
||||
const renderer = createRenderer()
|
||||
const fill: Fill = {
|
||||
type: 'PATTERN',
|
||||
sourceNodeId: source.id,
|
||||
sourceNodeId: '12:34',
|
||||
patternSpacing: { x: 0.25, y: 0.4 },
|
||||
color: { r: 0.2, g: 0.3, b: 0.4, a: 0.8 },
|
||||
opacity: 1,
|
||||
|
|
|
|||
64
tests/fixtures/figma-oracles/visual-comparison-report.json
vendored
Normal file
64
tests/fixtures/figma-oracles/visual-comparison-report.json
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"source": {
|
||||
"tool": "scripts/visual-compare.ts",
|
||||
"captured": "2026-05-22",
|
||||
"outputRoot": "/tmp/open-pencil-oracles"
|
||||
},
|
||||
"comparisons": [
|
||||
{
|
||||
"name": "rich-text-decorations",
|
||||
"nodeId": "296488:2238",
|
||||
"output": "/tmp/open-pencil-oracles/rich-text/decorations-scale1",
|
||||
"figmaSize": "493x41",
|
||||
"openPencilSizeBeforeResize": "493x23",
|
||||
"differentPixels": 10611,
|
||||
"differentPercent": 52.5,
|
||||
"finding": "OpenPencil imports style-run decoration metadata, but exact Figma line metrics/leading trim still differ substantially."
|
||||
},
|
||||
{
|
||||
"name": "rich-text-offset-skip-ink",
|
||||
"nodeId": "296619:2252",
|
||||
"output": "/tmp/open-pencil-oracles/rich-text/offset-skipink-scale1",
|
||||
"figmaSize": "212x32",
|
||||
"openPencilSizeBeforeResize": "212x39",
|
||||
"differentPixels": 4154,
|
||||
"differentPercent": 61.23,
|
||||
"finding": "Underline offset and skip-ink now round-trip, but CanvasKit rendering does not expose matching underline offset/skip-ink controls."
|
||||
},
|
||||
{
|
||||
"name": "nested-luminance-mask",
|
||||
"nodeId": "296616:2249",
|
||||
"output": "/tmp/open-pencil-oracles/masks/nested-scale1",
|
||||
"figmaSize": "240x120",
|
||||
"openPencilSizeBeforeResize": "240x120",
|
||||
"differentPixels": 12944,
|
||||
"differentPercent": 44.94,
|
||||
"finding": "Visual output is structurally close, but colors/antialiasing differ enough for a high raw AE score."
|
||||
},
|
||||
{
|
||||
"name": "pattern-target-without-source",
|
||||
"nodeId": "296519:2245",
|
||||
"output": "/tmp/open-pencil-oracles/pattern-scale1",
|
||||
"figmaSize": "160x100",
|
||||
"openPencilSizeBeforeResize": "160x100",
|
||||
"differentPixels": 16000,
|
||||
"differentPercent": 100,
|
||||
"finding": "Copying/exporting only a pattern-filled target does not include its source node in clipboard data; OpenPencil must fall back without whole-document context."
|
||||
},
|
||||
{
|
||||
"name": "pattern-frame-with-hidden-source",
|
||||
"nodeId": "296731:2254",
|
||||
"output": "/tmp/open-pencil-oracles/pattern-frame-scale1-after-sourceid",
|
||||
"figmaSize": "180x100",
|
||||
"openPencilSizeBeforeResize": "180x100",
|
||||
"differentPixels": 9102,
|
||||
"differentPercent": 50.57,
|
||||
"finding": "Resolving pattern sources by imported Figma source id works in OpenPencil, but this hidden-source frame oracle renders blank in Figma and is not a parity baseline."
|
||||
}
|
||||
],
|
||||
"nextActions": [
|
||||
"Create a Figma pattern oracle where the source is included in the copied subtree and Figma still renders the pattern target.",
|
||||
"Use perceptual/color-tolerant image diff for mask comparisons instead of raw absolute-error counts.",
|
||||
"Tune rich text line metrics before treating underline placement diffs as actionable."
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue