zrender-nightly 5.7.0-dev.20250619 → 5.7.0-dev.20250621
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.
- package/README.md +1 -1
- package/build/prepublish.js +20 -0
- package/dist/zrender.js +563 -277
- package/dist/zrender.js.map +1 -1
- package/dist/zrender.min.js +1 -1
- package/lib/Element.d.ts +4 -0
- package/lib/Element.js +34 -16
- package/lib/Handler.js +1 -1
- package/lib/Storage.js +20 -20
- package/lib/canvas/graphic.js +1 -1
- package/lib/contain/text.d.ts +14 -2
- package/lib/contain/text.js +65 -15
- package/lib/core/BoundingRect.d.ts +25 -3
- package/lib/core/BoundingRect.js +182 -76
- package/lib/core/OrientedBoundingRect.d.ts +2 -2
- package/lib/core/OrientedBoundingRect.js +50 -34
- package/lib/core/PathProxy.d.ts +1 -0
- package/lib/core/PathProxy.js +16 -1
- package/lib/core/dom.d.ts +1 -0
- package/lib/core/dom.js +17 -0
- package/lib/core/env.js +15 -10
- package/lib/core/types.d.ts +1 -0
- package/lib/core/util.d.ts +1 -0
- package/lib/core/util.js +2 -1
- package/lib/graphic/Displayable.js +1 -1
- package/lib/graphic/Text.d.ts +4 -2
- package/lib/graphic/Text.js +23 -14
- package/lib/graphic/helper/parseText.d.ts +13 -4
- package/lib/graphic/helper/parseText.js +110 -54
- package/lib/svg-legacy/helper/ClippathManager.js +6 -6
- package/lib/tool/color.d.ts +3 -1
- package/lib/tool/color.js +6 -6
- package/lib/tool/parseSVG.js +11 -0
- package/lib/tool/path.js +7 -4
- package/lib/zrender.d.ts +1 -1
- package/lib/zrender.js +1 -1
- package/package.json +3 -2
- package/src/Element.ts +69 -16
- package/src/Handler.ts +1 -1
- package/src/Storage.ts +25 -24
- package/src/canvas/graphic.ts +1 -1
- package/src/canvas/helper.ts +1 -1
- package/src/contain/text.ts +103 -19
- package/src/core/BoundingRect.ts +308 -87
- package/src/core/OrientedBoundingRect.ts +86 -46
- package/src/core/PathProxy.ts +17 -1
- package/src/core/Transformable.ts +2 -0
- package/src/core/dom.ts +24 -0
- package/src/core/env.ts +31 -24
- package/src/core/matrix.ts +2 -1
- package/src/core/types.ts +2 -0
- package/src/core/util.ts +4 -2
- package/src/graphic/Displayable.ts +1 -3
- package/src/graphic/Group.ts +2 -0
- package/src/graphic/Text.ts +68 -21
- package/src/graphic/helper/parseText.ts +211 -83
- package/src/svg-legacy/helper/ClippathManager.ts +5 -5
- package/src/tool/color.ts +15 -11
- package/src/tool/parseSVG.ts +12 -1
- package/src/tool/path.ts +9 -4
- 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 {
|
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
|
-
|
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
|
-
|
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
|
-
|
78
|
+
truncateSingleLine(truncateOut, textLines[i], options as InnerPreparedTruncateOption);
|
79
|
+
textLines[i] = truncateOut.textLine;
|
80
|
+
isTruncated = isTruncated || truncateOut.isTruncated;
|
58
81
|
}
|
59
82
|
|
60
|
-
|
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
|
-
|
77
|
-
|
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 =
|
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(
|
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
|
-
|
136
|
+
out.textLine = '';
|
137
|
+
out.isTruncated = false;
|
138
|
+
return;
|
114
139
|
}
|
115
140
|
|
116
|
-
let lineWidth =
|
141
|
+
let lineWidth = measureWidth(fontMeasureInfo, textLine);
|
117
142
|
|
118
143
|
if (lineWidth <= containerWidth) {
|
119
|
-
|
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,
|
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 =
|
162
|
+
lineWidth = measureWidth(fontMeasureInfo, textLine);
|
136
163
|
}
|
137
164
|
|
138
165
|
if (textLine === '') {
|
139
166
|
textLine = options.placeholder;
|
140
167
|
}
|
141
168
|
|
142
|
-
|
169
|
+
out.textLine = textLine;
|
170
|
+
out.isTruncated = true;
|
143
171
|
}
|
144
172
|
|
145
173
|
function estimateLength(
|
146
|
-
text: string,
|
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
|
-
|
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
|
-
|
170
|
-
|
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
|
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
|
-
|
207
|
-
|
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
|
-
|
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(
|
293
|
+
contentWidth = Math.max(measureWidth(fontMeasureInfo, lines[i]), contentWidth);
|
241
294
|
}
|
242
295
|
if (width == null) {
|
243
|
-
// When width is not explicitly set, use
|
296
|
+
// When width is not explicitly set, use contentWidth as width.
|
244
297
|
width = contentWidth;
|
245
298
|
}
|
246
299
|
|
247
|
-
let outerWidth =
|
248
|
-
|
249
|
-
|
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
|
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
|
-
//
|
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(
|
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
|
338
|
-
const
|
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
|
-
//
|
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 ||
|
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 =
|
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
|
-
|
537
|
+
truncateText2(
|
538
|
+
tmpTruncateOut,
|
465
539
|
token.text, remainTruncWidth - paddingH, font, style.ellipsis,
|
466
540
|
{minChar: style.truncateMinChar}
|
467
541
|
);
|
468
|
-
token.
|
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 =
|
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
|
-
|
493
|
-
|
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
|
-
|
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] //
|
564
|
-
:
|
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 =
|
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
|
-
|
146
|
-
if (
|
147
|
-
|
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(
|
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
|
}
|