Skip to main content
Question

[MCP] loadFontAsync fails for locally installed fonts

  • March 26, 2026
  • 28 replies
  • 1853 views

Show first post

28 replies

Aaron_Xu
  • Active Member
  • May 15, 2026

My current solution is, ask AI to fallback font family to inter, then use a script to restore text style batchly.
here is my script

```javascript
/**
* 代码作用描述:
* 该脚本用于自动化管理 Figma 中的文本样式,共分为两个阶段:
* 1. 重置实例样式:遍历选中的组件实例,将其内部的文本样式重置为对应主组件(Main Component)的默认样式,消除冗余覆盖。
* 2. 自动匹配绑定:扫描所选范围内的独立文本层(以及重置后的文本),根据字号、字重(Weight)和行高,
* 自动从本地或已使用的非 Inter 字体样式中寻找最匹配的项并进行重新绑定。
*/
const selections = figma.currentPage.selection;
if (selections.length === 0) {
figma.notify("请选择至少一个图层。");
return;
}

// 1. 获取所有可用的目标样式(本地 + 已使用的导入样式),过滤掉 Inter 样式
const availableStyles = new Map<string, TextStyle>();
const localStyles = await figma.getLocalTextStylesAsync();
localStyles.forEach(s => availableStyles.set(s.id, s));

try {
const pageTextNodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] });
for (const node of pageTextNodes) {
const styleId = node.textStyleId;
if (typeof styleId === 'string' && styleId !== '') {
if (!availableStyles.has(styleId)) {
const style = await figma.getStyleByIdAsync(styleId) as TextStyle | null;
if (style && style.type === 'TEXT') {
availableStyles.set(style.id, style);
}
}
}
}
} catch (e) {
console.warn("扫描已使用样式失败:", e);
}

const targetStyles = Array.from(availableStyles.values()).filter(s => s.fontName.family !== 'Inter');

// 2. 收集需要处理的节点(实例和独立文本)
const instances: InstanceNode[] = [];
const standaloneTexts: TextNode[] = [];

const traverse = (nodes: readonly SceneNode[]) => {
for (const node of nodes) {
if (node.type === 'INSTANCE') {
instances.push(node);
// 实例内部的文本由 Phase 1 统一处理,不再递归
} else if (node.type === 'TEXT') {
// 独立文本(非实例内部,ID 中不含分号)
if (!node.id.includes(';')) {
standaloneTexts.push(node);
}
}
// 递归处理容器(排除实例,因为 Phase 1 会内部处理)
if ('children' in node && node.type !== 'INSTANCE') {
traverse(node.children);
}
}
};
traverse(selections);

let resetCount = 0;
let matchedCount = 0;

// Phase 1: 重置实例中的文本样式(还原为组件默认)
for (const instance of instances) {
const mainComp = await instance.getMainComponentAsync();
if (!mainComp) continue;

const templateStyles = new Map<string, string | typeof figma.mixed>();
const templateTextNodes = mainComp.findAllWithCriteria({ types: ['TEXT'] });
for (const t of templateTextNodes) {
templateStyles.set(t.id, t.textStyleId);
}

const instanceTextNodes = instance.findAllWithCriteria({ types: ['TEXT'] });
for (const t of instanceTextNodes) {
const parts = t.id.split(';');
const templateId = parts[parts.length - 1];

if (templateStyles.has(templateId)) {
const styleId = templateStyles.get(templateId);
if (styleId !== figma.mixed && t.textStyleId !== styleId) {
await t.setTextStyleIdAsync(styleId as string);
resetCount++;
}
}
}
}

// Phase 2: 匹配并应用样式(针对独立文本或组件内的文本)
for (const textNode of standaloneTexts) {
if (textNode.hasMissingFont) continue;

const fontSize = textNode.fontSize;
const fontName = textNode.fontName;
const lineHeight = textNode.lineHeight;

if (fontSize === figma.mixed || fontName === figma.mixed || lineHeight === figma.mixed) {
continue;
}

const nodeFontStyle = (fontName as FontName).style.replace(/[\s-_]/g, '').toLowerCase();

// 寻找最佳匹配逻辑:
// 1. 首先找出所有字号和字重匹配的样式
const candidates = targetStyles.filter(style => {
if (style.fontSize !== fontSize) return false;
const styleFontStyle = style.fontName.style.replace(/[\s-_]/g, '').toLowerCase();
return styleFontStyle === nodeFontStyle;
});

if (candidates.length === 0) continue;

// 2. 在备选样式中,优先寻找行高也匹配的
let bestMatch = candidates.find(style => {
const styleLh = style.lineHeight;
const nodeLh = lineHeight as LineHeight;
if (styleLh.unit !== nodeLh.unit) return false;
if (styleLh.unit !== 'AUTO' && nodeLh.unit !== 'AUTO' && styleLh.value != null && nodeLh.value != null) {
if (Math.abs(styleLh.value - nodeLh.value) > 0.1) return false;
}
return true;
});

// 3. 如果没有行高完全匹配的,则退而求其次使用第一个字号字重匹配的样式
if (!bestMatch) {
bestMatch = candidates[0];
}

if (bestMatch && textNode.textStyleId !== bestMatch.id) {
await figma.loadFontAsync(textNode.fontName as FontName);
await textNode.setTextStyleIdAsync(bestMatch.id);
matchedCount++;
}
}

figma.notify(`完成:重置了 ${resetCount} 个实例文本,重新绑定了 ${matchedCount} 个独立文本。`);
```


tomoki
  • New Member
  • May 19, 2026

The workaround for now is using figma-console and its Figma Desktop Bridge plugin:


https://github.com/southleft/figma-console-mcp/tree/main

The setup is a bit convoluted, but it does work!


ramoses_
  • New Member
  • June 17, 2026

That can't be that hard to fix it. Figma acting like Adobe at this point taking too much time for something that is simple, avoiding the issue to make users pay more and not taking accountability with its decisions.