> **本文回答**：字体加载 —— opentype 字体加载、默认字体、按需加载。

## 一、核心 API（按 vjmapext.d.ts 核对）

```js
// 加载内置默认字体（MapCadLayer 构造时已自动调用过一次，结果存 _fontReady）
await vjmapext.initDefaultFont();                    // Promise<boolean>

// 加载自定义 ttf/otf
await vjmapext.loadOpentypeFont('/fonts/NotoSansSC-Regular.otf', 'NotoSansSC');
// 或 layer 方法（同上，另会 forceFullRender；但已渲染过的文字有字形缓存，见坑 1）
await mapcadLayer.loadFont('/fonts/arial.ttf', 'Arial');

// 查已加载
vjmapext.getLoadedFontNames();                       // string[]
```

完成后即可在 `TextEnt.font` / `MTextEnt.font` 中引用字体名（属性名是 `font`，不是 `fontName`）。

## 二、默认字体与回退

- 未指定 `font` → 用"当前默认字体"：**最近一次成功加载的字体**（初始为内置 `simkai`；每次 `loadOpentypeFont`/`loadFont` 成功都会把它设为默认）
- 指定但未加载 → 同样回退到当前默认字体（字体名查找不区分大小写）

判一个字体是否已就绪——⚠️ `getLoadedFontNames()` 返回的是**小写化**后的注册名：

```js
const ready = vjmapext.getLoadedFontNames().includes('cad');   // 注册时传 'CAD'，这里也要查 'cad'
if (!ready) await vjmapext.loadOpentypeFont('/fonts/cad-full.ttf', 'CAD');
```

## 三、按需加载

字体文件通常大（10+ MB），建议分批：

```js
// 初始：默认字体够用
await vjmapext.initDefaultFont();

// 用到 CAD 文字时再加载大字库
async function ensureCadFont() {
  if (!vjmapext.getLoadedFontNames().includes('cad')) {   // 名字已小写化
    await vjmapext.loadOpentypeFont('/fonts/cad-full.ttf', 'CAD');
  }
}
```

## 四、子集化

超大字库推荐子集（TTF → 只保留用到的字形）：

- 打包阶段用 `fonttools pyftsubset` / `subfont` 生成子集
- 运行时用 `loadOpentypeFont` 加载子集，体积可从 15 MB 压到 500 KB

## 五、Web 字体 vs opentype

| 方式 | 场景 |
|---|---|
| 浏览器 `@font-face` | DOM 元素（Marker / Popup / HTML 面板） |
| `loadOpentypeFont` / `mapcadLayer.loadFont` | CAD `TextEnt` / `MTextEnt`（WebGL 绘制） |

二者互不相干：DOM 文字不需要 loadOpentypeFont；CAD 文字只认它。

## 六、辅助函数

包内还有 `measureTextWidth` / `estimateTextWidth` / `getTextWidth` / `getCharWidth` / `truncateTextToWidth` 等排版测量导出（签名见 d.ts），自定义实体排版文字时可用。

语义（v1.0.x 源码）：

- `measureTextWidth(text, size, fontName?)` = 纯字形宽：取字体 `getAdvanceWidth` 或逐 glyph `advanceWidth` 累加；`fontName` 未加载/不存在时**回退当前默认字体**测量，只有一个字体都没加载（内置 simkai 尚未就绪）时才返回 0。
- `getTextWidth` / `getCharWidth` = `measureTextWidth` 结果 >0 用之，否则回退 `estimateTextWidth` 估算。
- `estimateTextWidth` = 规则估算（非测量）：空格 `0.35×size`、CJK 全角 `0.95×size`、其他字符 `0.58×size`——拉丁宽窄字（W vs i）在估算与 simkai 下都**等宽**，别指望它区分。
- `truncateTextToWidth(text, maxWidth, size, fontName?)` 逐字累加 `getCharWidth`，超宽截断返回前缀。
- `initDefaultFont()` 幂等（内部 Promise 缓存），`getLoadedFontNames()` 返回已注册名数组（默认字体 `simkai`）。
- `loadOpentypeFont(url, name)` 失败返回 `false` 不抛错（坏 URL/非字体内容皆然），且 `Pn` 全局默认字体指针会被**成功加载的字体覆盖**——加载顺序影响后续未指定字体的渲染。

## 七、常见坑

1. **loadFont 不 await 直接 addEntity**：首次绘制时字体未就绪，用回退字体渲染。`TextEnt`/`MTextEnt` 把字形缓存在实体上（`_tessCache`），只有 `ent.changed()` 才清；`loadFont` 的 `forceFullRender` 只清渲染缓存——**后加载的字体不会自动替换已用回退字体渲染过的文字**，须对这些实体逐个 `ent.changed()`。最好先 `await` 字体再 `addEntity`。
2. **中文路径/空格**：浏览器对 URL 编码敏感；路径用 ASCII 并 URL-encode。
3. **跨域 CORS**：CDN 字体需服务端设置 `Access-Control-Allow-Origin`。
4. **woff2 子集浏览器兼容**：vjmapext 对 ttf/otf 支持最稳；woff/woff2 依赖浏览器。
5. **字体内存占用**：大字库加载后驻留；不需要的字体就别加载（1.0.1 没有 unloadFont，控制加载列表即可）。
