Commands: - open-pencil info <file> — document stats, node types, fonts - open-pencil tree <file> — visual node tree with agentfmt - open-pencil find <file> — search by name/type with rich output - open-pencil export <file> — render to PNG/JPG/WEBP at any scale Uses canvaskit-wasm/full for headless CPU rasterization. All commands support --json for machine-readable output. agentfmt provides consistent colored terminal formatting.
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
import CanvasKitInit from 'canvaskit-wasm/full'
|
|
import type { CanvasKit } from 'canvaskit-wasm'
|
|
import {
|
|
parseFigFile,
|
|
SceneGraph,
|
|
SkiaRenderer,
|
|
computeAllLayouts,
|
|
loadFont,
|
|
renderNodesToImage,
|
|
renderThumbnail
|
|
} from '@open-pencil/core'
|
|
import type { ExportFormat } from '@open-pencil/core'
|
|
|
|
let ck: CanvasKit | null = null
|
|
|
|
export async function initCanvasKit(): Promise<CanvasKit> {
|
|
if (ck) return ck
|
|
const ckPath = import.meta.resolve('canvaskit-wasm/full')
|
|
const binDir = new URL('.', ckPath).pathname
|
|
ck = await CanvasKitInit({
|
|
locateFile: (file) => binDir + file
|
|
})
|
|
return ck
|
|
}
|
|
|
|
export async function loadDocument(filePath: string): Promise<SceneGraph> {
|
|
const data = await Bun.file(filePath).arrayBuffer()
|
|
const graph = await parseFigFile(data)
|
|
computeAllLayouts(graph)
|
|
return graph
|
|
}
|
|
|
|
export async function loadFonts(graph: SceneGraph): Promise<void> {
|
|
const families = new Set<string>()
|
|
for (const node of graph.getAllNodes()) {
|
|
if (node.fontFamily) families.add(node.fontFamily)
|
|
}
|
|
for (const family of families) {
|
|
await loadFont(family)
|
|
}
|
|
}
|
|
|
|
function createRenderer(ckInstance: CanvasKit, width: number, height: number): SkiaRenderer {
|
|
const surface = ckInstance.MakeSurface(width, height)!
|
|
const renderer = new SkiaRenderer(ckInstance, surface)
|
|
renderer.viewportWidth = width
|
|
renderer.viewportHeight = height
|
|
renderer.dpr = 1
|
|
return renderer
|
|
}
|
|
|
|
export async function exportNodes(
|
|
graph: SceneGraph,
|
|
pageId: string,
|
|
nodeIds: string[],
|
|
options: { scale?: number; format?: ExportFormat; quality?: number }
|
|
): Promise<Uint8Array | null> {
|
|
const ckInstance = await initCanvasKit()
|
|
const renderer = createRenderer(ckInstance, 1, 1)
|
|
return renderNodesToImage(ckInstance, renderer, graph, pageId, nodeIds, {
|
|
scale: options.scale ?? 1,
|
|
format: options.format ?? 'PNG',
|
|
quality: options.quality
|
|
})
|
|
}
|
|
|
|
export async function exportThumbnail(
|
|
graph: SceneGraph,
|
|
pageId: string,
|
|
width: number,
|
|
height: number
|
|
): Promise<Uint8Array | null> {
|
|
const ckInstance = await initCanvasKit()
|
|
const renderer = createRenderer(ckInstance, width, height)
|
|
return renderThumbnail(ckInstance, renderer, graph, pageId, width, height)
|
|
}
|