ice-render 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +22 -9
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.mjs +3 -3
  4. package/dist/index.umd.js +3 -3
  5. package/dist/types/FrameManager.d.mts +16 -1
  6. package/dist/types/FrameManager.d.ts +16 -1
  7. package/dist/types/ICE.d.mts +103 -0
  8. package/dist/types/ICE.d.ts +103 -0
  9. package/dist/types/animation/AnimationManager.d.mts +117 -16
  10. package/dist/types/animation/AnimationManager.d.ts +117 -16
  11. package/dist/types/animation/AnimationTimeline.d.mts +77 -0
  12. package/dist/types/animation/AnimationTimeline.d.ts +77 -0
  13. package/dist/types/animation/easing-registry.d.mts +37 -0
  14. package/dist/types/animation/easing-registry.d.ts +37 -0
  15. package/dist/types/animation/interpolators.d.mts +34 -0
  16. package/dist/types/animation/interpolators.d.ts +34 -0
  17. package/dist/types/animation/validate-animations.d.mts +55 -0
  18. package/dist/types/animation/validate-animations.d.ts +55 -0
  19. package/dist/types/event/DOMEventDispatcher.d.mts +8 -0
  20. package/dist/types/event/DOMEventDispatcher.d.ts +8 -0
  21. package/dist/types/export/SvgExporter.d.mts +9 -2
  22. package/dist/types/export/SvgExporter.d.ts +9 -2
  23. package/dist/types/export/compose-layers.d.mts +30 -0
  24. package/dist/types/export/compose-layers.d.ts +30 -0
  25. package/dist/types/graphic/ICEComponent.d.mts +37 -1
  26. package/dist/types/graphic/ICEComponent.d.ts +37 -1
  27. package/dist/types/graphic/container/ICEGroup.d.mts +3 -1
  28. package/dist/types/graphic/container/ICEGroup.d.ts +3 -1
  29. package/dist/types/graphic/link/ICEPolyLine.d.mts +9 -1
  30. package/dist/types/graphic/link/ICEPolyLine.d.ts +9 -1
  31. package/dist/types/graphic/shape/ICEEllipse.d.mts +3 -1
  32. package/dist/types/graphic/shape/ICEEllipse.d.ts +3 -1
  33. package/dist/types/graphic/shape/ICEIsogon.d.mts +3 -1
  34. package/dist/types/graphic/shape/ICEIsogon.d.ts +3 -1
  35. package/dist/types/graphic/shape/ICERose.d.mts +3 -1
  36. package/dist/types/graphic/shape/ICERose.d.ts +3 -1
  37. package/dist/types/graphic/shape/ICEStar.d.mts +3 -1
  38. package/dist/types/graphic/shape/ICEStar.d.ts +3 -1
  39. package/dist/types/graphic/text/ICEText.d.mts +154 -2
  40. package/dist/types/graphic/text/ICEText.d.ts +154 -2
  41. package/dist/types/graphic/text/text-direction.d.mts +28 -0
  42. package/dist/types/graphic/text/text-direction.d.ts +28 -0
  43. package/dist/types/graphic/text/text-style.d.mts +25 -0
  44. package/dist/types/graphic/text/text-style.d.ts +25 -0
  45. package/dist/types/graphic/text/text-wrap.d.mts +26 -0
  46. package/dist/types/graphic/text/text-wrap.d.ts +26 -0
  47. package/dist/types/index.d.mts +11 -0
  48. package/dist/types/index.d.ts +11 -0
  49. package/dist/types/util/data-util.d.mts +10 -0
  50. package/dist/types/util/data-util.d.ts +10 -0
  51. package/dist/types/util/errors.d.mts +72 -0
  52. package/dist/types/util/errors.d.ts +72 -0
  53. package/package.json +5 -3
@@ -50,5 +50,12 @@ export type SvgExportResult = {
50
50
  *
51
51
  * @param target ICE 实例(导出整幅画布)或任意组件(导出它的子树)
52
52
  */
53
- export declare function exportSvg(target: any, options?: SvgExportOptions): string;
54
- export declare function exportSvgResult(target: any, options?: SvgExportOptions): SvgExportResult;
53
+ /**
54
+ * 导出场景为 SVG 字符串。
55
+ *
56
+ * `target` 可以是 **ICE 实例**、**任意组件**,也可以是**这两个的数组**(多层合成):
57
+ * 分层渲染(静态层 + 动画层)导出时按数组顺序叠加,各层的世界坐标共用同一套 `worldToView`。
58
+ * 单层(传单个 target)的输出与历史版本逐字节一致。
59
+ */
60
+ export declare function exportSvg(target: any | any[], options?: SvgExportOptions): string;
61
+ export declare function exportSvgResult(target: any | any[], options?: SvgExportOptions): SvgExportResult;
@@ -0,0 +1,30 @@
1
+ export type ComposeLayersOptions = {
2
+ /** 输出类型(`toDataURL` 的 MIME),默认 `image/png` */
3
+ type?: string;
4
+ /** 有损格式的质量 0~1(`image/jpeg` / `image/webp` 用) */
5
+ quality?: number;
6
+ /** 背景色(如 `'#ffffff'`)。缺省不铺背景 → 保留透明度(分层图通常需要透明背景) */
7
+ background?: string | null;
8
+ /** 输出尺寸(缺省 = 各层 canvas 中最大的那一份,单位:**设备像素**) */
9
+ width?: number;
10
+ height?: number;
11
+ };
12
+ /**
13
+ * 把多层 canvas 叠到一张离屏画布上(按数组顺序,第一层在最下面)。
14
+ *
15
+ * @returns `{ canvas, ctx, width, height }`;运行时没有离屏画布能力时抛 `ICE_UNSUPPORTED_RUNTIME`。
16
+ */
17
+ export declare function composeLayersToCanvas(layers: any[], options?: ComposeLayersOptions): {
18
+ canvas: any;
19
+ ctx: any;
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /**
24
+ * 多层合成为一张图(DataURL)。
25
+ *
26
+ * ```js
27
+ * const png = composeLayersDataURL([staticIce, animIce], { background: '#fff' });
28
+ * ```
29
+ */
30
+ export declare function composeLayersDataURL(layers: any[], options?: ComposeLayersOptions): string;
@@ -0,0 +1,30 @@
1
+ export type ComposeLayersOptions = {
2
+ /** 输出类型(`toDataURL` 的 MIME),默认 `image/png` */
3
+ type?: string;
4
+ /** 有损格式的质量 0~1(`image/jpeg` / `image/webp` 用) */
5
+ quality?: number;
6
+ /** 背景色(如 `'#ffffff'`)。缺省不铺背景 → 保留透明度(分层图通常需要透明背景) */
7
+ background?: string | null;
8
+ /** 输出尺寸(缺省 = 各层 canvas 中最大的那一份,单位:**设备像素**) */
9
+ width?: number;
10
+ height?: number;
11
+ };
12
+ /**
13
+ * 把多层 canvas 叠到一张离屏画布上(按数组顺序,第一层在最下面)。
14
+ *
15
+ * @returns `{ canvas, ctx, width, height }`;运行时没有离屏画布能力时抛 `ICE_UNSUPPORTED_RUNTIME`。
16
+ */
17
+ export declare function composeLayersToCanvas(layers: any[], options?: ComposeLayersOptions): {
18
+ canvas: any;
19
+ ctx: any;
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /**
24
+ * 多层合成为一张图(DataURL)。
25
+ *
26
+ * ```js
27
+ * const png = composeLayersDataURL([staticIce, animIce], { background: '#fff' });
28
+ * ```
29
+ */
30
+ export declare function composeLayersDataURL(layers: any[], options?: ComposeLayersOptions): string;
@@ -408,7 +408,43 @@ declare abstract class ICEComponent extends ICEEventTarget {
408
408
  *
409
409
  * @param newState
410
410
  */
411
- setState(newState: any): void;
411
+ setState(newState: any, options?: {
412
+ paramsDirty?: boolean;
413
+ }): void;
414
+ /**
415
+ * 「动画安全键」白名单:写这些 state 键**不会改变派生参数**(尺寸 / 点集 / 文本量测),
416
+ * 因此动画/高频写值可以跳过 `paramsDirty` —— 省一次重量测,并且保住离屏位图的纯平移复用。
417
+ *
418
+ * 判定方向很重要:**未声明的键一律当作"影响派生参数"**(保守)。这样漏判只会少一点优化,
419
+ * 不会让尺寸/点集停在旧值上;第三方组件不声明就等于维持旧行为(每帧都置脏)。
420
+ * 子类覆盖时应把基类的键并入(也允许更窄,例如文本把字号/字间距/行高那类量测相关 style 键排除在外)。
421
+ */
422
+ static readonly ANIMATION_SAFE_KEYS: readonly string[];
423
+ /**
424
+ * 运行时挂上/改写一条属性动画(免"必须在构造时声明 `animations`")。
425
+ *
426
+ * 关键细节:**写时复制**。没在构造时传 `animations` 的组件,`props.animations` 继承的是
427
+ * `DEFAULT_PROPS.animations`(**冻结的共享默认对象**)—— 直接 `props.animations[key] = cfg`
428
+ * 在严格模式下会抛 "Cannot add property …: object is not extensible"。这里先把默认对象复制成
429
+ * 实例自己的、可扩展的对象再写,并顺手把组件纳入 `AnimationManager`(否则动画不会推进)。
430
+ *
431
+ * ```js
432
+ * card.setAnimation('style.globalAlpha', { from: 0, to: 1, duration: 300 });
433
+ * ```
434
+ */
435
+ setAnimation(key: string, config: any): this;
436
+ /** 运行时摘掉一条属性动画(组件没在动画里就什么都不做)。 */
437
+ removeAnimation(key: string): this;
438
+ /** 该 state 键路径是否在「动画安全键」白名单里(`transform` 这类前缀按 `transform.xxx` 匹配)。 */
439
+ isAnimationSafeKey(path: string): boolean;
440
+ /**
441
+ * 按**类**查询某个 state 键路径是否"动画安全"(不需要实例)。
442
+ *
443
+ * 给下游用(DSL / Agent 侧校验):它们手里只有类型(`ICEText` / `ICERect`…),
444
+ * 没有也不该造实例 —— 但需要给出"这个属性动画会每帧重量测"这类性能提示。
445
+ * 判定口径与实例方法完全一致(只查静态白名单)。
446
+ */
447
+ static isAnimationSafeKeyFor(ctor: any, path: string): boolean;
412
448
  /**
413
449
  * `setState` 的**前置**钩子:必须在 `merge(this.state, newState)` 之前调用(要对比新旧值)。
414
450
  *
@@ -408,7 +408,43 @@ declare abstract class ICEComponent extends ICEEventTarget {
408
408
  *
409
409
  * @param newState
410
410
  */
411
- setState(newState: any): void;
411
+ setState(newState: any, options?: {
412
+ paramsDirty?: boolean;
413
+ }): void;
414
+ /**
415
+ * 「动画安全键」白名单:写这些 state 键**不会改变派生参数**(尺寸 / 点集 / 文本量测),
416
+ * 因此动画/高频写值可以跳过 `paramsDirty` —— 省一次重量测,并且保住离屏位图的纯平移复用。
417
+ *
418
+ * 判定方向很重要:**未声明的键一律当作"影响派生参数"**(保守)。这样漏判只会少一点优化,
419
+ * 不会让尺寸/点集停在旧值上;第三方组件不声明就等于维持旧行为(每帧都置脏)。
420
+ * 子类覆盖时应把基类的键并入(也允许更窄,例如文本把字号/字间距/行高那类量测相关 style 键排除在外)。
421
+ */
422
+ static readonly ANIMATION_SAFE_KEYS: readonly string[];
423
+ /**
424
+ * 运行时挂上/改写一条属性动画(免"必须在构造时声明 `animations`")。
425
+ *
426
+ * 关键细节:**写时复制**。没在构造时传 `animations` 的组件,`props.animations` 继承的是
427
+ * `DEFAULT_PROPS.animations`(**冻结的共享默认对象**)—— 直接 `props.animations[key] = cfg`
428
+ * 在严格模式下会抛 "Cannot add property …: object is not extensible"。这里先把默认对象复制成
429
+ * 实例自己的、可扩展的对象再写,并顺手把组件纳入 `AnimationManager`(否则动画不会推进)。
430
+ *
431
+ * ```js
432
+ * card.setAnimation('style.globalAlpha', { from: 0, to: 1, duration: 300 });
433
+ * ```
434
+ */
435
+ setAnimation(key: string, config: any): this;
436
+ /** 运行时摘掉一条属性动画(组件没在动画里就什么都不做)。 */
437
+ removeAnimation(key: string): this;
438
+ /** 该 state 键路径是否在「动画安全键」白名单里(`transform` 这类前缀按 `transform.xxx` 匹配)。 */
439
+ isAnimationSafeKey(path: string): boolean;
440
+ /**
441
+ * 按**类**查询某个 state 键路径是否"动画安全"(不需要实例)。
442
+ *
443
+ * 给下游用(DSL / Agent 侧校验):它们手里只有类型(`ICEText` / `ICERect`…),
444
+ * 没有也不该造实例 —— 但需要给出"这个属性动画会每帧重量测"这类性能提示。
445
+ * 判定口径与实例方法完全一致(只查静态白名单)。
446
+ */
447
+ static isAnimationSafeKeyFor(ctor: any, path: string): boolean;
412
448
  /**
413
449
  * `setState` 的**前置**钩子:必须在 `merge(this.state, newState)` 之前调用(要对比新旧值)。
414
450
  *
@@ -97,7 +97,9 @@ declare class ICEGroup extends ICERect {
97
97
  * setState 仅仅修改参数,不会立即导致重新渲染,需要等待 FrameManager 调度,最小延迟时间约为 1/60=16.67 ms 。
98
98
  * @param newState
99
99
  */
100
- setState(newState: any): void;
100
+ setState(newState: any, options?: {
101
+ paramsDirty?: boolean;
102
+ }): void;
101
103
  /**
102
104
  * @overwrite
103
105
  * @method destory
@@ -97,7 +97,9 @@ declare class ICEGroup extends ICERect {
97
97
  * setState 仅仅修改参数,不会立即导致重新渲染,需要等待 FrameManager 调度,最小延迟时间约为 1/60=16.67 ms 。
98
98
  * @param newState
99
99
  */
100
- setState(newState: any): void;
100
+ setState(newState: any, options?: {
101
+ paramsDirty?: boolean;
102
+ }): void;
101
103
  /**
102
104
  * @overwrite
103
105
  * @method destory
@@ -15,6 +15,12 @@ import ICEDotPath from '../ICEDotPath.mjs';
15
15
  * @author 大漠穷秋<damoqiongqiu@126.com>
16
16
  */
17
17
  declare class ICEPolyLine extends ICEDotPath {
18
+ /**
19
+ * 折线的「动画安全键」**不能**沿用基类那套:`setState({left})` 会就地平移 `points`,
20
+ * 而折线的 width/height 是从点集推出来的 —— 位置对它来说是**几何**,必须走 `paramsDirty`。
21
+ * 这里只放行纯绘制/容器语义的键。
22
+ */
23
+ static readonly ANIMATION_SAFE_KEYS: readonly string[];
18
24
  /**
19
25
  * 类型标识
20
26
  * 用来解决 TypeScript 的 instanceof 兼容性问题, https://github.com/microsoft/TypeScript/issues/22585
@@ -209,7 +215,9 @@ declare class ICEPolyLine extends ICEDotPath {
209
215
  * @overwrite
210
216
  * @param newState 新的状态
211
217
  */
212
- setState(newState: any): void;
218
+ setState(newState: any, options?: {
219
+ paramsDirty?: boolean;
220
+ }): void;
213
221
  /**
214
222
  * 插槽方向 → 单位方向向量(T=上/B=下/L=左/R=右,其余为无方向)。
215
223
  *
@@ -15,6 +15,12 @@ import ICEDotPath from '../ICEDotPath';
15
15
  * @author 大漠穷秋<damoqiongqiu@126.com>
16
16
  */
17
17
  declare class ICEPolyLine extends ICEDotPath {
18
+ /**
19
+ * 折线的「动画安全键」**不能**沿用基类那套:`setState({left})` 会就地平移 `points`,
20
+ * 而折线的 width/height 是从点集推出来的 —— 位置对它来说是**几何**,必须走 `paramsDirty`。
21
+ * 这里只放行纯绘制/容器语义的键。
22
+ */
23
+ static readonly ANIMATION_SAFE_KEYS: readonly string[];
18
24
  /**
19
25
  * 类型标识
20
26
  * 用来解决 TypeScript 的 instanceof 兼容性问题, https://github.com/microsoft/TypeScript/issues/22585
@@ -209,7 +215,9 @@ declare class ICEPolyLine extends ICEDotPath {
209
215
  * @overwrite
210
216
  * @param newState 新的状态
211
217
  */
212
- setState(newState: any): void;
218
+ setState(newState: any, options?: {
219
+ paramsDirty?: boolean;
220
+ }): void;
213
221
  /**
214
222
  * 插槽方向 → 单位方向向量(T=上/B=下/L=左/R=右,其余为无方向)。
215
223
  *
@@ -24,6 +24,8 @@ declare class ICEEllipse extends ICEPath {
24
24
  * @overwrite
25
25
  * @param newState
26
26
  */
27
- setState(newState: any): void;
27
+ setState(newState: any, options?: {
28
+ paramsDirty?: boolean;
29
+ }): void;
28
30
  }
29
31
  export default ICEEllipse;
@@ -24,6 +24,8 @@ declare class ICEEllipse extends ICEPath {
24
24
  * @overwrite
25
25
  * @param newState
26
26
  */
27
- setState(newState: any): void;
27
+ setState(newState: any, options?: {
28
+ paramsDirty?: boolean;
29
+ }): void;
28
30
  }
29
31
  export default ICEEllipse;
@@ -47,6 +47,8 @@ declare class ICEIsogon extends ICEDotPath {
47
47
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
48
48
  * @param newState
49
49
  */
50
- setState(newState: any): void;
50
+ setState(newState: any, options?: {
51
+ paramsDirty?: boolean;
52
+ }): void;
51
53
  }
52
54
  export default ICEIsogon;
@@ -47,6 +47,8 @@ declare class ICEIsogon extends ICEDotPath {
47
47
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
48
48
  * @param newState
49
49
  */
50
- setState(newState: any): void;
50
+ setState(newState: any, options?: {
51
+ paramsDirty?: boolean;
52
+ }): void;
51
53
  }
52
54
  export default ICEIsogon;
@@ -36,6 +36,8 @@ declare class ICERose extends ICEDotPath {
36
36
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
37
37
  * @param newState
38
38
  */
39
- setState(newState: any): void;
39
+ setState(newState: any, options?: {
40
+ paramsDirty?: boolean;
41
+ }): void;
40
42
  }
41
43
  export default ICERose;
@@ -36,6 +36,8 @@ declare class ICERose extends ICEDotPath {
36
36
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
37
37
  * @param newState
38
38
  */
39
- setState(newState: any): void;
39
+ setState(newState: any, options?: {
40
+ paramsDirty?: boolean;
41
+ }): void;
40
42
  }
41
43
  export default ICERose;
@@ -40,6 +40,8 @@ declare class ICEStar extends ICEDotPath {
40
40
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
41
41
  * @param newState
42
42
  */
43
- setState(newState: any): void;
43
+ setState(newState: any, options?: {
44
+ paramsDirty?: boolean;
45
+ }): void;
44
46
  }
45
47
  export default ICEStar;
@@ -40,6 +40,8 @@ declare class ICEStar extends ICEDotPath {
40
40
  * - 如果 setState 时仅仅指定 width 参数,则 radius 会被重新计算,如果仅仅指定了 height 参数,则 radius 会被重新计算。
41
41
  * @param newState
42
42
  */
43
- setState(newState: any): void;
43
+ setState(newState: any, options?: {
44
+ paramsDirty?: boolean;
45
+ }): void;
44
46
  }
45
47
  export default ICEStar;
@@ -5,6 +5,12 @@ import ICEComponent from '../ICEComponent.mjs';
5
5
  * @author 大漠穷秋<damoqiongqiu@126.com>
6
6
  */
7
7
  declare class ICEText extends ICEComponent {
8
+ /**
9
+ * 文本的「动画安全键」:基类那批(位置/变换/透明度/显示/zIndex)之外,再放行**不参与量测**的
10
+ * 绘制类与编辑类键;字号/字间距/行高/内边距/`font*`/文本内容/换行参数一概**不在**白名单里
11
+ * (它们都会改变盒子尺寸或换行结果 → 必须走 `paramsDirty`)。
12
+ */
13
+ static readonly ANIMATION_SAFE_KEYS: readonly string[];
8
14
  /**
9
15
  * @cfg
10
16
  * {
@@ -26,6 +32,28 @@ declare class ICEText extends ICEComponent {
26
32
  * @param props
27
33
  */
28
34
  constructor(props?: any);
35
+ /** 用户没显式给 width → 宽度按量测自适应 */
36
+ private __autoWidth;
37
+ /** 用户没显式给 height → 高度按量测自适应 */
38
+ private __autoHeight;
39
+ /**
40
+ * 逐行宽度缓存(第 8 项):量测时顺手记下(`__measureByCanvas` 本来就要逐行 measureText),
41
+ * 供居中 / 右对齐、文本装饰线、SVG 导出、光标与选区复用。
42
+ *
43
+ * 失效策略:`setState`(任何 state 变化都会置 `paramsDirty`)与 `remeasureText()` 清空;
44
+ * 另外缓存带 key(内容 + 字体 + 字间距),即使漏清也能自我纠正。
45
+ */
46
+ private __lineWidthCache;
47
+ /**
48
+ * 最近一次量测得到的**字形墨迹**上下沿(相对基线;来自 `actualBoundingBoxAscent/Descent`)
49
+ * 与**字体 em 盒**上下沿(`fontBoundingBox*`,部分运行时没有则按字号粗估)。
50
+ *
51
+ * 光标 / 选区 / 命中都要把「行带」换算成屏幕上的矩形,而 canvas 的 `textBaseline` 有
52
+ * top / middle / bottom / alphabetic 几种口径(`y` 分别指 em 顶 / em 中 / em 底 / 字母基线)——
53
+ * 只按 `y + 行号 × 行高` 推会在非 bottom 基线(如 `textBaseline: 'top'`)下整体错位半行到一行。
54
+ */
55
+ private __inkMetrics;
56
+ private __fontMetrics;
29
57
  protected static arrangeParam(props: any): any;
30
58
  /**
31
59
  * @overwrite
@@ -52,6 +80,9 @@ declare class ICEText extends ICEComponent {
52
80
  /**
53
81
  * 创建透明的 HTML input 覆盖在文本上,捕获输入(含中文 IME)。
54
82
  * input 文字设为透明(canvas 负责显示),只保留可见光标。
83
+ *
84
+ * `multiline`(或文本里已有 `\n`)时改用 `<textarea>`:回车插入换行而不是提交,
85
+ * 选区 / 换行都由浏览器接管(与单行输入同一套「文字透明、只保留光标」的做法)。
55
86
  */
56
87
  private __mountEditInput;
57
88
  /**
@@ -67,13 +98,82 @@ declare class ICEText extends ICEComponent {
67
98
  */
68
99
  setText(text: string): this;
69
100
  getText(): string;
101
+ /**
102
+ * 度量前提变了(自定义字体加载完成、主题换字号…)时调用:只标脏,
103
+ * 真正的重算交给下一次 render(`paramsDirty → calcComponentParams → measureText`)。
104
+ * 见 `ICE.remeasureTexts()` 与 `ICE.loadFont()`。
105
+ */
106
+ remeasureText(): this;
107
+ /**
108
+ * 外部**显式**设置 width/height(应用代码,或布局管理器按容器分配尺寸)时,关掉对应方向的
109
+ * 自动量测 —— 否则下一帧 `measureText → __applyMeasuredSize` 会把刚设的尺寸又改回去,
110
+ * 表现为「setState({width}) 不生效」。
111
+ *
112
+ * 与构造函数里「用户是否显式传 width/height」是同一套语义(见 __autoWidth/__autoHeight)。
113
+ */
114
+ protected __beforeStateMerge(newState: any): boolean;
70
115
  /**
71
116
  * @overwrite
72
117
  * 编辑态下接管键盘输入:字符插入 / Backspace / Delete / 方向键移动光标 / Enter 提交。
73
118
  */
74
119
  protected keyboardEvtHandler(evt: any): void;
120
+ /** caret 之前最近的一个 grapheme 边界(无 DOM 时按 grapheme 移动/退格;DOM 由浏览器负责)。 */
121
+ private __prevGraphemeBoundary;
122
+ /** caret 之后最近的一个 grapheme 边界。 */
123
+ private __nextGraphemeBoundary;
124
+ /** 编辑态是否按多行处理:显式 `multiline`,或文本里已经存在 `\n`。 */
125
+ private __isMultilineEditing;
126
+ /**
127
+ * 每一行的**行盒**(组件本地坐标,原点在盒子中心):文字左边缘 `x`、行宽 `width`、行带 `top/height`。
128
+ *
129
+ * 三处共用它,避免各算一遍又漂移:光标(renderCaret)、选区(renderSelection)、
130
+ * 坐标 → 下标(getCaretIndexAt)/ 编辑态命中(containsLocalPoint)。
131
+ * 行宽走缓存(第 8 项),不再逐帧 measureText。
132
+ */
133
+ private __lineBoxes;
134
+ /**
135
+ * 选中区间(`selectionStart` → `selectionEnd`,按原始文本下标;-1 表示没有选区)。
136
+ *
137
+ * 选区是**编辑**语义:按 `\n` 拆行定位与 `caretIndex` 一致;开启 `wrap` 的非编辑态下
138
+ * 显示行与原始下标不再一一对应(此时不绘制选区,避免画到错误的位置)。
139
+ */
140
+ getSelection(): {
141
+ start: number;
142
+ end: number;
143
+ };
144
+ /** 设置选区(终点省略时 = 光标位置,即「没有选中内容」);DOM 编辑态会同步给 HTML 输入元素。 */
145
+ setSelection(start: number, end?: number): this;
146
+ /** 全选。 */
147
+ selectAll(): this;
148
+ /** 清空选区(保留光标)。 */
149
+ clearSelection(): this;
150
+ /**
151
+ * 本地坐标 → 光标下标(**按字形**)。
152
+ *
153
+ * 先按 y 选中行带(行外取最近的一行),再在该行的 grapheme 边界里取**离点击点最近的**一个
154
+ * —— 判定用相邻边界的**中点**(点过中点才开始算下一个字符),这是各主流文本编辑器的手感。
155
+ * 返回值是**原始文本**里的下标(含 `\n` 偏移),可直接喂给 `caretIndex`。
156
+ */
157
+ getCaretIndexAt(localX: number, localY: number): number;
75
158
  /**
76
- * 在编辑态下渲染光标(垂直竖线),位置由 caretIndex + ctx.measureText 计算。
159
+ * 编辑态下按**文本行**命中(而不是整个盒子):点在行带之外(如 padding / 盒子右下空白)不算命中。
160
+ * 非编辑态仍是盒子语义 —— 拖动、框选、双击进入编辑这些既有交互都依赖它。
161
+ */
162
+ protected containsLocalPoint(localX: number, localY: number): boolean;
163
+ /**
164
+ * 绘制选区底色(无 DOM 运行时没有浏览器选区;DOM 编辑态下浏览器 input/textarea 自己会画)。
165
+ */
166
+ private renderSelection;
167
+ /**
168
+ * 在编辑态下渲染光标(垂直竖线)。
169
+ *
170
+ * 无 DOM 的运行时(小程序 / Node)没有浏览器 caret 可用,这里自己算位置,三条规则:
171
+ * - **多行**:`caretIndex` 先按 `\n` 折成「第几行 + 行内偏移」,光标画在对应行(旧实现把整段前缀
172
+ * 都量在一个位置上,多行文本里光标会跑到第一行);
173
+ * - **方向**:RTL 行的阅读起点在右,光标 x 要从右边缘往左量(`rightEdge - measure(前缀)`);
174
+ * - **对齐**:与 `getRenderLines()` 同一套(left / center / right,start/end 已按方向解析)。
175
+ *
176
+ * DOM 编辑态直接返回 —— 那时光标由 HTML input 的 `caretColor` 接管(浏览器处理 grapheme / IME 更准)。
77
177
  */
78
178
  private renderCaret;
79
179
  /**
@@ -118,13 +218,30 @@ declare class ICEText extends ICEComponent {
118
218
  private __computeWrappedLines;
119
219
  /** 统一的测宽函数:优先 ctx.measureText;无 ctx 时按 fontSize 粗估。 */
120
220
  private __measureFn;
221
+ /** 字号(px):所有相对单位(em / %)与默认行高都按它折算。 */
222
+ private __fontSizePx;
223
+ /** 把 `style.letterSpacing`(数字 / '2px' / '0.2em' / '20%')归一成 CSS 值写进 ctx。 */
224
+ private __applyLetterSpacingToCtx;
225
+ /**
226
+ * 每一行的行高(px):
227
+ * - 显式配置(数字 px / 字符串)→ 用它,单行也照用(盒子高度可预测);
228
+ * - 未配置 → `max(墨迹高, 字号 × 1.35)`(见 LINE_HEIGHT_RATIO 的注释)。
229
+ */
230
+ private __lineAdvance;
121
231
  /**
122
232
  * 按 grapheme cluster 切分。
123
233
  * 优先 Intl.Segmenter(Baseline 2024),能把 emoji / ZWJ 序列 / 组合字符合成一个单元;
124
234
  * 不可用时退化为码点切分(至少不会把代理对拆开)。
235
+ *
236
+ * 实现放在 `text-wrap.ts`(断行策略共用同一份切分 + 缓存)。
125
237
  */
126
238
  private __graphemes;
127
- /** 贪心换行:逐 grapheme 累加,超过可用宽度即断行。保留段落自身的 \n。 */
239
+ /**
240
+ * 换行:保留段落自身的 `\n`,段内按 `state.wordBreak` 策略断行。
241
+ *
242
+ * 断行规则是**排版**职责(见 `text-wrap.ts`):`'normal'` 下拉丁词不被硬拆、CJK 逐字断并做禁则;
243
+ * `'break-all'` 保留旧的逐 grapheme 贪心。i18n 词条本身由应用层提供,这里不做任何文本加工。
244
+ */
128
245
  private __wrapText;
129
246
  /**
130
247
  * 超过 maxLines 时截断末行并追加省略号;逐 grapheme 回退直到「内容+省略号」放得下。
@@ -135,6 +252,14 @@ declare class ICEText extends ICEComponent {
135
252
  private __applyMeasuredSize;
136
253
  /** DOM 降级测量:line-height 归一为 1,减少 leading 干扰(旧环境/小程序)。 */
137
254
  private __measureByDOM;
255
+ /** 行宽缓存的 key:行内容 + 字体 + 字间距(三者任一变了,行宽就不可信)。 */
256
+ private __lineWidthsKey;
257
+ /** 记下量测阶段算好的行宽(`__measureByCanvas` 专用)。 */
258
+ private __cacheLineWidths;
259
+ /**
260
+ * 取逐行宽度:命中缓存直接返回;未命中也**只量这一次**(结果写回缓存)。
261
+ */
262
+ private __lineWidths;
138
263
  /**
139
264
  * 文本的**渲染行布局**:每一行的内容与基线坐标(组件本地坐标)。
140
265
  *
@@ -154,5 +279,32 @@ declare class ICEText extends ICEComponent {
154
279
  * 同时把移动坐标轴原点的偏移量计算进去。
155
280
  */
156
281
  protected doRender(): void;
282
+ /**
283
+ * 自绘文本装饰线。
284
+ *
285
+ * - 横向范围取**每一行自己的宽度**(缓存里的行宽,必要时补量一次),居右/居中/RTL 下才对得上文字;
286
+ * - 基线偏移按字号比例:下划线 `+0.12em`、删除线 `-0.30em`、上划线 `-0.80em`(与主流排版接近);
287
+ * - 颜色:`style.textDecorationColor` 优先,留空跟随 `fillStyle`;粗细 `style.textDecorationWidth`
288
+ * 留 0 时按 `字号 / 14`(至少 1px)。
289
+ */
290
+ private __drawTextDecoration;
291
+ /**
292
+ * `direction: 'auto'` 需要按文本解析成具体的 ltr/rtl —— canvas 只认 `ltr | rtl | inherit`,
293
+ * 所以这里在通用 style 应用之后覆盖一次(`style.direction` 的原始值 `'auto'` 不会被 canvas 采纳)。
294
+ *
295
+ * 特性检测用 `'direction' in ctx`(不读值):**不支持 `direction` 的运行时**(部分小程序基础库、
296
+ * 极简测试桩)就跳过,退化为默认 LTR —— 这也是小程序「Canvas 2D 子集」回归能通过的原因。
297
+ */
298
+ protected applyStyleToCtx(): void;
299
+ /** 本帧是否把 `ctx.direction` 写过(用于渲染结束后的归位)。 */
300
+ private __directionApplied;
301
+ /**
302
+ * @overwrite
303
+ * 除基类的泄漏属性外,`direction` 也要归位 —— 否则 RTL 文本会把方向"漏"给后面绘制的组件,
304
+ * 破坏「组件渲染自包含」这条铁律(脏矩形局部重绘与离屏缓存都依赖它)。
305
+ */
306
+ __resetLeakyCtxState(): void;
307
+ /** 解析后的文字方向(`'auto'` → 按首个强方向字符判定)。 */
308
+ private __resolvedDirection;
157
309
  }
158
310
  export default ICEText;