> **本文回答**：`TextEnt` 单行文字 / `MTextEnt` 多行文字 —— 高度、对齐、旋转、样式。

## 一、TextEnt 单行文字

### 创建（位置参数，不是 options 对象）

```js
// 构造签名：new TextEnt(insertionPoint?, textString?, height?)
const t = new vjmapext.TextEnt([100, 100], 'Hello vjmap', 20);
t.rotation = 0;                    // 度
t.color = 0xff0000;
mapcadLayer.addEntity(t);
```

### 属性（d.ts 真实成员）

| 属性 | 类型 | 说明 |
|---|---|---|
| `insertionPoint` | `Point2D` | 插入点（注意：不叫 `position`） |
| `textString` | `string` | 文字内容（注意：不叫 `text`） |
| `height` | `number` | 高度（CAD 单位） |
| `rotation` | `number` | 旋转，**度** |
| `widthFactor` | `number` | 宽度因子，默认 1（⚠️ 1.0.1 渲染和 `toJSON` 都不使用，改了画面不变、存盘也丢） |
| `font` | `string` | 字体名（需预加载；不叫 `fontName`） |
| `textAlignment` | `TextAlignment` | 对齐枚举，见下 |
| `opacity` | `number` | 不透明度 |
| `tag` / `prompt` | `string?` | 属性定义标签/提示（ATTDEF 用） |
| `invisible` / `constant` / `preset` | `boolean?` | 属性定义标志位 |

### 对齐：`textAlignment` 用枚举 `TextAlignmentEnum`（1–9）

```js
t.textAlignment = vjmapext.TextAlignmentEnum.MidCenter;  // 中心对齐到插入点
t.changed();                // 对齐改变字形排布（几何），styleChanged() 不生效
```

| 值 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 含义 | Left | Center | Right | MidLeft | **MidCenter** | MidRight | TopLeft | TopCenter | TopRight |

### 查询

```js
t.boundingBox();              // 外接盒 BBox
t.height;                     // 高度
```

## 二、MTextEnt 多行文字

构造同样是位置参数；换行宽度属性叫 `maxWidth`（≤0 不换行），不是 `width`：

```js
const mt = new vjmapext.MTextEnt([0, 0], '第一行\n第二行', 18);  // 存储层用真实 \n 换行
mt.maxWidth = 300;               // 换行宽度，0 = 不换行
mt.lineSpacingFactor = 1.6;      // 行距系数，默认 1.6
mapcadLayer.addEntity(mt);
```

### ⚠️ 换行与"富文本"的真实支持度

`MTextEnt` 渲染时**只按真实换行符 `\n` 分行**（再按 `maxWidth` 自动换行），**不解析任何内联 MText 格式码**。直接把 `\\P`、`{\\C1;…}`、`{\\L…\\l}`、`{\\S…/…;}`、`\\U+XXXX` 写进 `textString`，会按字面字符画出来（屏幕上真的显示 "\P"、"{\C1;…}"）。

`\\P` 只在**交互输入路径**有效：MTEXT 命令输入框、属性面板的文字编辑会把 `\\P`/`\\n` 转成 `\n` 再存入 `textString`。程序化赋值请直接用 `'\n'`。

逐字符变色/下划线/堆叠等排版能力当前未实现；要局部样式只能拆多个 `TextEnt`。

### 段落对齐：`attachmentPoint` 用枚举 `MTextAttachmentEnum`（1–9）

```js
mt.attachmentPoint = vjmapext.MTextAttachmentEnum.MidCenter;  // =5
```

编号布局与 `TextAlignmentEnum` **相同**（3×3 九宫格，从下往上数）：MText 是 `1=BottomLeft … 3=BottomRight, 4=MidLeft … 6=MidRight, 7=TopLeft … 9=TopRight`。区别只在第一行的含义和名字：TEXT 的 1–3（`Left/Center/Right`）对齐到**基线**，MTEXT 的 1–3（`BottomLeft/…`）对齐到文字块**底边**。**建议只用枚举名**。

## 三、字体加载

vjmapext 默认启用 opentype 子集渲染，字体需要预先加载：

```js
await vjmapext.loadOpentypeFont('/fonts/simsun.ttf', '宋体');
await vjmapext.loadOpentypeFont('/fonts/arial.ttf', 'Arial');
// 或 mapcadLayer.loadFont(url, fontName?)
```

未加载的字体自动回退到默认字体。相关工具函数：`getLoadedFontNames()`、`measureTextWidth(text, size, fontName)`、`truncateTextToWidth(...)`、`initDefaultFont()`。详见 [font-text](font-text.md)。

## 四、动态更新

```js
t.textString = '动态更新 ' + Date.now();
t.changed();                  // 内容变了用 changed
t.color = 0x00ff00;
t.styleChanged();             // 只改样式
```

## 五、查找文字

```js
mapcadLayer.getEntities()
  .filter(e => e.type === 'TEXT' && e.textString.includes('关键字'))
  .forEach(e => { e.color = 0xffff00; e.styleChanged(); });
```

## 六、场景速查

| 场景 | 推荐 |
|---|---|
| 单行标签 / 编号 | `TextEnt` |
| 带换行的注释 | `MTextEnt` |
| 海量标签（图面文字） | [symbol/text-symbol](../../../vjmap/references/symbol/text-symbol.md) |
| 可交互的 HTML 文字 | [marker](../../../vjmap/references/marker/overview.md) |

## 七、常见坑

1. **构造是位置参数**：`new TextEnt(pos, str, height)`——传 options 对象不报错，但 `insertionPoint` 变成 `Point2D(0,0)`（`toPoint2D` 取 `p[0]/p[1]`=undefined 被构造默认值兜底）、`textString` 为空的实体。
2. **属性名**：`textString`/`insertionPoint`/`font`，不是 `text`/`position`/`fontName`。
3. **rotation 单位**：度，不是弧度。
4. **两个对齐枚举名字不同、布局相同**：都是 1–3 下行、4–6 中行、7–9 上行；但 `TextAlignmentEnum` 1=Left（基线），`MTextAttachmentEnum` 1=BottomLeft（块底边）——用枚举名别用数字字面量。
5. **MText 只认 `\n`**：`textString` 里写 `'\\P'` 不会换行（画成字面 "\P"）；交互输入框才做 `\\P`→`\n` 转换。程序化构造用 `'第一行\n第二行'`。
6. **字体未加载**：画面出现方块或默认字体；确认 `loadOpentypeFont` 完成再 add。
7. **height 过小**：屏幕上看不见；height 是 CAD 单位，会随 zoom 变大变小。
