zrender-nightly 5.6.2-dev.20250623 → 5.6.2-dev.20250625

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 (50) hide show
  1. package/dist/zrender.js +491 -253
  2. package/dist/zrender.js.map +1 -1
  3. package/dist/zrender.min.js +1 -1
  4. package/lib/Element.d.ts +4 -0
  5. package/lib/Element.js +34 -16
  6. package/lib/Handler.js +1 -1
  7. package/lib/Storage.js +20 -20
  8. package/lib/contain/text.d.ts +14 -2
  9. package/lib/contain/text.js +65 -15
  10. package/lib/core/BoundingRect.d.ts +25 -3
  11. package/lib/core/BoundingRect.js +182 -76
  12. package/lib/core/OrientedBoundingRect.d.ts +2 -2
  13. package/lib/core/OrientedBoundingRect.js +50 -34
  14. package/lib/core/PathProxy.d.ts +1 -0
  15. package/lib/core/PathProxy.js +16 -1
  16. package/lib/core/types.d.ts +1 -0
  17. package/lib/core/util.d.ts +1 -0
  18. package/lib/core/util.js +1 -0
  19. package/lib/graphic/Displayable.js +1 -1
  20. package/lib/graphic/Text.d.ts +3 -2
  21. package/lib/graphic/Text.js +20 -13
  22. package/lib/graphic/helper/parseText.d.ts +11 -4
  23. package/lib/graphic/helper/parseText.js +71 -44
  24. package/lib/svg-legacy/helper/ClippathManager.js +6 -6
  25. package/lib/tool/color.d.ts +1 -1
  26. package/lib/tool/color.js +4 -4
  27. package/lib/tool/path.js +7 -4
  28. package/lib/zrender.d.ts +1 -1
  29. package/lib/zrender.js +1 -1
  30. package/package.json +3 -2
  31. package/src/Element.ts +69 -16
  32. package/src/Handler.ts +1 -1
  33. package/src/Storage.ts +25 -24
  34. package/src/canvas/helper.ts +1 -1
  35. package/src/contain/text.ts +103 -19
  36. package/src/core/BoundingRect.ts +308 -87
  37. package/src/core/OrientedBoundingRect.ts +86 -46
  38. package/src/core/PathProxy.ts +17 -1
  39. package/src/core/Transformable.ts +2 -0
  40. package/src/core/matrix.ts +2 -1
  41. package/src/core/types.ts +2 -0
  42. package/src/core/util.ts +3 -1
  43. package/src/graphic/Displayable.ts +1 -3
  44. package/src/graphic/Group.ts +2 -0
  45. package/src/graphic/Text.ts +59 -22
  46. package/src/graphic/helper/parseText.ts +151 -73
  47. package/src/svg-legacy/helper/ClippathManager.ts +5 -5
  48. package/src/tool/color.ts +13 -9
  49. package/src/tool/path.ts +9 -4
  50. package/src/zrender.ts +1 -1
package/src/Storage.ts CHANGED
@@ -8,6 +8,7 @@ import timsort from './core/timsort';
8
8
  import Displayable from './graphic/Displayable';
9
9
  import Path from './graphic/Path';
10
10
  import { REDRAW_BIT } from './graphic/constants';
11
+ import { NullUndefined } from './core/types';
11
12
 
12
13
  let invalidZErrorLogged = false;
13
14
  function logInvalidZError() {
@@ -83,7 +84,7 @@ export default class Storage {
83
84
 
84
85
  private _updateAndAddDisplayable(
85
86
  el: Element,
86
- clipPaths: Path[],
87
+ parentClipPaths: Path[] | NullUndefined,
87
88
  includeIgnore?: boolean
88
89
  ) {
89
90
  if (el.ignore && !includeIgnore) {
@@ -95,18 +96,21 @@ export default class Storage {
95
96
  el.afterUpdate();
96
97
 
97
98
  const userSetClipPath = el.getClipPath();
98
-
99
- if (el.ignoreClip) {
100
- clipPaths = null;
101
- }
102
- else if (userSetClipPath) {
103
-
104
- // FIXME 效率影响
105
- if (clipPaths) {
106
- clipPaths = clipPaths.slice();
99
+ const parentHasClipPaths = parentClipPaths && parentClipPaths.length;
100
+ let clipPathIdx = 0;
101
+ let thisClipPaths = el.__clipPaths;
102
+
103
+ if (!el.ignoreClip
104
+ && (parentHasClipPaths || userSetClipPath)
105
+ ) { // has clipPath in this pass
106
+ if (!thisClipPaths) {
107
+ thisClipPaths = el.__clipPaths = [];
107
108
  }
108
- else {
109
- clipPaths = [];
109
+ if (parentHasClipPaths) {
110
+ // PENDING: performance?
111
+ for (let idx = 0; idx < parentClipPaths.length; idx++) {
112
+ thisClipPaths[clipPathIdx++] = parentClipPaths[idx];
113
+ }
110
114
  }
111
115
 
112
116
  let currentClipPath = userSetClipPath;
@@ -118,13 +122,17 @@ export default class Storage {
118
122
  currentClipPath.parent = parentClipPath as Group;
119
123
  currentClipPath.updateTransform();
120
124
 
121
- clipPaths.push(currentClipPath);
125
+ thisClipPaths[clipPathIdx++] = currentClipPath;
122
126
 
123
127
  parentClipPath = currentClipPath;
124
128
  currentClipPath = currentClipPath.getClipPath();
125
129
  }
126
130
  }
127
131
 
132
+ if (thisClipPaths) { // Remove other old clipPath in array.
133
+ thisClipPaths.length = clipPathIdx;
134
+ }
135
+
128
136
  // ZRText and Group and combining morphing Path may use children
129
137
  if ((el as GroupLike).childrenRef) {
130
138
  const children = (el as GroupLike).childrenRef();
@@ -137,7 +145,7 @@ export default class Storage {
137
145
  child.__dirty |= REDRAW_BIT;
138
146
  }
139
147
 
140
- this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
148
+ this._updateAndAddDisplayable(child, thisClipPaths, includeIgnore);
141
149
  }
142
150
 
143
151
  // Mark group clean here
@@ -146,13 +154,6 @@ export default class Storage {
146
154
  }
147
155
  else {
148
156
  const disp = el as Displayable;
149
- // Element is displayable
150
- if (clipPaths && clipPaths.length) {
151
- disp.__clipPaths = clipPaths;
152
- }
153
- else if (disp.__clipPaths && disp.__clipPaths.length > 0) {
154
- disp.__clipPaths = [];
155
- }
156
157
 
157
158
  // Avoid invalid z, z2, zlevel cause sorting error.
158
159
  if (isNaN(disp.z)) {
@@ -174,18 +175,18 @@ export default class Storage {
174
175
  // Add decal
175
176
  const decalEl = (el as Path).getDecalElement && (el as Path).getDecalElement();
176
177
  if (decalEl) {
177
- this._updateAndAddDisplayable(decalEl, clipPaths, includeIgnore);
178
+ this._updateAndAddDisplayable(decalEl, thisClipPaths, includeIgnore);
178
179
  }
179
180
 
180
181
  // Add attached text element and guide line.
181
182
  const textGuide = el.getTextGuideLine();
182
183
  if (textGuide) {
183
- this._updateAndAddDisplayable(textGuide, clipPaths, includeIgnore);
184
+ this._updateAndAddDisplayable(textGuide, thisClipPaths, includeIgnore);
184
185
  }
185
186
 
186
187
  const textEl = el.getTextContent();
187
188
  if (textEl) {
188
- this._updateAndAddDisplayable(textEl, clipPaths, includeIgnore);
189
+ this._updateAndAddDisplayable(textEl, thisClipPaths, includeIgnore);
189
190
  }
190
191
  }
191
192
 
@@ -82,8 +82,8 @@ export function getCanvasGradient(this: void, ctx: CanvasRenderingContext2D, obj
82
82
  return canvasGradient;
83
83
  }
84
84
 
85
+ // [CAVEAT] Assume the clipPaths array is never modified during a batch of `isClipPathChanged` calling.
85
86
  export function isClipPathChanged(clipPaths: Path[], prevClipPaths: Path[]): boolean {
86
- // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
87
87
  if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
88
88
  return false;
89
89
  }
@@ -1,25 +1,110 @@
1
1
  import BoundingRect, { RectLike } from '../core/BoundingRect';
2
- import { Dictionary, TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types';
2
+ import { TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types';
3
3
  import LRU from '../core/LRU';
4
4
  import { DEFAULT_FONT, platformApi } from '../core/platform';
5
5
 
6
- let textWidthCache: Dictionary<LRU<number>> = {};
7
-
6
+ /**
7
+ * @deprecated But keep for possible outside usage.
8
+ * Use `ensureFontMeasureInfo` + `measureWidth` instead.
9
+ */
8
10
  export function getWidth(text: string, font: string): number {
11
+ return measureWidth(ensureFontMeasureInfo(font), text);
12
+ }
13
+
14
+ export interface FontMeasureInfo {
15
+ font: string;
16
+ strWidthCache: LRU<number>;
17
+ // Key: char code, index: 0~127 (include 127)
18
+ asciiWidthMap: number[] | null | undefined;
19
+ asciiWidthMapTried: boolean;
20
+ // Default width char width used both in non-ascii and line height.
21
+ stWideCharWidth: number;
22
+ // Default asc char width
23
+ asciiCharWidth: number;
24
+ }
25
+
26
+ export function ensureFontMeasureInfo(font: string): FontMeasureInfo {
27
+ if (!_fontMeasureInfoCache) {
28
+ _fontMeasureInfoCache = new LRU(100);
29
+ }
9
30
  font = font || DEFAULT_FONT;
10
- let cacheOfFont = textWidthCache[font];
11
- if (!cacheOfFont) {
12
- cacheOfFont = textWidthCache[font] = new LRU(500);
31
+ let measureInfo = _fontMeasureInfoCache.get(font);
32
+ if (!measureInfo) {
33
+ measureInfo = {
34
+ font: font,
35
+ strWidthCache: new LRU(500),
36
+ asciiWidthMap: null, // Init lazily for performance.
37
+ asciiWidthMapTried: false,
38
+ // FIXME
39
+ // Other languages?
40
+ // FIXME
41
+ // Consider proportional font?
42
+ stWideCharWidth: platformApi.measureText('国', font).width,
43
+ asciiCharWidth: platformApi.measureText('a', font).width,
44
+ };
45
+ _fontMeasureInfoCache.put(font, measureInfo);
13
46
  }
14
- let width = cacheOfFont.get(text);
15
- if (width == null) {
16
- width = platformApi.measureText(text, font).width;
17
- cacheOfFont.put(text, width);
47
+ return measureInfo;
48
+ }
49
+ let _fontMeasureInfoCache: LRU<FontMeasureInfo>;
50
+
51
+ /**
52
+ * For getting more precise result in truncate.
53
+ * non-monospace font vary in char width.
54
+ * But if it is time consuming in some platform, return null/undefined.
55
+ * @return Key: char code, index: 0~127 (include 127)
56
+ */
57
+ function tryCreateASCIIWidthMap(font: string): FontMeasureInfo['asciiWidthMap'] {
58
+ // PENDING: is it necessary? Re-examine it if bad case reported.
59
+ if (_getASCIIWidthMapLongCount >= GET_ASCII_WIDTH_LONG_COUNT_MAX) {
60
+ return;
18
61
  }
62
+ font = font || DEFAULT_FONT;
63
+ const asciiWidthMap = [];
64
+ const start = +(new Date());
65
+ // 0~31 and 127 may also have width, and may vary in some fonts.
66
+ for (let code = 0; code <= 127; code++) {
67
+ asciiWidthMap[code] = platformApi.measureText(String.fromCharCode(code), font).width;
68
+ }
69
+ const cost = +(new Date()) - start;
70
+ if (cost > 16) {
71
+ _getASCIIWidthMapLongCount = GET_ASCII_WIDTH_LONG_COUNT_MAX;
72
+ }
73
+ else if (cost > 2) {
74
+ _getASCIIWidthMapLongCount++;
75
+ }
76
+ return asciiWidthMap;
77
+ }
78
+ let _getASCIIWidthMapLongCount: number = 0;
79
+ const GET_ASCII_WIDTH_LONG_COUNT_MAX = 5;
19
80
 
81
+ /**
82
+ * Hot path, performance sensitive.
83
+ */
84
+ export function measureCharWidth(fontMeasureInfo: FontMeasureInfo, charCode: number): number {
85
+ if (!fontMeasureInfo.asciiWidthMapTried) {
86
+ fontMeasureInfo.asciiWidthMap = tryCreateASCIIWidthMap(fontMeasureInfo.font);
87
+ fontMeasureInfo.asciiWidthMapTried = true;
88
+ }
89
+ return (0 <= charCode && charCode <= 127)
90
+ ? (fontMeasureInfo.asciiWidthMap != null
91
+ ? fontMeasureInfo.asciiWidthMap[charCode]
92
+ : fontMeasureInfo.asciiCharWidth
93
+ )
94
+ : fontMeasureInfo.stWideCharWidth;
95
+ }
96
+
97
+ export function measureWidth(fontMeasureInfo: FontMeasureInfo, text: string): number {
98
+ const strWidthCache = fontMeasureInfo.strWidthCache;
99
+ let width = strWidthCache.get(text);
100
+ if (width == null) {
101
+ width = platformApi.measureText(text, fontMeasureInfo.font).width;
102
+ strWidthCache.put(text, width);
103
+ }
20
104
  return width;
21
105
  }
22
106
 
107
+
23
108
  /**
24
109
  *
25
110
  * Get bounding rect for inner usage(TSpan)
@@ -31,7 +116,7 @@ export function innerGetBoundingRect(
31
116
  textAlign?: TextAlign,
32
117
  textBaseline?: TextVerticalAlign
33
118
  ): BoundingRect {
34
- const width = getWidth(text, font);
119
+ const width = measureWidth(ensureFontMeasureInfo(font), text);
35
120
  const height = getLineHeight(font);
36
121
 
37
122
  const x = adjustTextX(0, width, textAlign);
@@ -68,31 +153,30 @@ export function getBoundingRect(
68
153
  }
69
154
  }
70
155
 
71
- export function adjustTextX(x: number, width: number, textAlign: TextAlign): number {
156
+ export function adjustTextX(x: number, width: number, textAlign: TextAlign, inverse?: boolean): number {
72
157
  // TODO Right to left language
73
158
  if (textAlign === 'right') {
74
- x -= width;
159
+ !inverse ? (x -= width) : (x += width);
75
160
  }
76
161
  else if (textAlign === 'center') {
77
- x -= width / 2;
162
+ !inverse ? (x -= width / 2) : (x += width / 2);
78
163
  }
79
164
  return x;
80
165
  }
81
166
 
82
- export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign): number {
167
+ export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign, inverse?: boolean): number {
83
168
  if (verticalAlign === 'middle') {
84
- y -= height / 2;
169
+ !inverse ? (y -= height / 2) : (y += height / 2);
85
170
  }
86
171
  else if (verticalAlign === 'bottom') {
87
- y -= height;
172
+ !inverse ? (y -= height) : (y += height);
88
173
  }
89
174
  return y;
90
175
  }
91
176
 
92
-
93
177
  export function getLineHeight(font?: string): number {
94
178
  // FIXME A rough approach.
95
- return getWidth('国', font);
179
+ return ensureFontMeasureInfo(font).stWideCharWidth;
96
180
  }
97
181
 
98
182
  export function measureText(text: string, font?: string): {