Skip to main content
Question

[MCP] loadFontAsync fails for locally installed fonts

  • March 26, 2026
  • 26 replies
  • 1176 views

Show first post

26 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} 个独立文本。`);
```