Skip to content

Commit 980b0ea

Browse files
authored
Merge pull request #1101 from ecomfe/feat/truncate-inquiry
feat: support output the state `isTruncated` in `Text` element.
2 parents 4055b18 + 92dbb39 commit 980b0ea

File tree

3 files changed

+87
-11
lines changed

3 files changed

+87
-11
lines changed

src/graphic/Text.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
275275
*/
276276
innerTransformable: Transformable
277277

278+
// Be `true` if and only if the result text is modified due to overflow, due to
279+
// settings on either `overflow` or `lineOverflow`. Based on this the caller can
280+
// take some action like showing the original text in a particular tip.
281+
// Only take effect after rendering. So do not visit it before it.
282+
isTruncated: boolean
283+
278284
private _children: (ZRImage | Rect | TSpan)[] = []
279285

280286
private _childCursor: 0
@@ -497,6 +503,8 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
497503

498504
const defaultStyle = this._defaultStyle;
499505

506+
this.isTruncated = !!contentBlock.isTruncated;
507+
500508
const baseX = style.x || 0;
501509
const baseY = style.y || 0;
502510
const textAlign = style.align || defaultStyle.align || 'left';
@@ -635,6 +643,8 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
635643
const textAlign = style.align || defaultStyle.align;
636644
const verticalAlign = style.verticalAlign || defaultStyle.verticalAlign;
637645

646+
this.isTruncated = !!contentBlock.isTruncated;
647+
638648
const boxX = adjustTextX(baseX, outerWidth, textAlign);
639649
const boxY = adjustTextY(baseY, outerHeight, verticalAlign);
640650
let xLeft = boxX;

src/graphic/helper/parseText.ts

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,42 @@ export function truncateText(
4444
ellipsis?: string,
4545
options?: InnerTruncateOption
4646
): string {
47+
const out = {} as Parameters<typeof truncateText2>[0];
48+
truncateText2(out, text, containerWidth, font, ellipsis, options);
49+
return out.text;
50+
}
51+
52+
// PENDING: not sure whether `truncateText` is used outside zrender, since it has an `export`
53+
// specifier. So keep it and perform the interface modification in `truncateText2`.
54+
function truncateText2(
55+
out: {text: string, isTruncated: boolean},
56+
text: string,
57+
containerWidth: number,
58+
font: string,
59+
ellipsis?: string,
60+
options?: InnerTruncateOption
61+
): void {
4762
if (!containerWidth) {
48-
return '';
63+
out.text = '';
64+
out.isTruncated = false;
65+
return;
4966
}
5067

5168
const textLines = (text + '').split('\n');
5269
options = prepareTruncateOptions(containerWidth, font, ellipsis, options);
5370

5471
// FIXME
5572
// It is not appropriate that every line has '...' when truncate multiple lines.
73+
let isTruncated = false;
74+
const truncateOut = {} as Parameters<typeof truncateSingleLine>[0];
5675
for (let i = 0, len = textLines.length; i < len; i++) {
57-
textLines[i] = truncateSingleLine(textLines[i], options as InnerPreparedTruncateOption);
76+
truncateSingleLine(truncateOut, textLines[i], options as InnerPreparedTruncateOption);
77+
textLines[i] = truncateOut.textLine;
78+
isTruncated = isTruncated || truncateOut.isTruncated;
5879
}
5980

60-
return textLines.join('\n');
81+
out.text = textLines.join('\n');
82+
out.isTruncated = isTruncated;
6183
}
6284

6385
function prepareTruncateOptions(
@@ -104,19 +126,27 @@ function prepareTruncateOptions(
104126
return preparedOpts;
105127
}
106128

107-
function truncateSingleLine(textLine: string, options: InnerPreparedTruncateOption): string {
129+
function truncateSingleLine(
130+
out: {textLine: string, isTruncated: boolean},
131+
textLine: string,
132+
options: InnerPreparedTruncateOption
133+
): void {
108134
const containerWidth = options.containerWidth;
109135
const font = options.font;
110136
const contentWidth = options.contentWidth;
111137

112138
if (!containerWidth) {
113-
return '';
139+
out.textLine = '';
140+
out.isTruncated = false;
141+
return;
114142
}
115143

116144
let lineWidth = getWidth(textLine, font);
117145

118146
if (lineWidth <= containerWidth) {
119-
return textLine;
147+
out.textLine = textLine;
148+
out.isTruncated = false;
149+
return;
120150
}
121151

122152
for (let j = 0; ; j++) {
@@ -139,7 +169,8 @@ function truncateSingleLine(textLine: string, options: InnerPreparedTruncateOpti
139169
textLine = options.placeholder;
140170
}
141171

142-
return textLine;
172+
out.textLine = textLine;
173+
out.isTruncated = true;
143174
}
144175

145176
function estimateLength(
@@ -174,6 +205,10 @@ export interface PlainTextContentBlock {
174205
outerHeight: number
175206

176207
lines: string[]
208+
209+
// Be `true` if and only if the result text is modified due to overflow, due to
210+
// settings on either `overflow` or `lineOverflow`
211+
isTruncated: boolean
177212
}
178213

179214
export function parsePlainText(
@@ -192,6 +227,7 @@ export function parsePlainText(
192227
const bgColorDrawn = !!(style.backgroundColor);
193228

194229
const truncateLineOverflow = style.lineOverflow === 'truncate';
230+
let isTruncated = false;
195231

196232
let width = style.width;
197233
let lines: string[];
@@ -210,6 +246,7 @@ export function parsePlainText(
210246
if (contentHeight > height && truncateLineOverflow) {
211247
const lineCount = Math.floor(height / lineHeight);
212248

249+
isTruncated = isTruncated || (lines.length > lineCount);
213250
lines = lines.slice(0, lineCount);
214251

215252
// TODO If show ellipse for line truncate
@@ -228,8 +265,11 @@ export function parsePlainText(
228265
placeholder: style.placeholder
229266
});
230267
// Having every line has '...' when truncate multiple lines.
268+
const singleOut = {} as Parameters<typeof truncateSingleLine>[0];
231269
for (let i = 0; i < lines.length; i++) {
232-
lines[i] = truncateSingleLine(lines[i], options);
270+
truncateSingleLine(singleOut, lines[i], options);
271+
lines[i] = singleOut.textLine;
272+
isTruncated = isTruncated || singleOut.isTruncated;
233273
}
234274
}
235275

@@ -265,7 +305,8 @@ export function parsePlainText(
265305
calculatedLineHeight: calculatedLineHeight,
266306
contentWidth: contentWidth,
267307
contentHeight: contentHeight,
268-
width: width
308+
width: width,
309+
isTruncated: isTruncated
269310
};
270311
}
271312

@@ -314,6 +355,9 @@ export class RichTextContentBlock {
314355
outerWidth: number = 0
315356
outerHeight: number = 0
316357
lines: RichTextLine[] = []
358+
// Be `true` if and only if the result text is modified due to overflow, due to
359+
// settings on either `overflow` or `lineOverflow`
360+
isTruncated: boolean = false
317361
}
318362

319363
type WrapInfo = {
@@ -326,7 +370,7 @@ type WrapInfo = {
326370
* Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
327371
* If styleName is undefined, it is plain text.
328372
*/
329-
export function parseRichText(text: string, style: TextStyleProps) {
373+
export function parseRichText(text: string, style: TextStyleProps): RichTextContentBlock {
330374
const contentBlock = new RichTextContentBlock();
331375

332376
text != null && (text += '');
@@ -366,6 +410,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
366410

367411
const truncate = overflow === 'truncate';
368412
const truncateLine = style.lineOverflow === 'truncate';
413+
const tmpTruncateOut = {} as Parameters<typeof truncateText2>[0];
369414

370415
// let prevToken: RichTextToken;
371416

@@ -412,6 +457,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
412457
if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
413458
// TODO Add ellipsis on the previous token.
414459
// prevToken.text =
460+
const originalLength = contentBlock.lines.length;
415461
if (j > 0) {
416462
line.tokens = line.tokens.slice(0, j);
417463
finishLine(line, lineWidth, lineHeight);
@@ -420,6 +466,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
420466
else {
421467
contentBlock.lines = contentBlock.lines.slice(0, i);
422468
}
469+
contentBlock.isTruncated = contentBlock.isTruncated || (contentBlock.lines.length < originalLength);
423470
break outer;
424471
}
425472

@@ -461,10 +508,13 @@ export function parseRichText(text: string, style: TextStyleProps) {
461508
token.width = token.contentWidth = 0;
462509
}
463510
else {
464-
token.text = truncateText(
511+
truncateText2(
512+
tmpTruncateOut,
465513
token.text, remainTruncWidth - paddingH, font, style.ellipsis,
466514
{minChar: style.truncateMinChar}
467515
);
516+
token.text = tmpTruncateOut.text;
517+
contentBlock.isTruncated = contentBlock.isTruncated || tmpTruncateOut.isTruncated;
468518
token.width = token.contentWidth = getWidth(token.text, font);
469519
}
470520
}

test/text-overflow.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
height: 100%;
1818
margin: 0;
1919
}
20+
#message {
21+
position: relative;
22+
padding: 20px 0;
23+
}
2024
</style>
25+
<div id="message"></div>
2126
<div id="main"></div>
2227

2328
<script>
@@ -123,6 +128,17 @@
123128
console.time('render');
124129
zr.refreshImmediately();
125130
console.timeEnd('render');
131+
132+
const msgHTML = [];
133+
textElementList.forEach((text, idx) => {
134+
msgHTML.push('[text block ' + idx + '] text.isTruncated: ' + text.isTruncated);
135+
});
136+
updateMessageDisplay(msgHTML.join('<br/>'));
137+
}
138+
139+
function updateMessageDisplay(unescapedHTML) {
140+
const dom = document.getElementById('message');
141+
dom.innerHTML = unescapedHTML;
126142
}
127143

128144
update();

0 commit comments

Comments
 (0)