# grapheme-segmentation(技术计划) ## 1. 计划目标 基于 `spec.md` 与 `设计说明文档/文档换行算法.md v1.2.1`,在客户端侧(JS/TS)落地**可复用且确定性**的 TC grapheme cluster(字符簇)分割能力,用于 Text Wrap 的 TC tokens 生成,确保: - 不拆 surrogate pair、ZWJ、VS16、肤色修饰符、国旗(regional indicator flags)、组合字符(如 `é`) - 同输入同输出(clusters 内容与顺序完全一致) - 输出边界可作为 TC 断点边界(后续模块仅在 cluster 边界断行) - 提供可解释 meta(采用何种策略、是否降级) ## 2. 默认技术决策(本计划采用) > 说明:本模块要解决的是“分割口径”,不引入排版/搜索逻辑。 - **优先策略**:若运行环境支持 `Intl.Segmenter`,优先使用: - `new Intl.Segmenter('zh-Hant', { granularity: 'grapheme' })` - 取其 `segment(text)` 的 `segment` 字段作为 clusters - **兜底策略(两种实现路径,默认选 A)**: - **方案 A(推荐)**:引入轻量依赖 `grapheme-splitter` 作为 fallback,避免手写不完整的 Unicode 规则导致漏拆/误拆 - **方案 B(无依赖兜底)**:实现“最低可用”分割(满足文档列出的组合不拆),但不承诺覆盖所有 Unicode 边界规则(风险较高) > 本计划默认采用 **方案 A**。若后续明确“禁止新增依赖”,再切换到方案 B 并补齐更多回归。 ## 3. 目录与产物 本子模块目录: - `spec_kit/Text Wrap/modules/grapheme-segmentation/spec.md` - `spec_kit/Text Wrap/modules/grapheme-segmentation/plan.md`(本文) 建议未来代码落位(实现阶段落地): - `client/src/features/textWrap/grapheme/` - `segmentGraphemes.ts` - `strategies/intlSegmenter.ts` - `strategies/fallback.ts` - `__tests__/segmentGraphemes.test.ts` ## 4. API 设计(实现阶段的稳定契约) 实现一个最小可用纯函数: - `segmentGraphemes(text: string, mode: 'PREFERRED' | 'FALLBACK') => { clusters: string[]; meta: { strategy: 'INTL_SEGMENTER' | 'FALLBACK'; hadFallback: boolean } }` 设计约束: - `clusters.join('') === text`(不允许丢字符/改字符顺序) - `clusters` 为空数组时必须是 `text==''`(不允许把空白当成 cluster 误输出) ## 5. 实现步骤(按落地顺序) ### 5.1 优先策略:Intl.Segmenter - 检测 `globalThis.Intl?.Segmenter` 是否可用 - 若可用且 `mode='PREFERRED'`: - 使用 `granularity='grapheme'` 分割 - 输出 `meta.strategy='INTL_SEGMENTER'`,`hadFallback=false` 注意: - 需要确认 Expo/RN 的运行时是否始终具备 `Intl.Segmenter`(不同 JS 引擎/版本可能差异) - 即使可用,也必须通过回归样例验证“不拆”要求 ### 5.2 兜底策略:Fallback 当出现以下任一情况时进入 fallback: - `mode='FALLBACK'` - `Intl.Segmenter` 不存在 - `Intl.Segmenter` 运行抛错/返回异常结果(如空、丢字符) #### 方案 A:`grapheme-splitter` - 新增依赖:`grapheme-splitter` - 使用其分割能力输出 clusters - 输出 `meta.strategy='FALLBACK'`,`hadFallback=true` #### 方案 B:最低可用手写规则(仅在禁止依赖时启用) 实现最低要求: - 合并 surrogate pair - 合并 ZWJ sequence(`U+200D` 连接) - 合并 variation selector(如 `U+FE0F`) - 合并 skin tone modifier(`U+1F3FB..U+1F3FF`) - 合并 regional indicator flags(两两成对) - 合并组合字符(combining marks)与预组合等价形式(至少覆盖 `e\u0301`) 风险提示: - 该实现容易漏掉其他扩展 grapheme cluster 规则;需要更高的测试覆盖与持续维护 ## 6. 回归用例与测试计划(Vitest) ### 6.1 必须覆盖的样例(文档要求) 以下输入必须“不拆”为单个 cluster: - `👨‍👩‍👧‍👦` - `🇸🇬` - `👍🏽` - `😮‍💨` - `é`(至少覆盖 `e\u0301` 组合形式) 断言: - `clusters.length === 1` - `clusters[0] === input` ### 6.2 基础性质测试(建议) - **可逆性**:`clusters.join('') === input` - **确定性**:同输入多次调用输出完全一致 - **空字符串**:`'' -> []` ### 6.3 跨策略一致性(建议) 在支持 `Intl.Segmenter` 的环境中: - 同一输入在 `PREFERRED` 与 `FALLBACK` 两种模式下输出 clusters 应一致 - 若出现差异,必须新增回归样例并明确差异原因(并在上层通过 configVersion 治理) ## 7. 性能与安全 - 单次分割复杂度应接近 \(O(n)\) - 对超长文本(例如 > 2000 code units)建议在上层模块触发裁剪或打点(本模块仅保证不崩溃) - 任何异常必须被捕获并降级到 fallback(保证“可用性优先”) ## 8. 完成定义(DoD) - `segmentGraphemes()` 在本地可运行,并通过必测回归样例 - 输出 meta 能区分 `INTL_SEGMENTER` 与 `FALLBACK` - 单测覆盖:必测样例 + 可逆性 + 确定性 - 文档口径与实现一致(不拆要求、fallback 触发条件、返回结构)