zrender-nightly 5.7.0-dev.20250620 → 5.7.0-dev.20250622

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 (61) hide show
  1. package/README.md +1 -1
  2. package/build/prepublish.js +20 -0
  3. package/dist/zrender.js +563 -277
  4. package/dist/zrender.js.map +1 -1
  5. package/dist/zrender.min.js +1 -1
  6. package/lib/Element.d.ts +4 -0
  7. package/lib/Element.js +34 -16
  8. package/lib/Handler.js +1 -1
  9. package/lib/Storage.js +20 -20
  10. package/lib/canvas/graphic.js +1 -1
  11. package/lib/contain/text.d.ts +14 -2
  12. package/lib/contain/text.js +65 -15
  13. package/lib/core/BoundingRect.d.ts +25 -3
  14. package/lib/core/BoundingRect.js +182 -76
  15. package/lib/core/OrientedBoundingRect.d.ts +2 -2
  16. package/lib/core/OrientedBoundingRect.js +50 -34
  17. package/lib/core/PathProxy.d.ts +1 -0
  18. package/lib/core/PathProxy.js +16 -1
  19. package/lib/core/dom.d.ts +1 -0
  20. package/lib/core/dom.js +17 -0
  21. package/lib/core/env.js +15 -10
  22. package/lib/core/types.d.ts +1 -0
  23. package/lib/core/util.d.ts +1 -0
  24. package/lib/core/util.js +2 -1
  25. package/lib/graphic/Displayable.js +1 -1
  26. package/lib/graphic/Text.d.ts +4 -2
  27. package/lib/graphic/Text.js +23 -14
  28. package/lib/graphic/helper/parseText.d.ts +13 -4
  29. package/lib/graphic/helper/parseText.js +110 -54
  30. package/lib/svg-legacy/helper/ClippathManager.js +6 -6
  31. package/lib/tool/color.d.ts +3 -1
  32. package/lib/tool/color.js +6 -6
  33. package/lib/tool/parseSVG.js +11 -0
  34. package/lib/tool/path.js +7 -4
  35. package/lib/zrender.d.ts +1 -1
  36. package/lib/zrender.js +1 -1
  37. package/package.json +3 -2
  38. package/src/Element.ts +69 -16
  39. package/src/Handler.ts +1 -1
  40. package/src/Storage.ts +25 -24
  41. package/src/canvas/graphic.ts +1 -1
  42. package/src/canvas/helper.ts +1 -1
  43. package/src/contain/text.ts +103 -19
  44. package/src/core/BoundingRect.ts +308 -87
  45. package/src/core/OrientedBoundingRect.ts +86 -46
  46. package/src/core/PathProxy.ts +17 -1
  47. package/src/core/Transformable.ts +2 -0
  48. package/src/core/dom.ts +24 -0
  49. package/src/core/env.ts +31 -24
  50. package/src/core/matrix.ts +2 -1
  51. package/src/core/types.ts +2 -0
  52. package/src/core/util.ts +4 -2
  53. package/src/graphic/Displayable.ts +1 -3
  54. package/src/graphic/Group.ts +2 -0
  55. package/src/graphic/Text.ts +68 -21
  56. package/src/graphic/helper/parseText.ts +211 -83
  57. package/src/svg-legacy/helper/ClippathManager.ts +5 -5
  58. package/src/tool/color.ts +15 -11
  59. package/src/tool/parseSVG.ts +12 -1
  60. package/src/tool/path.ts +9 -4
  61. 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
  /**
@@ -44,8 +46,25 @@ export function truncateText(
44
46
  ellipsis?: string,
45
47
  options?: InnerTruncateOption
46
48
  ): string {
49
+ const out = {} as Parameters<typeof truncateText2>[0];
50
+ truncateText2(out, text, containerWidth, font, ellipsis, options);
51
+ return out.text;
52
+ }
53
+
54
+ // PENDING: not sure whether `truncateText` is used outside zrender, since it has an `export`
55
+ // specifier. So keep it and perform the interface modification in `truncateText2`.
56
+ function truncateText2(
57
+ out: {text: string, isTruncated: boolean},
58
+ text: string,
59
+ containerWidth: number,
60
+ font: string,
61
+ ellipsis?: string,
62
+ options?: InnerTruncateOption
63
+ ): void {
47
64
  if (!containerWidth) {
48
- return '';
65
+ out.text = '';
66
+ out.isTruncated = false;
67
+ return;
49
68
  }
50
69
 
51
70
  const textLines = (text + '').split('\n');
@@ -53,11 +72,16 @@ export function truncateText(
53
72
 
54
73
  // FIXME
55
74
  // It is not appropriate that every line has '...' when truncate multiple lines.
75
+ let isTruncated = false;
76
+ const truncateOut = {} as Parameters<typeof truncateSingleLine>[0];
56
77
  for (let i = 0, len = textLines.length; i < len; i++) {
57
- textLines[i] = truncateSingleLine(textLines[i], options as InnerPreparedTruncateOption);
78
+ truncateSingleLine(truncateOut, textLines[i], options as InnerPreparedTruncateOption);
79
+ textLines[i] = truncateOut.textLine;
80
+ isTruncated = isTruncated || truncateOut.isTruncated;
58
81
  }
59
82
 
60
- return textLines.join('\n');
83
+ out.text = textLines.join('\n');
84
+ out.isTruncated = isTruncated;
61
85
  }
62
86
 
63
87
  function prepareTruncateOptions(
@@ -69,16 +93,11 @@ function prepareTruncateOptions(
69
93
  options = options || {};
70
94
  let preparedOpts = extend({}, options) as InnerPreparedTruncateOption;
71
95
 
72
- preparedOpts.font = font;
73
96
  ellipsis = retrieve2(ellipsis, '...');
74
97
  preparedOpts.maxIterations = retrieve2(options.maxIterations, 2);
75
98
  const minChar = preparedOpts.minChar = retrieve2(options.minChar, 0);
76
- // FIXME
77
- // Other languages?
78
- preparedOpts.cnCharWidth = getWidth('国', font);
79
- // FIXME
80
- // Consider proportional font?
81
- const ascCharWidth = preparedOpts.ascCharWidth = getWidth('a', font);
99
+ const fontMeasureInfo = preparedOpts.fontMeasureInfo = ensureFontMeasureInfo(font);
100
+ const ascCharWidth = fontMeasureInfo.asciiCharWidth;
82
101
  preparedOpts.placeholder = retrieve2(options.placeholder, '');
83
102
 
84
103
  // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'.
@@ -88,7 +107,7 @@ function prepareTruncateOptions(
88
107
  contentWidth -= ascCharWidth;
89
108
  }
90
109
 
91
- let ellipsisWidth = getWidth(ellipsis, font);
110
+ let ellipsisWidth = measureWidth(fontMeasureInfo, ellipsis);
92
111
  if (ellipsisWidth > contentWidth) {
93
112
  ellipsis = '';
94
113
  ellipsisWidth = 0;
@@ -104,19 +123,27 @@ function prepareTruncateOptions(
104
123
  return preparedOpts;
105
124
  }
106
125
 
107
- function truncateSingleLine(textLine: string, options: InnerPreparedTruncateOption): string {
126
+ function truncateSingleLine(
127
+ out: {textLine: string, isTruncated: boolean},
128
+ textLine: string,
129
+ options: InnerPreparedTruncateOption
130
+ ): void {
108
131
  const containerWidth = options.containerWidth;
109
- const font = options.font;
110
132
  const contentWidth = options.contentWidth;
133
+ const fontMeasureInfo = options.fontMeasureInfo;
111
134
 
112
135
  if (!containerWidth) {
113
- return '';
136
+ out.textLine = '';
137
+ out.isTruncated = false;
138
+ return;
114
139
  }
115
140
 
116
- let lineWidth = getWidth(textLine, font);
141
+ let lineWidth = measureWidth(fontMeasureInfo, textLine);
117
142
 
118
143
  if (lineWidth <= containerWidth) {
119
- return textLine;
144
+ out.textLine = textLine;
145
+ out.isTruncated = false;
146
+ return;
120
147
  }
121
148
 
122
149
  for (let j = 0; ; j++) {
@@ -126,30 +153,32 @@ function truncateSingleLine(textLine: string, options: InnerPreparedTruncateOpti
126
153
  }
127
154
 
128
155
  const subLength = j === 0
129
- ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
156
+ ? estimateLength(textLine, contentWidth, fontMeasureInfo)
130
157
  : lineWidth > 0
131
158
  ? Math.floor(textLine.length * contentWidth / lineWidth)
132
159
  : 0;
133
160
 
134
161
  textLine = textLine.substr(0, subLength);
135
- lineWidth = getWidth(textLine, font);
162
+ lineWidth = measureWidth(fontMeasureInfo, textLine);
136
163
  }
137
164
 
138
165
  if (textLine === '') {
139
166
  textLine = options.placeholder;
140
167
  }
141
168
 
142
- return textLine;
169
+ out.textLine = textLine;
170
+ out.isTruncated = true;
143
171
  }
144
172
 
145
173
  function estimateLength(
146
- text: string, contentWidth: number, ascCharWidth: number, cnCharWidth: number
174
+ text: string,
175
+ contentWidth: number,
176
+ fontMeasureInfo: FontMeasureInfo
147
177
  ): number {
148
178
  let width = 0;
149
179
  let i = 0;
150
180
  for (let len = text.length; i < len && width < contentWidth; i++) {
151
- const charCode = text.charCodeAt(i);
152
- width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
181
+ width += measureCharWidth(fontMeasureInfo, text.charCodeAt(i));
153
182
  }
154
183
  return i;
155
184
  }
@@ -159,41 +188,57 @@ export interface PlainTextContentBlock {
159
188
  // Line height of actual content.
160
189
  calculatedLineHeight: number
161
190
 
191
+ // Calculated based on the text.
162
192
  contentWidth: number
163
193
  contentHeight: number
164
194
 
195
+ // i.e., `retrieve2(style.width/height, contentWidth/contentHeight)`
165
196
  width: number
166
197
  height: number
167
198
 
168
- /**
169
- * Real text width containing padding.
170
- * It should be the same as `width` if background is rendered
171
- * and `width` is set by user.
172
- */
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.
173
202
  outerWidth: number
174
203
  outerHeight: number
175
204
 
176
205
  lines: string[]
206
+
207
+ // Be `true` if and only if the result text is modified due to overflow, due to
208
+ // settings on either `overflow` or `lineOverflow`
209
+ isTruncated: boolean
177
210
  }
178
211
 
179
212
  export function parsePlainText(
180
213
  text: string,
181
- style?: TextStyleProps
214
+ style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
215
+ defaultOuterWidth: number | NullUndefined,
216
+ defaultOuterHeight: number | NullUndefined
182
217
  ): PlainTextContentBlock {
183
218
  text != null && (text += '');
184
219
 
185
220
  // textPadding has been normalized
186
221
  const overflow = style.overflow;
187
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;
188
225
  const font = style.font;
189
226
  const truncate = overflow === 'truncate';
190
227
  const calculatedLineHeight = getLineHeight(font);
191
228
  const lineHeight = retrieve2(style.lineHeight, calculatedLineHeight);
192
- const bgColorDrawn = !!(style.backgroundColor);
193
229
 
194
230
  const truncateLineOverflow = style.lineOverflow === 'truncate';
231
+ let isTruncated = false;
195
232
 
196
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
+
197
242
  let lines: string[];
198
243
 
199
244
  if (width != null && (overflow === 'break' || overflow === 'breakAll')) {
@@ -203,14 +248,18 @@ export function parsePlainText(
203
248
  lines = text ? text.split('\n') : [];
204
249
  }
205
250
 
206
- const contentHeight = lines.length * lineHeight;
207
- const height = retrieve2(style.height, contentHeight);
251
+ let contentHeight = lines.length * lineHeight;
252
+ if (height == null) {
253
+ height = contentHeight;
254
+ }
208
255
 
209
256
  // Truncate lines.
210
257
  if (contentHeight > height && truncateLineOverflow) {
211
258
  const lineCount = Math.floor(height / lineHeight);
212
259
 
260
+ isTruncated = isTruncated || (lines.length > lineCount);
213
261
  lines = lines.slice(0, lineCount);
262
+ contentHeight = lines.length * lineHeight;
214
263
 
215
264
  // TODO If show ellipse for line truncate
216
265
  // if (style.ellipsis) {
@@ -228,33 +277,29 @@ export function parsePlainText(
228
277
  placeholder: style.placeholder
229
278
  });
230
279
  // Having every line has '...' when truncate multiple lines.
280
+ const singleOut = {} as Parameters<typeof truncateSingleLine>[0];
231
281
  for (let i = 0; i < lines.length; i++) {
232
- lines[i] = truncateSingleLine(lines[i], options);
282
+ truncateSingleLine(singleOut, lines[i], options);
283
+ lines[i] = singleOut.textLine;
284
+ isTruncated = isTruncated || singleOut.isTruncated;
233
285
  }
234
286
  }
235
287
 
236
288
  // Calculate real text width and height
237
289
  let outerHeight = height;
238
290
  let contentWidth = 0;
291
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
239
292
  for (let i = 0; i < lines.length; i++) {
240
- contentWidth = Math.max(getWidth(lines[i], font), contentWidth);
293
+ contentWidth = Math.max(measureWidth(fontMeasureInfo, lines[i]), contentWidth);
241
294
  }
242
295
  if (width == null) {
243
- // When width is not explicitly set, use outerWidth as width.
296
+ // When width is not explicitly set, use contentWidth as width.
244
297
  width = contentWidth;
245
298
  }
246
299
 
247
- let outerWidth = contentWidth;
248
- if (padding) {
249
- outerHeight += padding[0] + padding[2];
250
- outerWidth += padding[1] + padding[3];
251
- width += padding[1] + padding[3];
252
- }
253
-
254
- if (bgColorDrawn) {
255
- // When render background, outerWidth should be the same as width.
256
- outerWidth = width;
257
- }
300
+ let outerWidth = width;
301
+ outerHeight += paddingV;
302
+ outerWidth += paddingH;
258
303
 
259
304
  return {
260
305
  lines: lines,
@@ -265,17 +310,21 @@ export function parsePlainText(
265
310
  calculatedLineHeight: calculatedLineHeight,
266
311
  contentWidth: contentWidth,
267
312
  contentHeight: contentHeight,
268
- width: width
313
+ width: width,
314
+ isTruncated: isTruncated
269
315
  };
270
316
  }
271
317
 
272
318
  class RichTextToken {
273
319
  styleName: string
274
320
  text: string
321
+
322
+ // Includes `tokenStyle.padding`
275
323
  width: number
276
324
  height: number
277
325
 
278
326
  // Inner height exclude padding
327
+ // i.e., `retrieve2(tokenStyle.height, token.contentHeight)`
279
328
  innerHeight: number
280
329
 
281
330
  // Width and height of actual text content.
@@ -304,16 +353,22 @@ class RichTextLine {
304
353
  }
305
354
  }
306
355
  export class RichTextContentBlock {
307
- // width/height of content
356
+ // i.e. `retrieve2(outermostStyle.width, contentWidth)`.
357
+ // exclude outermost style.padding.
308
358
  width: number = 0
309
359
  height: number = 0
310
- // Calculated text height
360
+ // Calculated text width/height based on content (including tokenStyle.padding).
311
361
  contentWidth: number = 0
312
362
  contentHeight: number = 0
313
- // 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.
314
366
  outerWidth: number = 0
315
367
  outerHeight: number = 0
316
368
  lines: RichTextLine[] = []
369
+ // Be `true` if and only if the result text is modified due to overflow, due to
370
+ // settings on either `overflow` or `lineOverflow`
371
+ isTruncated: boolean = false
317
372
  }
318
373
 
319
374
  type WrapInfo = {
@@ -326,7 +381,13 @@ type WrapInfo = {
326
381
  * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
327
382
  * If styleName is undefined, it is plain text.
328
383
  */
329
- export function parseRichText(text: string, style: TextStyleProps) {
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 {
330
391
  const contentBlock = new RichTextContentBlock();
331
392
 
332
393
  text != null && (text += '');
@@ -334,8 +395,19 @@ export function parseRichText(text: string, style: TextStyleProps) {
334
395
  return contentBlock;
335
396
  }
336
397
 
337
- const topWidth = style.width;
338
- 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
+
339
411
  const overflow = style.overflow;
340
412
  let wrapInfo: WrapInfo = (overflow === 'break' || overflow === 'breakAll') && topWidth != null
341
413
  ? {width: topWidth, accumWidth: 0, breakAll: overflow === 'breakAll'}
@@ -362,10 +434,9 @@ export function parseRichText(text: string, style: TextStyleProps) {
362
434
  let calculatedHeight = 0;
363
435
  let calculatedWidth = 0;
364
436
 
365
- const stlPadding = style.padding as number[];
366
-
367
437
  const truncate = overflow === 'truncate';
368
438
  const truncateLine = style.lineOverflow === 'truncate';
439
+ const tmpTruncateOut = {} as Parameters<typeof truncateText2>[0];
369
440
 
370
441
  // let prevToken: RichTextToken;
371
442
 
@@ -401,17 +472,18 @@ export function parseRichText(text: string, style: TextStyleProps) {
401
472
 
402
473
  textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
403
474
  token.height = tokenHeight;
404
- // Inlcude padding in lineHeight.
475
+ // Include padding in lineHeight.
405
476
  token.lineHeight = retrieve3(
406
477
  tokenStyle.lineHeight, style.lineHeight, tokenHeight
407
478
  );
408
479
 
409
- token.align = tokenStyle && tokenStyle.align || style.align;
480
+ token.align = tokenStyle && tokenStyle.align || topTextAlign;
410
481
  token.verticalAlign = tokenStyle && tokenStyle.verticalAlign || 'middle';
411
482
 
412
483
  if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
413
484
  // TODO Add ellipsis on the previous token.
414
485
  // prevToken.text =
486
+ const originalLength = contentBlock.lines.length;
415
487
  if (j > 0) {
416
488
  line.tokens = line.tokens.slice(0, j);
417
489
  finishLine(line, lineWidth, lineHeight);
@@ -420,6 +492,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
420
492
  else {
421
493
  contentBlock.lines = contentBlock.lines.slice(0, i);
422
494
  }
495
+ contentBlock.isTruncated = contentBlock.isTruncated || (contentBlock.lines.length < originalLength);
423
496
  break outer;
424
497
  }
425
498
 
@@ -432,7 +505,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
432
505
  token.percentWidth = styleTokenWidth;
433
506
  pendingList.push(token);
434
507
 
435
- token.contentWidth = getWidth(token.text, font);
508
+ token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
436
509
  // Do not truncate in this case, because there is no user case
437
510
  // and it is too complicated.
438
511
  }
@@ -461,15 +534,18 @@ export function parseRichText(text: string, style: TextStyleProps) {
461
534
  token.width = token.contentWidth = 0;
462
535
  }
463
536
  else {
464
- token.text = truncateText(
537
+ truncateText2(
538
+ tmpTruncateOut,
465
539
  token.text, remainTruncWidth - paddingH, font, style.ellipsis,
466
540
  {minChar: style.truncateMinChar}
467
541
  );
468
- token.width = token.contentWidth = getWidth(token.text, font);
542
+ token.text = tmpTruncateOut.text;
543
+ contentBlock.isTruncated = contentBlock.isTruncated || tmpTruncateOut.isTruncated;
544
+ token.width = token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
469
545
  }
470
546
  }
471
547
  else {
472
- token.contentWidth = getWidth(token.text, font);
548
+ token.contentWidth = measureWidth(ensureFontMeasureInfo(font), token.text);
473
549
  }
474
550
  }
475
551
 
@@ -489,10 +565,8 @@ export function parseRichText(text: string, style: TextStyleProps) {
489
565
  contentBlock.contentHeight = calculatedHeight;
490
566
  contentBlock.contentWidth = calculatedWidth;
491
567
 
492
- if (stlPadding) {
493
- contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
494
- contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
495
- }
568
+ contentBlock.outerWidth += stlPaddingH;
569
+ contentBlock.outerHeight += stlPaddingV;
496
570
 
497
571
  for (let i = 0; i < pendingList.length; i++) {
498
572
  const token = pendingList[i];
@@ -544,10 +618,12 @@ function pushTokens(
544
618
  strLines = res.lines;
545
619
  }
546
620
  }
547
- else {
621
+
622
+ if (!strLines) {
548
623
  strLines = str.split('\n');
549
624
  }
550
625
 
626
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
551
627
  for (let i = 0; i < strLines.length; i++) {
552
628
  const text = strLines[i];
553
629
  const token = new RichTextToken();
@@ -560,8 +636,8 @@ function pushTokens(
560
636
  }
561
637
  else {
562
638
  token.width = linesWidths
563
- ? linesWidths[i] // Caculated width in the wrap
564
- : getWidth(text, font);
639
+ ? linesWidths[i] // Calculated width in the wrap
640
+ : measureWidth(fontMeasureInfo, text);
565
641
  }
566
642
 
567
643
  // The first token should be appended to the last line if not new line.
@@ -621,6 +697,11 @@ function isWordBreakChar(ch: string) {
621
697
  return true;
622
698
  }
623
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
+ */
624
705
  function wrapText(
625
706
  text: string,
626
707
  font: string,
@@ -634,6 +715,7 @@ function wrapText(
634
715
  let currentWord = '';
635
716
  let currentWordWidth = 0;
636
717
  let accumWidth = 0;
718
+ const fontMeasureInfo = ensureFontMeasureInfo(font);
637
719
 
638
720
  for (let i = 0; i < text.length; i++) {
639
721
 
@@ -653,7 +735,7 @@ function wrapText(
653
735
  continue;
654
736
  }
655
737
 
656
- const chWidth = getWidth(ch, font);
738
+ const chWidth = measureCharWidth(fontMeasureInfo, ch.charCodeAt(0));
657
739
  const inWord = isBreakAll ? false : !isWordBreakChar(ch);
658
740
 
659
741
  if (!lines.length
@@ -735,12 +817,6 @@ function wrapText(
735
817
  }
736
818
  }
737
819
 
738
- if (!lines.length && !line) {
739
- line = text;
740
- currentWord = '';
741
- currentWordWidth = 0;
742
- }
743
-
744
820
  // Append last line.
745
821
  if (currentWord) {
746
822
  line += currentWord;
@@ -761,4 +837,56 @@ function wrapText(
761
837
  lines: lines,
762
838
  linesWidths
763
839
  };
764
- }
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 = {
@@ -93,7 +93,7 @@ function clampCssFloat(f: number): number { // Clamp to float 0.0 .. 1.0.
93
93
  return f < 0 ? 0 : f > 1 ? 1 : f;
94
94
  }
95
95
 
96
- function parseCssInt(val: string | number): number { // int or percentage.
96
+ export function parseCssInt(val: string | number): number { // int or percentage.
97
97
  let str = val as string;
98
98
  if (str.length && str.charAt(str.length - 1) === '%') {
99
99
  return clampCssByte(parseFloat(str) / 100 * 255);
@@ -101,7 +101,7 @@ function parseCssInt(val: string | number): number { // int or percentage.
101
101
  return clampCssByte(parseInt(str, 10));
102
102
  }
103
103
 
104
- function parseCssFloat(val: string | number): number { // float or percentage.
104
+ export function parseCssFloat(val: string | number): number { // float or percentage.
105
105
  let str = val as string;
106
106
  if (str.length && str.charAt(str.length - 1) === '%') {
107
107
  return clampCssFloat(parseFloat(str) / 100);
@@ -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
  }