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
@@ -3,11 +3,16 @@ import {
3
3
  extend,
4
4
  retrieve2,
5
5
  retrieve3,
6
- reduce
6
+ reduce,
7
7
  } from '../../core/util';
8
- import { TextAlign, TextVerticalAlign, ImageLike, Dictionary } from '../../core/types';
9
- import { TextStyleProps } from '../Text';
10
- import { getLineHeight, getWidth, parsePercent } from '../../contain/text';
8
+ import { TextAlign, TextVerticalAlign, ImageLike, Dictionary, NullUndefined } from '../../core/types';
9
+ import { DefaultTextStyle, TextStyleProps } from '../Text';
10
+ import {
11
+ adjustTextX,
12
+ adjustTextY,
13
+ ensureFontMeasureInfo, FontMeasureInfo, getLineHeight, measureCharWidth, measureWidth, parsePercent,
14
+ } from '../../contain/text';
15
+ import BoundingRect, { BoundingRectIntersectOpt } from '../../core/BoundingRect';
11
16
 
12
17
  const STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;
13
18
 
@@ -23,15 +28,12 @@ interface InnerTruncateOption {
23
28
  }
24
29
 
25
30
  interface InnerPreparedTruncateOption extends Required<InnerTruncateOption> {
26
- font: string
27
-
28
31
  ellipsis: string
29
32
  ellipsisWidth: number
30
33
  contentWidth: number
31
34
 
32
35
  containerWidth: number
33
- cnCharWidth: number
34
- ascCharWidth: number
36
+ fontMeasureInfo: FontMeasureInfo
35
37
  }
36
38
 
37
39
  /**
@@ -91,16 +93,11 @@ function prepareTruncateOptions(
91
93
  options = options || {};
92
94
  let preparedOpts = extend({}, options) as InnerPreparedTruncateOption;
93
95
 
94
- preparedOpts.font = font;
95
96
  ellipsis = retrieve2(ellipsis, '...');
96
97
  preparedOpts.maxIterations = retrieve2(options.maxIterations, 2);
97
98
  const minChar = preparedOpts.minChar = retrieve2(options.minChar, 0);
98
- // FIXME
99
- // Other languages?
100
- preparedOpts.cnCharWidth = getWidth('国', font);
101
- // FIXME
102
- // Consider proportional font?
103
- const ascCharWidth = preparedOpts.ascCharWidth = getWidth('a', font);
99
+ const fontMeasureInfo = preparedOpts.fontMeasureInfo = ensureFontMeasureInfo(font);
100
+ const ascCharWidth = fontMeasureInfo.asciiCharWidth;
104
101
  preparedOpts.placeholder = retrieve2(options.placeholder, '');
105
102
 
106
103
  // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'.
@@ -110,7 +107,7 @@ function prepareTruncateOptions(
110
107
  contentWidth -= ascCharWidth;
111
108
  }
112
109
 
113
- let ellipsisWidth = getWidth(ellipsis, font);
110
+ let ellipsisWidth = measureWidth(fontMeasureInfo, ellipsis);
114
111
  if (ellipsisWidth > contentWidth) {
115
112
  ellipsis = '';
116
113
  ellipsisWidth = 0;
@@ -132,8 +129,8 @@ function truncateSingleLine(
132
129
  options: InnerPreparedTruncateOption
133
130
  ): void {
134
131
  const containerWidth = options.containerWidth;
135
- const font = options.font;
136
132
  const contentWidth = options.contentWidth;
133
+ const fontMeasureInfo = options.fontMeasureInfo;
137
134
 
138
135
  if (!containerWidth) {
139
136
  out.textLine = '';
@@ -141,7 +138,7 @@ function truncateSingleLine(
141
138
  return;
142
139
  }
143
140
 
144
- let lineWidth = getWidth(textLine, font);
141
+ let lineWidth = measureWidth(fontMeasureInfo, textLine);
145
142
 
146
143
  if (lineWidth <= containerWidth) {
147
144
  out.textLine = textLine;
@@ -156,13 +153,13 @@ function truncateSingleLine(
156
153
  }
157
154
 
158
155
  const subLength = j === 0
159
- ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
156
+ ? estimateLength(textLine, contentWidth, fontMeasureInfo)
160
157
  : lineWidth > 0
161
158
  ? Math.floor(textLine.length * contentWidth / lineWidth)
162
159
  : 0;
163
160
 
164
161
  textLine = textLine.substr(0, subLength);
165
- lineWidth = getWidth(textLine, font);
162
+ lineWidth = measureWidth(fontMeasureInfo, textLine);
166
163
  }
167
164
 
168
165
  if (textLine === '') {
@@ -174,13 +171,14 @@ function truncateSingleLine(
174
171
  }
175
172
 
176
173
  function estimateLength(
177
- text: string, contentWidth: number, ascCharWidth: number, cnCharWidth: number
174
+ text: string,
175
+ contentWidth: number,
176
+ fontMeasureInfo: FontMeasureInfo
178
177
  ): number {
179
178
  let width = 0;
180
179
  let i = 0;
181
180
  for (let len = text.length; i < len && width < contentWidth; i++) {
182
- const charCode = text.charCodeAt(i);
183
- width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
181
+ width += measureCharWidth(fontMeasureInfo, text.charCodeAt(i));
184
182
  }
185
183
  return i;
186
184
  }
@@ -190,17 +188,17 @@ export interface PlainTextContentBlock {
190
188
  // Line height of actual content.
191
189
  calculatedLineHeight: number
192
190
 
191
+ // Calculated based on the text.
193
192
  contentWidth: number
194
193
  contentHeight: number
195
194
 
195
+ // i.e., `retrieve2(style.width/height, contentWidth/contentHeight)`
196
196
  width: number
197
197
  height: number
198
198
 
199
- /**
200
- * Real text width containing padding.
201
- * It should be the same as `width` if background is rendered
202
- * and `width` is set by user.
203
- */
199
+ // i.e., `contentBlock.width/height + style.padding`
200
+ // `borderWidth` is not included here, because historically Path is placed regardless of `lineWidth`,
201
+ // and `outerWidth`/`outerHeight` is used to calculate placement.
204
202
  outerWidth: number
205
203
  outerHeight: number
206
204
 
@@ -213,23 +211,34 @@ export interface PlainTextContentBlock {
213
211
 
214
212
  export function parsePlainText(
215
213
  text: string,
216
- style?: TextStyleProps
214
+ style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
215
+ defaultOuterWidth: number | NullUndefined,
216
+ defaultOuterHeight: number | NullUndefined
217
217
  ): PlainTextContentBlock {
218
218
  text != null && (text += '');
219
219
 
220
220
  // textPadding has been normalized
221
221
  const overflow = style.overflow;
222
222
  const padding = style.padding as number[];
223
+ const paddingH = padding ? padding[1] + padding[3] : 0;
224
+ const paddingV = padding ? padding[0] + padding[2] : 0;
223
225
  const font = style.font;
224
226
  const truncate = overflow === 'truncate';
225
227
  const calculatedLineHeight = getLineHeight(font);
226
228
  const lineHeight = retrieve2(style.lineHeight, calculatedLineHeight);
227
- const bgColorDrawn = !!(style.backgroundColor);
228
229
 
229
230
  const truncateLineOverflow = style.lineOverflow === 'truncate';
230
231
  let isTruncated = false;
231
232
 
232
233
  let width = style.width;
234
+ if (width == null && defaultOuterWidth != null) {
235
+ width = defaultOuterWidth - paddingH;
236
+ }
237
+ let height = style.height;
238
+ if (height == null && defaultOuterHeight != null) {
239
+ height = defaultOuterHeight - paddingV;
240
+ }
241
+
233
242
  let lines: string[];
234
243
 
235
244
  if (width != null && (overflow === 'break' || overflow === 'breakAll')) {
@@ -239,8 +248,10 @@ export function parsePlainText(
239
248
  lines = text ? text.split('\n') : [];
240
249
  }
241
250
 
242
- const contentHeight = lines.length * lineHeight;
243
- const height = retrieve2(style.height, contentHeight);
251
+ let contentHeight = lines.length * lineHeight;
252
+ if (height == null) {
253
+ height = contentHeight;
254
+ }
244
255
 
245
256
  // Truncate lines.
246
257
  if (contentHeight > height && truncateLineOverflow) {
@@ -248,6 +259,7 @@ export function parsePlainText(
248
259
 
249
260
  isTruncated = isTruncated || (lines.length > lineCount);
250
261
  lines = lines.slice(0, lineCount);
262
+ contentHeight = lines.length * lineHeight;
251
263
 
252
264
  // TODO If show ellipse for line truncate
253
265
  // if (style.ellipsis) {
@@ -276,25 +288,18 @@ export function parsePlainText(
276
288
  // Calculate real text width and height
277
289
  let outerHeight = height;
278
290
  let contentWidth = 0;
291
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
279
292
  for (let i = 0; i < lines.length; i++) {
280
- contentWidth = Math.max(getWidth(lines[i], font), contentWidth);
293
+ contentWidth = Math.max(measureWidth(fontMeasureInfo, lines[i]), contentWidth);
281
294
  }
282
295
  if (width == null) {
283
- // When width is not explicitly set, use outerWidth as width.
296
+ // When width is not explicitly set, use contentWidth as width.
284
297
  width = contentWidth;
285
298
  }
286
299
 
287
- let outerWidth = contentWidth;
288
- if (padding) {
289
- outerHeight += padding[0] + padding[2];
290
- outerWidth += padding[1] + padding[3];
291
- width += padding[1] + padding[3];
292
- }
293
-
294
- if (bgColorDrawn) {
295
- // When render background, outerWidth should be the same as width.
296
- outerWidth = width;
297
- }
300
+ let outerWidth = width;
301
+ outerHeight += paddingV;
302
+ outerWidth += paddingH;
298
303
 
299
304
  return {
300
305
  lines: lines,
@@ -313,10 +318,13 @@ export function parsePlainText(
313
318
  class RichTextToken {
314
319
  styleName: string
315
320
  text: string
321
+
322
+ // Includes `tokenStyle.padding`
316
323
  width: number
317
324
  height: number
318
325
 
319
326
  // Inner height exclude padding
327
+ // i.e., `retrieve2(tokenStyle.height, token.contentHeight)`
320
328
  innerHeight: number
321
329
 
322
330
  // Width and height of actual text content.
@@ -345,13 +353,16 @@ class RichTextLine {
345
353
  }
346
354
  }
347
355
  export class RichTextContentBlock {
348
- // width/height of content
356
+ // i.e. `retrieve2(outermostStyle.width, contentWidth)`.
357
+ // exclude outermost style.padding.
349
358
  width: number = 0
350
359
  height: number = 0
351
- // Calculated text height
360
+ // Calculated text width/height based on content (including tokenStyle.padding).
352
361
  contentWidth: number = 0
353
362
  contentHeight: number = 0
354
- // outerWidth/outerHeight with padding
363
+ // i.e., contentBlock.width/height + outermostStyle.padding
364
+ // `borderWidth` is not included here, because historically Path is placed regardless of `lineWidth`,
365
+ // and `outerWidth`/`outerHeight` is used to calculate placement.
355
366
  outerWidth: number = 0
356
367
  outerHeight: number = 0
357
368
  lines: RichTextLine[] = []
@@ -370,7 +381,13 @@ type WrapInfo = {
370
381
  * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
371
382
  * If styleName is undefined, it is plain text.
372
383
  */
373
- export function parseRichText(text: string, style: TextStyleProps): RichTextContentBlock {
384
+ export function parseRichText(
385
+ text: string,
386
+ style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
387
+ defaultOuterWidth: number | NullUndefined,
388
+ defaultOuterHeight: number | NullUndefined,
389
+ topTextAlign: TextAlign
390
+ ): RichTextContentBlock {
374
391
  const contentBlock = new RichTextContentBlock();
375
392
 
376
393
  text != null && (text += '');
@@ -378,8 +395,19 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
378
395
  return contentBlock;
379
396
  }
380
397
 
381
- const topWidth = style.width;
382
- const topHeight = style.height;
398
+ const stlPadding = style.padding as number[];
399
+ const stlPaddingH = stlPadding ? stlPadding[1] + stlPadding[3] : 0;
400
+ const stlPaddingV = stlPadding ? stlPadding[0] + stlPadding[2] : 0;
401
+
402
+ let topWidth = style.width;
403
+ if (topWidth == null && defaultOuterWidth != null) {
404
+ topWidth = defaultOuterWidth - stlPaddingH;
405
+ }
406
+ let topHeight = style.height;
407
+ if (topHeight == null && defaultOuterHeight != null) {
408
+ topHeight = defaultOuterHeight - stlPaddingV;
409
+ }
410
+
383
411
  const overflow = style.overflow;
384
412
  let wrapInfo: WrapInfo = (overflow === 'break' || overflow === 'breakAll') && topWidth != null
385
413
  ? {width: topWidth, accumWidth: 0, breakAll: overflow === 'breakAll'}
@@ -406,8 +434,6 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
406
434
  let calculatedHeight = 0;
407
435
  let calculatedWidth = 0;
408
436
 
409
- const stlPadding = style.padding as number[];
410
-
411
437
  const truncate = overflow === 'truncate';
412
438
  const truncateLine = style.lineOverflow === 'truncate';
413
439
  const tmpTruncateOut = {} as Parameters<typeof truncateText2>[0];
@@ -446,12 +472,12 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
446
472
 
447
473
  textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
448
474
  token.height = tokenHeight;
449
- // Inlcude padding in lineHeight.
475
+ // Include padding in lineHeight.
450
476
  token.lineHeight = retrieve3(
451
477
  tokenStyle.lineHeight, style.lineHeight, tokenHeight
452
478
  );
453
479
 
454
- token.align = tokenStyle && tokenStyle.align || style.align;
480
+ token.align = tokenStyle && tokenStyle.align || topTextAlign;
455
481
  token.verticalAlign = tokenStyle && tokenStyle.verticalAlign || 'middle';
456
482
 
457
483
  if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
@@ -479,7 +505,7 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
479
505
  token.percentWidth = styleTokenWidth;
480
506
  pendingList.push(token);
481
507
 
482
- token.contentWidth = getWidth(token.text, font);
508
+ token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
483
509
  // Do not truncate in this case, because there is no user case
484
510
  // and it is too complicated.
485
511
  }
@@ -515,11 +541,11 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
515
541
  );
516
542
  token.text = tmpTruncateOut.text;
517
543
  contentBlock.isTruncated = contentBlock.isTruncated || tmpTruncateOut.isTruncated;
518
- token.width = token.contentWidth = getWidth(token.text, font);
544
+ token.width = token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
519
545
  }
520
546
  }
521
547
  else {
522
- token.contentWidth = getWidth(token.text, font);
548
+ token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
523
549
  }
524
550
  }
525
551
 
@@ -539,10 +565,8 @@ export function parseRichText(text: string, style: TextStyleProps): RichTextCont
539
565
  contentBlock.contentHeight = calculatedHeight;
540
566
  contentBlock.contentWidth = calculatedWidth;
541
567
 
542
- if (stlPadding) {
543
- contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
544
- contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
545
- }
568
+ contentBlock.outerWidth += stlPaddingH;
569
+ contentBlock.outerHeight += stlPaddingV;
546
570
 
547
571
  for (let i = 0; i < pendingList.length; i++) {
548
572
  const token = pendingList[i];
@@ -594,10 +618,12 @@ function pushTokens(
594
618
  strLines = res.lines;
595
619
  }
596
620
  }
597
- else {
621
+
622
+ if (!strLines) {
598
623
  strLines = str.split('\n');
599
624
  }
600
625
 
626
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
601
627
  for (let i = 0; i < strLines.length; i++) {
602
628
  const text = strLines[i];
603
629
  const token = new RichTextToken();
@@ -610,8 +636,8 @@ function pushTokens(
610
636
  }
611
637
  else {
612
638
  token.width = linesWidths
613
- ? linesWidths[i] // Caculated width in the wrap
614
- : getWidth(text, font);
639
+ ? linesWidths[i] // Calculated width in the wrap
640
+ : measureWidth(fontMeasureInfo, text);
615
641
  }
616
642
 
617
643
  // The first token should be appended to the last line if not new line.
@@ -671,6 +697,11 @@ function isWordBreakChar(ch: string) {
671
697
  return true;
672
698
  }
673
699
 
700
+ /**
701
+ * NOTE: The current strategy is that if no enough space, all the text is
702
+ * still displayed, regardless of overflow.
703
+ * A clip path can be used to completely avoid overflow.
704
+ */
674
705
  function wrapText(
675
706
  text: string,
676
707
  font: string,
@@ -684,6 +715,7 @@ function wrapText(
684
715
  let currentWord = '';
685
716
  let currentWordWidth = 0;
686
717
  let accumWidth = 0;
718
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
687
719
 
688
720
  for (let i = 0; i < text.length; i++) {
689
721
 
@@ -703,7 +735,7 @@ function wrapText(
703
735
  continue;
704
736
  }
705
737
 
706
- const chWidth = getWidth(ch, font);
738
+ const chWidth = measureCharWidth(fontMeasureInfo, ch.charCodeAt(0));
707
739
  const inWord = isBreakAll ? false : !isWordBreakChar(ch);
708
740
 
709
741
  if (!lines.length
@@ -785,12 +817,6 @@ function wrapText(
785
817
  }
786
818
  }
787
819
 
788
- if (!lines.length && !line) {
789
- line = text;
790
- currentWord = '';
791
- currentWordWidth = 0;
792
- }
793
-
794
820
  // Append last line.
795
821
  if (currentWord) {
796
822
  line += currentWord;
@@ -811,4 +837,56 @@ function wrapText(
811
837
  lines: lines,
812
838
  linesWidths
813
839
  };
814
- }
840
+ }
841
+
842
+ /**
843
+ * @see {ElementTextConfig['autoOverflowArea']}
844
+ */
845
+ export function calcInnerTextOverflowArea(
846
+ out: CalcInnerTextOverflowAreaOut,
847
+ overflowRect: DefaultTextStyle['overflowRect'],
848
+ baseX: number,
849
+ baseY: number,
850
+ textAlign: TextAlign,
851
+ textVerticalAlign: TextVerticalAlign
852
+ ): void {
853
+ out.baseX = baseX;
854
+ out.baseY = baseY;
855
+ out.outerWidth = out.outerHeight = null;
856
+
857
+ if (!overflowRect) {
858
+ return;
859
+ }
860
+
861
+ const textWidth = overflowRect.width * 2;
862
+ const textHeight = overflowRect.height * 2;
863
+ BoundingRect.set(
864
+ tmpCITCTextRect,
865
+ adjustTextX(baseX, textWidth, textAlign),
866
+ adjustTextY(baseY, textHeight, textVerticalAlign),
867
+ textWidth,
868
+ textHeight
869
+ );
870
+ // If `overflow: break` and no intersection or intersect but no enough space, we still display all text
871
+ // on the edge regardless of the overflow. Although that might be meaningless in production env, it is
872
+ // logically sound and helps in debug. Therefore we use `intersectOpt.clamp`.
873
+ BoundingRect.intersect(overflowRect, tmpCITCTextRect, null, tmpCITCIntersectRectOpt);
874
+ const outIntersectRect = tmpCITCIntersectRectOpt.outIntersectRect;
875
+ out.outerWidth = outIntersectRect.width;
876
+ out.outerHeight = outIntersectRect.height;
877
+ out.baseX = adjustTextX(outIntersectRect.x, outIntersectRect.width, textAlign, true);
878
+ out.baseY = adjustTextY(outIntersectRect.y, outIntersectRect.height, textVerticalAlign, true);
879
+ }
880
+ const tmpCITCTextRect = new BoundingRect(0, 0, 0, 0);
881
+ const tmpCITCIntersectRectOpt = {outIntersectRect: {}, clamp: true} as BoundingRectIntersectOpt;
882
+
883
+ export type CalcInnerTextOverflowAreaOut = {
884
+ // The input baseX/baseY or modified baseX/baseY, must exists.
885
+ baseX: number
886
+ baseY: number
887
+ // Calculated outer size based on overflowRect
888
+ // NaN indicates don't draw.
889
+ outerWidth: number | NullUndefined
890
+ outerHeight: number | NullUndefined
891
+ };
892
+
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import Definable from './Definable';
7
- import * as zrUtil from '../../core/util';
8
7
  import Displayable from '../../graphic/Displayable';
9
8
  import Path from '../../graphic/Path';
10
9
  import {path} from '../graphic';
@@ -142,13 +141,14 @@ export default class ClippathManager extends Definable {
142
141
  * @param displayable displayable element
143
142
  */
144
143
  markUsed(displayable: Displayable) {
145
- // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
146
- if (displayable.__clipPaths) {
147
- zrUtil.each(displayable.__clipPaths, (clipPath: PathExtended) => {
144
+ const clipPaths = displayable.__clipPaths;
145
+ if (clipPaths) {
146
+ for (let idx = 0; idx < clipPaths.length; idx++) {
147
+ const clipPath = clipPaths[idx] as PathExtended;
148
148
  if (clipPath._dom) {
149
149
  super.markDomUsed(clipPath._dom);
150
150
  }
151
- });
151
+ }
152
152
  }
153
153
  };
154
154
 
package/src/tool/color.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import LRU from '../core/LRU';
2
- import { extend, isGradientObject, isString, map } from '../core/util';
2
+ import { extend, isFunction, isGradientObject, isString, map } from '../core/util';
3
3
  import { GradientObject } from '../graphic/Gradient';
4
4
 
5
5
  const kCSSColorTable = {
@@ -498,21 +498,25 @@ export const mapToColor = lerp;
498
498
 
499
499
  /**
500
500
  * @param color
501
- * @param h 0 ~ 360, ignore when null.
502
- * @param s 0 ~ 1, ignore when null.
503
- * @param l 0 ~ 1, ignore when null.
501
+ * @param h 0 ~ 360, ignore when null. If function, it takes hue as argument and returns a new hue.
502
+ * @param s 0 ~ 1, ignore when null. If function, it takes saturation as argument and returns a new saturation.
503
+ * @param l 0 ~ 1, ignore when null. If function, it takes lightness as argument and returns a new lightness.
504
504
  * @return Color string in rgba format.
505
505
  * @memberOf module:zrender/util/color
506
506
  */
507
- export function modifyHSL(color: string, h?: number, s?: number, l?: number): string {
507
+ export function modifyHSL(
508
+ color: string,
509
+ h?: number | ((h: number) => number),
510
+ s?: number | string | ((s: number) => number),
511
+ l?: number | string | ((l: number) => number)
512
+ ): string {
508
513
  let colorArr = parse(color);
509
514
 
510
515
  if (color) {
511
516
  colorArr = rgba2hsla(colorArr);
512
- h != null && (colorArr[0] = clampCssAngle(h));
513
- s != null && (colorArr[1] = parseCssFloat(s));
514
- l != null && (colorArr[2] = parseCssFloat(l));
515
-
517
+ h != null && (colorArr[0] = clampCssAngle(isFunction(h) ? h(colorArr[0]) : h));
518
+ s != null && (colorArr[1] = parseCssFloat(isFunction(s) ? s(colorArr[1]) : s));
519
+ l != null && (colorArr[2] = parseCssFloat(isFunction(l) ? l(colorArr[2]) : l));
516
520
  return stringify(hsla2rgba(colorArr), 'rgba');
517
521
  }
518
522
  }
package/src/tool/path.ts CHANGED
@@ -383,13 +383,16 @@ class SVGPath extends Path {
383
383
  function isPathProxy(path: PathProxy | CanvasRenderingContext2D): path is PathProxy {
384
384
  return (path as PathProxy).setData != null;
385
385
  }
386
+
386
387
  // TODO Optimize double memory cost problem
387
388
  function createPathOptions(str: string, opts: SVGPathOption): InnerSVGPathOption {
388
389
  const pathProxy = createPathProxyFromString(str);
389
390
  const innerOpts: InnerSVGPathOption = extend({}, opts);
390
391
  innerOpts.buildPath = function (path: PathProxy | CanvasRenderingContext2D) {
391
- if (isPathProxy(path)) {
392
- path.setData(pathProxy.data);
392
+ const beProxy = isPathProxy(path);
393
+ if (beProxy && path.canSave()) {
394
+ // path.setData(pathProxy.data);
395
+ path.appendPath(pathProxy);
393
396
  // Svg and vml renderer don't have context
394
397
  const ctx = path.getContext();
395
398
  if (ctx) {
@@ -397,8 +400,10 @@ function createPathOptions(str: string, opts: SVGPathOption): InnerSVGPathOption
397
400
  }
398
401
  }
399
402
  else {
400
- const ctx = path;
401
- pathProxy.rebuildPath(ctx, 1);
403
+ const ctx = beProxy ? path.getContext() : path;
404
+ if (ctx) {
405
+ pathProxy.rebuildPath(ctx, 1);
406
+ }
402
407
  }
403
408
  };
404
409
 
package/src/zrender.ts CHANGED
@@ -556,7 +556,7 @@ export function registerSSRDataGetter<T>(getter: ElementSSRDataGetter<T>) {
556
556
  /**
557
557
  * @type {string}
558
558
  */
559
- export const version = '5.6.2-dev.20250623';
559
+ export const version = '5.6.2-dev.20250625';
560
560
 
561
561
 
562
562
  export interface ZRenderType extends ZRender {};