# 符号系统

符号库的创建、管理、插入完整指南。支持本地存储和服务端存储两种方式。

## 概览

| 存储方式 | 服务 | 特点 |
|---------|------|------|
| 本地存储 | `getLocalStorageService()` | IndexedDB 存储，离线可用 |
| 服务端存储 | `Engine.service` | 云端存储，需要网络和权限 |

---

## 创建符号

### 本地符号

```javascript
const { 
    LineEnt, CircleEnt, Engine, 
    getLocalStorageService, 
    buildSymbolVcadData, 
    generateThumbnailFromEntities 
} = vjcad;

// 1. 创建实体作为符号内容
const line1 = new LineEnt([-10, 0], [10, 0]);
const line2 = new LineEnt([0, -10], [0, 10]);
const circle = new CircleEnt([0, 0], 8);

line1.setDefaults();
line2.setDefaults();
circle.setDefaults();

// 2. 获取本地存储服务
const localService = getLocalStorageService();

// 3. 创建或获取分类
let categories = await localService.getLocalSymbolCategories();
let category = categories.find(c => c.name === "我的符号");

if (!category) {
    category = await localService.createLocalSymbolCategory("我的符号");
}

// 4. 构建符号数据
const entities = [line1, line2, circle];
const vcadData = await buildSymbolVcadData(entities, false);

// 5. 生成缩略图
const thumbnail = await generateThumbnailFromEntities(entities, 128, 128);

// 6. 保存符号
const symbol = await localService.saveLocalSymbol({
    categoryId: category.id,
    name: "十字圆符号",
    basePoint: [0, 0],      // 符号基点（插入时的参考点）
    thumbnail: thumbnail,
    vcadData: vcadData
});

console.log("符号已保存，ID:", symbol.id);
```

### 服务端符号

```javascript
const { 
    LineEnt, Engine,
    buildSymbolVcadData,
    compressSymbolData,
    uint8ArrayToBase64,
    generateThumbnailFromEntities
} = vjcad;

// 1. 创建实体
const line1 = new LineEnt([0, 0], [20, 0]);
const line2 = new LineEnt([20, 0], [15, 3]);
const line3 = new LineEnt([20, 0], [15, -3]);

line1.setDefaults();
line2.setDefaults();
line3.setDefaults();

// 2. 获取服务
const service = Engine.service;

// 3. 获取或创建分类
const categories = await service.getSymbolCategories();
let category = categories.find(c => c.name === "用户自定义");

if (!category) {
    category = await service.createSymbolCategory("用户自定义");
}

// 4. 构建符号数据
const entities = [line1, line2, line3];
const vcadData = await buildSymbolVcadData(entities, false);

// 5. 压缩数据（服务端存储需要压缩）
const compressedData = await compressSymbolData(vcadData, 6);
const base64Data = uint8ArrayToBase64(compressedData);

// 6. 生成缩略图
const thumbnail = await generateThumbnailFromEntities(entities, 128, 128);

// 7. 保存符号
const symbol = await service.createSymbol({
    categoryId: category.id,
    name: "箭头符号",
    basePoint: [0, 0],
    thumbnail: thumbnail,
    data: base64Data
});

console.log("符号已保存，ID:", symbol.id);
```

---

## 插入符号

### 指定参数插入

```javascript
const { 
    Point2D, Engine,
    getLocalStorageService,
    parseSymbolVcadData,
    mergeSymbolToDocument,
    regen
} = vjcad;

// 1. 获取符号数据
const localService = getLocalStorageService();
const categories = await localService.getLocalSymbolCategories();
const symbols = await localService.getLocalSymbolList(categories[0].id);
const symbolData = await localService.getLocalSymbol(symbols[0].id);

// 2. 解析符号数据
const symbolDoc = await parseSymbolVcadData(symbolData.vcadData);

// 3. 插入参数
const insertPoint = new Point2D(50, 50);  // 插入点
const basePoint = symbolData.record.basePoint;   // 符号基点（getLocalSymbol 返回 {record, vcadData}，basePoint 在 record 上）
const scale = 1.5;                         // 缩放比例
const rotation = 45;                       // 旋转角度（度）

// 4. 合并到当前文档
const addedEntities = mergeSymbolToDocument(
    symbolDoc,
    insertPoint,
    basePoint,
    scale,
    rotation,
    false  // 不清空来源图信息
);

console.log("已插入实体数:", addedEntities.length);

// 5. 刷新视图
regen();
Engine.zoomExtents();
```

### 服务端符号插入

```javascript
const { 
    Point2D, Engine,
    decompressSymbolData,
    parseSymbolVcadData,
    mergeSymbolToDocument
} = vjcad;

const service = Engine.service;

// 1. 获取符号列表
const categories = await service.getSymbolCategories();
const category = categories.find(c => c.name === "用户自定义");
const result = await service.getSymbolList(category.id);

// 2. 获取符号详情
const fullSymbol = await service.getSymbol(result.list[0].id);

// 3. 解压数据
const vcadData = await decompressSymbolData(fullSymbol.data);

// 4. 解析并插入
const symbolDoc = await parseSymbolVcadData(vcadData);
const insertPoint = new Point2D(100, 100);

const addedEntities = mergeSymbolToDocument(
    symbolDoc,
    insertPoint,
    fullSymbol.basePoint,
    1,    // 缩放
    0,    // 旋转
    false
);
```

### 交互式插入

```javascript
const { 
    Engine, 
    getLocalStorageService,
    insertSymbolInteractive,
    CommandRegistry, CommandDefinition, CommandOptions
} = vjcad;

class InsertSymbolCommand {
    async main() {
        // 1. 获取符号数据
        const localService = getLocalStorageService();
        const categories = await localService.getLocalSymbolCategories();
        const symbols = await localService.getLocalSymbolList(categories[0].id);
        const symbolData = await localService.getLocalSymbol(symbols[0].id);

        // 2. 交互式插入
        // 会依次提示用户指定：插入点、缩放比例、旋转角度
        const entities = await insertSymbolInteractive(
            symbolData.vcadData,
            symbolData.record.basePoint,   // 不是 symbolData.basePoint（undefined → 内部 basePoint[0] 抛错）
            {
                allowScale: true,      // 允许指定缩放
                allowRotation: true,   // 允许指定旋转
                clearSourceInfo: false
            }
        );

        if (entities) {
            console.log("符号插入成功，实体数量:", entities.length);
            Engine.zoomExtents();
        } else {
            console.log("符号插入已取消");
        }
    }
}

// 注册命令
const cmdDef = new CommandDefinition('MYINSERTSYMBOL', '交互式插入符号', InsertSymbolCommand);
CommandRegistry.regist(cmdDef);

// 执行命令
await Engine.editor.executerWithOp('MYINSERTSYMBOL');
```

---

## 分类管理

### 本地分类

```javascript
const localService = getLocalStorageService();

// 获取所有分类
const categories = await localService.getLocalSymbolCategories();

// 创建分类
const newCategory = await localService.createLocalSymbolCategory("新分类");

// 更新分类名称
await localService.updateLocalSymbolCategory(newCategory.id, "新名称");

// 删除分类
await localService.deleteLocalSymbolCategory(newCategory.id);
```

### 服务端分类

```javascript
const service = Engine.service;

// 获取所有分类
const categories = await service.getSymbolCategories();

// 创建分类（需要权限）
const newCategory = await service.createSymbolCategory("新分类");

// 更新分类名称
await service.updateSymbolCategory(newCategory.id, "新名称");

// 删除分类
await service.deleteSymbolCategory(newCategory.id);
```

---

## 符号管理

### 本地符号管理

```javascript
const localService = getLocalStorageService();

// 获取符号列表
const symbols = await localService.getLocalSymbolList(categoryId);

// 获取符号详情：返回 { record, vcadData } | null（basePoint/name 等元数据在 detail.record 上）
const detail = await localService.getLocalSymbol(symbolId);

// 删除符号
await localService.deleteLocalSymbol(symbolId);

// 获取统计信息
const stats = await localService.getLocalSymbolStats();
console.log("分类数:", stats.categoryCount);
console.log("符号数:", stats.symbolCount);
console.log("总大小:", stats.totalSize);
```

### 服务端符号管理

```javascript
const service = Engine.service;

// 获取符号列表（支持分页和搜索）
const result = await service.getSymbolList(categoryId, {
    page: 1,
    pageSize: 10,
    keyword: "箭头"  // 可选：关键字搜索
});

console.log("总数:", result.total);
console.log("当前页:", result.page);
console.log("列表:", result.list);

// 获取符号详情（包含压缩的 data）
const fullSymbol = await service.getSymbol(symbolId);

// 删除符号（需要权限）
await service.deleteSymbol(symbolId);
```

---

## 工具函数

| 函数 | 说明 |
|------|------|
| `buildSymbolVcadData(entities, clearSourceInfo)` | 将实体数组转换为符号数据 |
| `generateThumbnailFromEntities(entities, width, height)` | 生成缩略图 |
| `parseSymbolVcadData(vcadData)` | 解析符号数据为 CadDocument |
| `mergeSymbolToDocument(doc, insertPt, basePt, scale, rotation, clearInfo)` | 合并符号到当前文档 |
| `insertSymbolInteractive(vcadData, basePoint, options)` | 交互式插入符号 |
| `compressSymbolData(data, level)` | 压缩符号数据（服务端存储用）|
| `decompressSymbolData(base64Data)` | 解压符号数据 |
| `uint8ArrayToBase64(uint8Array)` | Uint8Array 转 Base64 |

---

## 符号数据格式

| 字段 | 说明 |
|------|------|
| `id` | 符号 ID |
| `name` | 符号名称 |
| `categoryId` | 所属分类 ID |
| `basePoint` | 符号基点 `[x, y]` |
| `thumbnail` | 缩略图（Base64 或 Data URL）|
| `vcadData` / `data` | 符号数据（本地为原始数据，服务端为压缩后的 Base64）|
| `createTime` | 创建时间 |

---

## 完整示例：符号库命令

```javascript
const { 
    Engine, CommandRegistry, CommandDefinition, CommandOptions,
    getLocalStorageService, insertSymbolInteractive,
    showSelectDialog, message
} = vjcad;

class SymbolLibraryCommand {
    async main() {
        const localService = getLocalStorageService();
        
        // 1. 获取所有分类
        const categories = await localService.getLocalSymbolCategories();
        
        if (categories.length === 0) {
            message.error("没有符号分类");
            return;
        }
        
        // 2. 选择分类（showSelectDialog 的 value 必须是字符串：未改选时内部会对默认值 .trim()，数字会抛错；
        //    结果 value 也总是字符串，而本地库的 id/categoryId 是数字，需转回 Number）
        const categoryResult = await showSelectDialog({
            title: "选择符号分类",
            label: "请选择分类:",
            options: categories.map(c => ({ value: String(c.id), label: c.name }))
        });
        
        if (!categoryResult.confirmed) return;
        
        // 3. 获取符号列表
        const symbols = await localService.getLocalSymbolList(Number(categoryResult.value));
        
        if (symbols.length === 0) {
            message.error("该分类下没有符号");
            return;
        }
        
        // 4. 选择符号
        const symbolResult = await showSelectDialog({
            title: "选择符号",
            label: "请选择要插入的符号:",
            options: symbols.map(s => ({ value: String(s.id), label: s.name }))
        });
        
        if (!symbolResult.confirmed) return;
        
        // 5. 获取符号详情
        const symbolId = Number(symbolResult.value);
        const symbolData = await localService.getLocalSymbol(symbolId);
        const symbolMeta = symbols.find(s => s.id === symbolId);
        
        // 6. 交互式插入
        const entities = await insertSymbolInteractive(
            symbolData.vcadData,
            symbolMeta.basePoint,
            { allowScale: true, allowRotation: true }
        );
        
        if (entities) {
            message.info(`已插入符号: ${symbolMeta.name}`);
            Engine.zoomExtents();
        }
    }
}

// 注册命令
CommandRegistry.regist(new CommandDefinition(
    'MYSYMBOLLIBRARY', 
    '符号库', 
    SymbolLibraryCommand
));
```
