# Sketch fontWeight 数字 ≠ Flutter FontWeight 值

**问题标签**: `sketch`, `fontWeight`, `字重`, `PingFangSC`, `字体`, `设计稿还原`
**问题类型**: `UI 还原错误`
**严重程度**: 高
**节省时间**: 多轮调试 → 1 次确认

---

## 问题描述

从 Sketch 设计稿读取 `layer.style.fontWeight` 后，按 `值 × 100` 推算 Flutter FontWeight，导致字重比设计稿偏粗或偏细：

- 设计师说"这是常规字重"，但代码里写了 `FontWeight.w500`（实际应是 w400）
- 价格数值看起来异常加粗，代码里用了 `FontWeight.w800`（实际应是 w600）

## 问题根因

Sketch JS API 的 `style.fontWeight` 返回的是**字体变体的内部枚举索引**（5、6、8、9...），与 CSS/Flutter 标准字重体系（100 ~ 900）**无数学对应关系**，不能直接 `×100` 使用。

| Sketch `style.fontWeight` | 实际字体面 | Flutter 正确值 | 错误推断 |
|---|---|---|---|
| 5 | PingFangSC-Regular | `FontWeight.w400` | ~~w500~~ |
| 6 | PingFangSC-Medium | `FontWeight.w500` | ~~w600~~ |
| 8 | PingFangSC-Semibold | `FontWeight.w600` | **~~w800~~（最常见！）** |
| 9 | PingFangSC-Bold | `FontWeight.w700` | ~~w900~~ |

## 错误尝试（导致反复）

| 尝试 | 结果 |
|------|------|
| 直接 `fontWeight × 100` | 所有字重偏粗（w500→w600，w600→w800） |
| 猜测"5≈medium≈w500" | 有时正确，有时偏差一级 |
| 看字体名前半段"PingFangSC" | 没有变体信息，无法区分 Regular/Semibold |

## 正确方案

### 获取字体面完整名称（必须用 Sketch native API）

```javascript
// ❌ 错误 - 返回内部索引，含义不明确
const weight = layer.style.fontWeight; // 返回 8，≠ w800

// ✅ 正确 - 获取字体面完整名称
const fontName = String(layer.sketchObject.font().fontName());
// 返回 "PingFangSC-Semibold" → 映射到 FontWeight.w600
```

### 标准映射表

```javascript
// PingFangSC 字体映射（中文场景最常见）
const PINGFANG_WEIGHT_MAP = {
  'Ultralight': 'FontWeight.w100',
  'Thin':       'FontWeight.w200',
  'Light':      'FontWeight.w300',
  'Regular':    'FontWeight.w400',
  'Medium':     'FontWeight.w500',
  'Semibold':   'FontWeight.w600',
  'Bold':       'FontWeight.w700',
  'Heavy':      'FontWeight.w800',
  'Black':      'FontWeight.w900',
};

function getFlutterFontWeight(layer) {
  const fontName = String(layer.sketchObject.font().fontName());
  const variant = Object.keys(PINGFANG_WEIGHT_MAP).find(k => fontName.includes(k));
  return PINGFANG_WEIGHT_MAP[variant || 'Regular'];
}
```

### 批量提取文本样式脚本

```javascript
const sketch = require('sketch');
const page = sketch.getSelectedDocument().selectedPage;

function extractTextStyles(containerName) {
  const container = sketch.find(`[name="${containerName}"]`, page)[0];
  if (!container) return;

  function walkLayers(layers) {
    layers.forEach(layer => {
      if (layer.type === 'Text') {
        const fontName = String(layer.sketchObject.font().fontName());
        const variant = ['Regular','Medium','Semibold','Bold','Light'].find(k => fontName.includes(k)) || 'Regular';
        const weightMap = { Regular:'w400', Medium:'w500', Semibold:'w600', Bold:'w700', Light:'w300' };
        console.log(`"${layer.name}": fontSize=${layer.style.fontSize}, fontWeight=FontWeight.${weightMap[variant]}, fontFace=${fontName}`);
      }
      if (layer.layers) walkLayers(layer.layers);
    });
  }

  walkLayers(container.layers || []);
}

extractTextStyles('组件名称');
```

## 关键原则

1. **永远不要根据 `style.fontWeight` 数字直接推断 Flutter FontWeight**
2. **必须通过 `layer.sketchObject.font().fontName()` 获取字体面名称**
3. 拿到字体面名称（如 `PingFangSC-Semibold`）后，取变体部分（`Semibold`）做映射
4. 不同字体族（SF Pro、Roboto）的映射规则相同，变体名称是通用的
