fix(kiwi): support raw OpenType toggles

This commit is contained in:
Danila Poyarkov 2026-05-26 12:01:03 +03:00
parent 4cd187c766
commit f77f58e2f6
4 changed files with 46 additions and 11 deletions

View file

@ -6,18 +6,35 @@ const LIGATURE_FEATURES = [
['fontVariantContextualLigatures', 'CALT']
] as const
function addFeature(features: FontFeature[], tag: string, enabled: boolean): void {
const normalizedTag = tag.toUpperCase()
if (features.some((feature) => feature.tag === normalizedTag)) return
features.push({ tag: normalizedTag, enabled })
}
export function convertFontFeatures(nc: NodeChange): FontFeature[] {
const features: FontFeature[] = []
for (const [field, tag] of LIGATURE_FEATURES) {
const enabled = nc[field]
if (enabled !== undefined) features.push({ tag, enabled })
if (enabled !== undefined) addFeature(features, tag, enabled)
}
for (const tag of nc.toggledOnOTFeatures ?? []) addFeature(features, tag, true)
for (const tag of nc.toggledOffOTFeatures ?? []) addFeature(features, tag, false)
return features
}
export function applyFontFeaturesToKiwi(nc: NodeChange, features: FontFeature[]): void {
const toggledOn: string[] = []
const toggledOff: string[] = []
for (const feature of features) {
if (feature.tag === 'LIGA') nc.fontVariantCommonLigatures = feature.enabled
else if (feature.tag === 'CALT') nc.fontVariantContextualLigatures = feature.enabled
const tag = feature.tag.toUpperCase()
if (tag === 'LIGA') nc.fontVariantCommonLigatures = feature.enabled
else if (tag === 'CALT') nc.fontVariantContextualLigatures = feature.enabled
else if (feature.enabled) toggledOn.push(tag)
else toggledOff.push(tag)
}
if (toggledOn.length > 0) nc.toggledOnOTFeatures = toggledOn
if (toggledOff.length > 0) nc.toggledOffOTFeatures = toggledOff
}

View file

@ -157,7 +157,7 @@ Figma's design documentation groups features into these areas:
| Text case | ✅ | ◐ | — | ✅ | ✅ | Model/export/JSX support; UI missing. |
| Vertical text alignment | ✅ | ◐ | — | ✅ | ✅ | Modeled; UI/render parity needs more coverage. |
| Justified text | ✅ | ◐ | — | ✅ | ✅ | Modeled; UI does not expose it. |
| Font variations / OpenType features | ✅ | ✅ | — | ✅ | — | Imported `fontVariations` and common ligature toggles are applied to CanvasKit text styles and exported; broader OpenType controls are not exposed. |
| Font variations / OpenType features | ✅ | ✅ | — | ✅ | — | Imported `fontVariations`, common ligature toggles, and raw `toggledOnOTFeatures` / `toggledOffOTFeatures` are applied to CanvasKit text styles and exported; UI controls are not exposed. |
| Variables: collections/modes/aliases | ✅ | ◐ | ◐ | ✅ | ✅ | Color/number/string/boolean model exists; inspector coverage is still incomplete. |
| Variables bound to fills/strokes | ✅ | ✅ | ✅ | ✅ | ✅ | Common color bindings render and edit. |
| Variables bound to text/layout/visibility/effects | ◐ | ◐ | ◐ | ◐ | ✅ | Some bindings exist; not full Figma property coverage. |
@ -206,7 +206,7 @@ These are parsed or visible in Figma docs and most likely to cause visible diffe
1. **Masks** — tune remaining exact Figma stack semantics beyond common alpha/vector/luminance and consecutive-mask paths.
2. **Corner smoothing** — expand Figma fixture comparisons and tune remaining stroke/effect edge cases.
3. **Pattern/noise/custom fills** — replace the current solid-color fallback with Figma-oracle rendering for schema-level paint objects and transforms beyond image tile fills.
4. **Variable-font and rich text fixtures** — broaden real-file coverage for variable axes, derived text data, leading trim, decoration style, semantic font metadata, and additional OpenType feature metadata beyond common ligature toggles.
4. **Variable-font and rich text fixtures** — broaden real-file coverage for variable axes, derived text data, leading trim, decoration style, semantic font metadata, and raw OpenType feature metadata.
5. **Boolean operation editing** — improve inspector/tooling workflows for imported boolean-operation nodes.
6. **Layout grids and guides** — render/edit page guides and Figma layout grids, or clearly keep them round-trip-only.
7. **Full component property and slot workflows** — support authoring, not just preserving imported payloads.

View file

@ -10,14 +10,21 @@ describe('Figma font variation export', () => {
const text = graph.createNode('TEXT', page.id, {
text: 'Axis',
fontVariations: [{ axis: 'wght', value: 650 }],
fontFeatures: [{ tag: 'LIGA', enabled: false }],
fontFeatures: [
{ tag: 'LIGA', enabled: false },
{ tag: 'DLIG', enabled: true },
{ tag: 'KERN', enabled: false }
],
styleRuns: [
{
start: 0,
length: 2,
style: {
fontVariations: [{ axis: 'wdth', value: 88 }],
fontFeatures: [{ tag: 'CALT', enabled: false }]
fontFeatures: [
{ tag: 'CALT', enabled: false },
{ tag: 'SS01', enabled: true }
]
}
}
]
@ -31,9 +38,12 @@ describe('Figma font variation export', () => {
])
expect(nodeChange.fontVariantCommonLigatures).toBe(false)
expect(nodeChange.fontVariantContextualLigatures).toBe(true)
expect(nodeChange.toggledOnOTFeatures).toEqual(['DLIG'])
expect(nodeChange.toggledOffOTFeatures).toEqual(['KERN'])
expect(nodeChange.textData?.styleOverrideTable?.[0]?.fontVariations).toEqual([
{ axisTag: 0x77647468, axisName: 'wdth', value: 88 }
])
expect(nodeChange.textData?.styleOverrideTable?.[0]?.fontVariantContextualLigatures).toBe(false)
expect(nodeChange.textData?.styleOverrideTable?.[0]?.toggledOnOTFeatures).toEqual(['SS01'])
})
})

View file

@ -29,14 +29,18 @@ describe('Figma font variation import', () => {
type: 'TEXT',
textData: { characters: 'Ligatures' },
fontVariantCommonLigatures: false,
fontVariantContextualLigatures: true
fontVariantContextualLigatures: true,
toggledOnOTFeatures: ['DLIG'],
toggledOffOTFeatures: ['KERN']
} as NodeChange,
[]
)
expect(props.fontFeatures).toEqual([
{ tag: 'LIGA', enabled: false },
{ tag: 'CALT', enabled: true }
{ tag: 'CALT', enabled: true },
{ tag: 'DLIG', enabled: true },
{ tag: 'KERN', enabled: false }
])
})
@ -51,7 +55,8 @@ describe('Figma font variation import', () => {
{
styleID: 1,
fontVariations: [{ axisName: 'GRAD', value: -50 }],
fontVariantCommonLigatures: false
fontVariantCommonLigatures: false,
toggledOnOTFeatures: ['SS01']
} as NodeChange
]
}
@ -63,7 +68,10 @@ describe('Figma font variation import', () => {
length: 2,
style: {
fontVariations: [{ axis: 'GRAD', value: -50 }],
fontFeatures: [{ tag: 'LIGA', enabled: false }]
fontFeatures: [
{ tag: 'LIGA', enabled: false },
{ tag: 'SS01', enabled: true }
]
}
}
])