openpencil/packages/scene-graph/src/shared-styles.ts
Danila Poyarkov 0bf74a418f feat(text): deepen typography controls
- Add case, justification, vertical alignment, truncation, max-lines, and OpenType controls

- Apply visual text case and measured vertical offsets in CanvasKit rendering

- Preserve nullable preview values, shared text styles, font demand, and .fig metadata

- Add panel, engine, and canvas visual coverage
2026-07-17 22:40:52 +03:00

74 lines
2.2 KiB
TypeScript

import type { SceneGraph, SceneNode, SharedStyle, SharedStyleKind, SharedStyleType } from './index'
const STYLE_REF_KEYS = {
fill: 'fillStyleId',
stroke: 'strokeStyleId',
text: 'textStyleId',
effect: 'effectStyleId',
grid: 'gridStyleId'
} as const satisfies Record<SharedStyleKind, keyof SceneNode>
const STYLE_TYPES = {
fill: 'FILL',
stroke: 'FILL',
text: 'TEXT',
effect: 'EFFECT',
grid: 'GRID'
} as const satisfies Record<SharedStyleKind, SharedStyleType>
const TEXT_STYLE_KEYS = new Set<keyof SceneNode>([
'fontFamily',
'fontWeight',
'italic',
'fontSize',
'lineHeight',
'letterSpacing',
'textDecoration',
'textCase',
'fontFeatures'
])
export function sharedStyleRefKey(kind: SharedStyleKind): (typeof STYLE_REF_KEYS)[SharedStyleKind] {
return STYLE_REF_KEYS[kind]
}
export function sharedStyleTypeForKind(kind: SharedStyleKind): SharedStyleType {
return STYLE_TYPES[kind]
}
export function getSharedStyles(graph: SceneGraph, kind: SharedStyleKind): SharedStyle[] {
const type = sharedStyleTypeForKind(kind)
const styles: SharedStyle[] = []
for (const node of graph.getAllNodes()) {
if (node.sharedStyleType !== type || !node.source.id) continue
styles.push({ id: node.source.id, nodeId: node.id, name: node.name, type })
}
return styles.sort((left, right) => left.name.localeCompare(right.name))
}
export function styleDetachmentChanges(
node: SceneNode,
changes: Partial<SceneNode>
): Partial<SceneNode> {
const next = { ...changes }
if ('fills' in changes && !('fillStyleId' in changes) && node.fillStyleId) {
next.fillStyleId = null
}
if ('strokes' in changes && !('strokeStyleId' in changes) && node.strokeStyleId) {
next.strokeStyleId = null
}
if ('effects' in changes && !('effectStyleId' in changes) && node.effectStyleId) {
next.effectStyleId = null
}
if ('layoutGrids' in changes && !('gridStyleId' in changes) && node.gridStyleId) {
next.gridStyleId = null
}
const changesTextStyle = (Object.keys(changes) as (keyof SceneNode)[]).some((key) =>
TEXT_STYLE_KEYS.has(key)
)
if (changesTextStyle && !('textStyleId' in changes) && node.textStyleId) {
next.textStyleId = null
}
return next
}