Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support output the state isTruncated in Text element. #1101

Merged
merged 1 commit into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/graphic/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
*/
innerTransformable: Transformable

// Be `true` if and only if the result text is modified due to overflow, due to
// settings on either `overflow` or `lineOverflow`. Based on this the caller can
// take some action like showing the original text in a particular tip.
// Only take effect after rendering. So do not visit it before it.
isTruncated: boolean

private _children: (ZRImage | Rect | TSpan)[] = []

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

const defaultStyle = this._defaultStyle;

this.isTruncated = !!contentBlock.isTruncated;

const baseX = style.x || 0;
const baseY = style.y || 0;
const textAlign = style.align || defaultStyle.align || 'left';
Expand Down Expand Up @@ -635,6 +643,8 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
const textAlign = style.align || defaultStyle.align;
const verticalAlign = style.verticalAlign || defaultStyle.verticalAlign;

this.isTruncated = !!contentBlock.isTruncated;

const boxX = adjustTextX(baseX, outerWidth, textAlign);
const boxY = adjustTextY(baseY, outerHeight, verticalAlign);
let xLeft = boxX;
Expand Down
72 changes: 61 additions & 11 deletions src/graphic/helper/parseText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,42 @@ export function truncateText(
ellipsis?: string,
options?: InnerTruncateOption
): string {
const out = {} as Parameters<typeof truncateText2>[0];
truncateText2(out, text, containerWidth, font, ellipsis, options);
return out.text;
}

// PENDING: not sure whether `truncateText` is used outside zrender, since it has an `export`
// specifier. So keep it and perform the interface modification in `truncateText2`.
function truncateText2(
out: {text: string, isTruncated: boolean},
text: string,
containerWidth: number,
font: string,
ellipsis?: string,
options?: InnerTruncateOption
): void {
if (!containerWidth) {
return '';
out.text = '';
out.isTruncated = false;
return;
}

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

// FIXME
// It is not appropriate that every line has '...' when truncate multiple lines.
let isTruncated = false;
const truncateOut = {} as Parameters<typeof truncateSingleLine>[0];
for (let i = 0, len = textLines.length; i < len; i++) {
textLines[i] = truncateSingleLine(textLines[i], options as InnerPreparedTruncateOption);
truncateSingleLine(truncateOut, textLines[i], options as InnerPreparedTruncateOption);
textLines[i] = truncateOut.textLine;
isTruncated = isTruncated || truncateOut.isTruncated;
}

return textLines.join('\n');
out.text = textLines.join('\n');
out.isTruncated = isTruncated;
}

function prepareTruncateOptions(
Expand Down Expand Up @@ -104,19 +126,27 @@ function prepareTruncateOptions(
return preparedOpts;
}

function truncateSingleLine(textLine: string, options: InnerPreparedTruncateOption): string {
function truncateSingleLine(
out: {textLine: string, isTruncated: boolean},
textLine: string,
options: InnerPreparedTruncateOption
): void {
const containerWidth = options.containerWidth;
const font = options.font;
const contentWidth = options.contentWidth;

if (!containerWidth) {
return '';
out.textLine = '';
out.isTruncated = false;
return;
}

let lineWidth = getWidth(textLine, font);

if (lineWidth <= containerWidth) {
return textLine;
out.textLine = textLine;
out.isTruncated = false;
return;
}

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

return textLine;
out.textLine = textLine;
out.isTruncated = true;
}

function estimateLength(
Expand Down Expand Up @@ -174,6 +205,10 @@ export interface PlainTextContentBlock {
outerHeight: number

lines: string[]

// Be `true` if and only if the result text is modified due to overflow, due to
// settings on either `overflow` or `lineOverflow`
isTruncated: boolean
}

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

const truncateLineOverflow = style.lineOverflow === 'truncate';
let isTruncated = false;

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

isTruncated = isTruncated || (lines.length > lineCount);
lines = lines.slice(0, lineCount);

// TODO If show ellipse for line truncate
Expand All @@ -228,8 +265,11 @@ export function parsePlainText(
placeholder: style.placeholder
});
// Having every line has '...' when truncate multiple lines.
const singleOut = {} as Parameters<typeof truncateSingleLine>[0];
for (let i = 0; i < lines.length; i++) {
lines[i] = truncateSingleLine(lines[i], options);
truncateSingleLine(singleOut, lines[i], options);
lines[i] = singleOut.textLine;
isTruncated = isTruncated || singleOut.isTruncated;
}
}

Expand Down Expand Up @@ -265,7 +305,8 @@ export function parsePlainText(
calculatedLineHeight: calculatedLineHeight,
contentWidth: contentWidth,
contentHeight: contentHeight,
width: width
width: width,
isTruncated: isTruncated
};
}

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

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

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

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

// let prevToken: RichTextToken;

Expand Down Expand Up @@ -412,6 +457,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
// TODO Add ellipsis on the previous token.
// prevToken.text =
const originalLength = contentBlock.lines.length;
if (j > 0) {
line.tokens = line.tokens.slice(0, j);
finishLine(line, lineWidth, lineHeight);
Expand All @@ -420,6 +466,7 @@ export function parseRichText(text: string, style: TextStyleProps) {
else {
contentBlock.lines = contentBlock.lines.slice(0, i);
}
contentBlock.isTruncated = contentBlock.isTruncated || (contentBlock.lines.length < originalLength);
break outer;
}

Expand Down Expand Up @@ -461,10 +508,13 @@ export function parseRichText(text: string, style: TextStyleProps) {
token.width = token.contentWidth = 0;
}
else {
token.text = truncateText(
truncateText2(
tmpTruncateOut,
token.text, remainTruncWidth - paddingH, font, style.ellipsis,
{minChar: style.truncateMinChar}
);
token.text = tmpTruncateOut.text;
contentBlock.isTruncated = contentBlock.isTruncated || tmpTruncateOut.isTruncated;
token.width = token.contentWidth = getWidth(token.text, font);
}
}
Expand Down
Loading
Loading