Skip to content

Commit 17a272f

Browse files
authored
Merge pull request #1135 from ecomfe/master
chore: merge master into release
2 parents 4cb9642 + 70f25b7 commit 17a272f

File tree

5 files changed

+243
-104
lines changed

5 files changed

+243
-104
lines changed

src/contain/text.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function measureWidth(fontMeasureInfo: FontMeasureInfo, text: string): nu
106106

107107

108108
/**
109-
*
109+
* @deprecated See `getBoundingRect`.
110110
* Get bounding rect for inner usage(TSpan)
111111
* Which not include text newline.
112112
*/
@@ -128,6 +128,9 @@ export function innerGetBoundingRect(
128128
}
129129

130130
/**
131+
* @deprecated Use `(new Text(...)).getBoundingRect()` or `(new TSpan(...)).getBoundingRect()` instead.
132+
* This method behaves differently from `Text#getBoundingRect()` - e.g., it does not support the overflow
133+
* strategy, and only has single line height even if multiple lines.
131134
*
132135
* Get bounding rect for outer usage. Compatitable with old implementation
133136
* Which includes text newline.

src/graphic/TSpan.ts

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import Displayable, { DisplayableProps, DisplayableStatePropNames } from './Displayable';
2-
import { getBoundingRect } from '../contain/text';
32
import BoundingRect from '../core/BoundingRect';
43
import { PathStyleProps, DEFAULT_PATH_STYLE } from './Path';
54
import { createObject, defaults } from '../core/util';
6-
import { FontStyle, FontWeight, TextAlign, TextVerticalAlign } from '../core/types';
5+
import { FontStyle, FontWeight } from '../core/types';
76
import { DEFAULT_FONT } from '../core/platform';
7+
import { tSpanCreateBoundingRect, tSpanHasStroke } from './helper/parseText';
88

99
export interface TSpanStyleProps extends PathStyleProps {
1010

@@ -53,9 +53,7 @@ class TSpan extends Displayable<TSpanProps> {
5353
style: TSpanStyleProps
5454

5555
hasStroke() {
56-
const style = this.style;
57-
const stroke = style.stroke;
58-
return stroke != null && stroke !== 'none' && style.lineWidth > 0;
56+
return tSpanHasStroke(this.style);
5957
}
6058

6159
hasFill() {
@@ -81,31 +79,8 @@ class TSpan extends Displayable<TSpanProps> {
8179
}
8280

8381
getBoundingRect(): BoundingRect {
84-
const style = this.style;
85-
8682
if (!this._rect) {
87-
let text = style.text;
88-
text != null ? (text += '') : (text = '');
89-
90-
const rect = getBoundingRect(
91-
text,
92-
style.font,
93-
style.textAlign as TextAlign,
94-
style.textBaseline as TextVerticalAlign
95-
);
96-
97-
rect.x += style.x || 0;
98-
rect.y += style.y || 0;
99-
100-
if (this.hasStroke()) {
101-
const w = style.lineWidth;
102-
rect.x -= w / 2;
103-
rect.y -= w / 2;
104-
rect.width += w;
105-
rect.height += w;
106-
}
107-
108-
this._rect = rect;
83+
this._rect = tSpanCreateBoundingRect(this.style);
10984
}
11085

11186
return this._rect;

src/graphic/Text.ts

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
TextAlign, TextVerticalAlign, ImageLike, Dictionary, MapToType, FontWeight, FontStyle, NullUndefined
77
} from '../core/types';
88
import {
9-
parseRichText, parsePlainText, CalcInnerTextOverflowAreaOut, calcInnerTextOverflowArea
9+
parseRichText, parsePlainText, CalcInnerTextOverflowAreaOut, calcInnerTextOverflowArea,
10+
tSpanCreateBoundingRect2,
1011
} from './helper/parseText';
1112
import TSpan, { TSpanStyleProps } from './TSpan';
1213
import { retrieve2, each, normalizeCssArray, trim, retrieve3, extend, keys, defaults } from '../core/util';
@@ -332,7 +333,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
332333
}
333334
}
334335

335-
updateTransform() {
336+
updateTransform() {
336337
const innerTransformable = this.innerTransformable;
337338
if (innerTransformable) {
338339
innerTransformable.updateTransform();
@@ -528,7 +529,6 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
528529

529530
const outerHeight = contentBlock.outerHeight;
530531
const outerWidth = contentBlock.outerWidth;
531-
const contentWidth = contentBlock.contentWidth;
532532

533533
const textLines = contentBlock.lines;
534534
const lineHeight = contentBlock.lineHeight;
@@ -544,6 +544,13 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
544544
const boxY = adjustTextY(baseY, outerHeight, verticalAlign);
545545
needDrawBg && this._renderBackground(style, style, boxX, boxY, outerWidth, outerHeight);
546546
}
547+
// PENDING:
548+
// Should text bounding rect contains style.padding, style.width, style.height when NO background
549+
// and border displayed? It depends on how to define "boundingRect". HTML `getBoundingClientRect`
550+
// contains padding in that case. But currently ZRText does not.
551+
// If implement that, an extra invisible Rect may need to be added as the placeholder for the bounding
552+
// rect computation, considering animation of padding. But will it degrade performance for the most
553+
// used plain texts cases?
547554

548555
// `textBaseline` is set as 'middle'.
549556
textY += lineHeight / 2;
@@ -559,6 +566,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
559566
}
560567

561568
let defaultLineWidth = 0;
569+
let usingDefaultStroke = false;
562570
let useDefaultFill = false;
563571
const textFill = getFill(
564572
'fill' in style
@@ -580,16 +588,12 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
580588
// we give the auto lineWidth to display the given stoke color.
581589
&& (!defaultStyle.autoStroke || useDefaultFill)
582590
)
583-
? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke)
591+
? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, usingDefaultStroke = true, defaultStyle.stroke)
584592
: null
585593
);
586594

587595
const hasShadow = style.textShadowBlur > 0;
588596

589-
const fixedBoundingRect = style.width != null
590-
&& (style.overflow === 'truncate' || style.overflow === 'break' || style.overflow === 'breakAll');
591-
const calculatedLineHeight = contentBlock.calculatedLineHeight;
592-
593597
for (let i = 0; i < textLines.length; i++) {
594598
const el = this._getOrCreateChild(TSpan);
595599
// Always create new style.
@@ -633,19 +637,27 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
633637

634638
textY += lineHeight;
635639

636-
if (fixedBoundingRect) {
637-
el.setBoundingRect(new BoundingRect(
638-
adjustTextX(subElStyle.x, contentWidth, subElStyle.textAlign as TextAlign),
639-
adjustTextY(subElStyle.y, calculatedLineHeight, subElStyle.textBaseline as TextVerticalAlign),
640-
/**
641-
* Text boundary should be the real text width.
642-
* Otherwise, there will be extra space in the
643-
* bounding rect calculated.
644-
*/
645-
contentWidth,
646-
calculatedLineHeight
647-
));
648-
}
640+
// Always set tspan bounding rect to guarantee the consistency if users lays out based
641+
// on these bounding rects.
642+
el.setBoundingRect(tSpanCreateBoundingRect2(
643+
subElStyle,
644+
contentBlock.contentWidth,
645+
contentBlock.calculatedLineHeight,
646+
// Should text bounding rect includes text stroke width?
647+
// Pros:
648+
// - Intuitively, and by convention, bounding rect of `Path` always includes stroke width.
649+
// Cons:
650+
// - It's unpredictable for users whether "auto stroke" is applied. If stroke width is included
651+
// and multiple texts are laid out based on its bounding rect, the position of texts may vary
652+
// and is unpredictable - especially in limited space (e.g., see echarts pie label cases).
653+
// - "auto stroke" attempts to use the same color as the background to make the border to be
654+
// invisible in most cases, thus it might be more reasonable to be excluded from bounding rect.
655+
// Conclusion:
656+
// - If users specifies style.stroke, it will be included into the bounding rect as normal.
657+
// Otherwise, keep the stroke width as `0` in this case to guarantee consistency of bounding
658+
// rect based layout, regardless of whether "auto stroke" is applied.
659+
usingDefaultStroke ? 0 : null
660+
));
649661
}
650662
}
651663

@@ -801,6 +813,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
801813
const defaultStyle = this._defaultStyle;
802814
let useDefaultFill = false;
803815
let defaultLineWidth = 0;
816+
let usingDefaultStroke = false;
804817
const textFill = getFill(
805818
'fill' in tokenStyle ? tokenStyle.fill
806819
: 'fill' in style ? style.fill
@@ -812,9 +825,9 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
812825
: (
813826
!bgColorDrawn
814827
&& !parentBgColorDrawn
815-
// See the strategy explained above.
828+
// See the strategy explained `_updatePlainTexts`.
816829
&& (!defaultStyle.autoStroke || useDefaultFill)
817-
) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke)
830+
) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, usingDefaultStroke = true, defaultStyle.stroke)
818831
: null
819832
);
820833

@@ -852,14 +865,13 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
852865
subElStyle.fill = textFill;
853866
}
854867

855-
const textWidth = token.contentWidth;
856-
const textHeight = token.contentHeight;
857868
// NOTE: Should not call dirtyStyle after setBoundingRect. Or it will be cleared.
858-
el.setBoundingRect(new BoundingRect(
859-
adjustTextX(subElStyle.x, textWidth, subElStyle.textAlign as TextAlign),
860-
adjustTextY(subElStyle.y, textHeight, subElStyle.textBaseline as TextVerticalAlign),
861-
textWidth,
862-
textHeight
869+
el.setBoundingRect(tSpanCreateBoundingRect2(
870+
subElStyle,
871+
token.contentWidth,
872+
token.contentHeight,
873+
// See the strategy explained `_updatePlainTexts`.
874+
usingDefaultStroke ? 0 : null
863875
));
864876
}
865877

src/graphic/helper/parseText.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
reduce,
77
} from '../../core/util';
88
import { TextAlign, TextVerticalAlign, ImageLike, Dictionary, NullUndefined } from '../../core/types';
9-
import { DefaultTextStyle, TextStyleProps } from '../Text';
9+
import type { DefaultTextStyle, TextStyleProps } from '../Text';
10+
import type { TSpanStyleProps } from '../TSpan';
1011
import {
1112
adjustTextX,
1213
adjustTextY,
@@ -210,12 +211,12 @@ export interface PlainTextContentBlock {
210211
}
211212

212213
export function parsePlainText(
213-
text: string,
214+
rawText: unknown,
214215
style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
215216
defaultOuterWidth: number | NullUndefined,
216217
defaultOuterHeight: number | NullUndefined
217218
): PlainTextContentBlock {
218-
text != null && (text += '');
219+
const text = formatText(rawText);
219220

220221
// textPadding has been normalized
221222
const overflow = style.overflow;
@@ -382,15 +383,15 @@ type WrapInfo = {
382383
* If styleName is undefined, it is plain text.
383384
*/
384385
export function parseRichText(
385-
text: string,
386+
rawText: unknown,
386387
style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
387388
defaultOuterWidth: number | NullUndefined,
388389
defaultOuterHeight: number | NullUndefined,
389390
topTextAlign: TextAlign
390391
): RichTextContentBlock {
391392
const contentBlock = new RichTextContentBlock();
392393

393-
text != null && (text += '');
394+
const text = formatText(rawText);
394395
if (!text) {
395396
return contentBlock;
396397
}
@@ -890,3 +891,61 @@ export type CalcInnerTextOverflowAreaOut = {
890891
outerHeight: number | NullUndefined
891892
};
892893

894+
// For backward compatibility, and possibly loose type.
895+
function formatText(text: unknown): string {
896+
return text != null ? (text += '') : (text = '');
897+
}
898+
899+
export function tSpanCreateBoundingRect(
900+
style: Pick<TSpanStyleProps, 'text' | 'font' | 'x' | 'y' | 'textAlign' | 'textBaseline' | 'lineWidth'>,
901+
): BoundingRect {
902+
// Should follow the same way as `parsePlainText` to guarantee the consistency of the result.
903+
const text = formatText(style.text);
904+
const font = style.font;
905+
const contentWidth = measureWidth(ensureFontMeasureInfo(font), text);
906+
const contentHeight = getLineHeight(font);
907+
908+
return tSpanCreateBoundingRect2(
909+
style,
910+
contentWidth,
911+
contentHeight,
912+
null
913+
);
914+
}
915+
916+
export function tSpanCreateBoundingRect2(
917+
style: Pick<TSpanStyleProps, 'x' | 'y' | 'textAlign' | 'textBaseline' | 'lineWidth'>,
918+
contentWidth: number,
919+
contentHeight: number,
920+
forceLineWidth: number | NullUndefined,
921+
): BoundingRect {
922+
const rect = new BoundingRect(
923+
adjustTextX(style.x || 0, contentWidth, style.textAlign as TextAlign),
924+
adjustTextY(style.y || 0, contentHeight, style.textBaseline as TextVerticalAlign),
925+
/**
926+
* Text boundary should be the real text width.
927+
* Otherwise, there will be extra space in the
928+
* bounding rect calculated.
929+
*/
930+
contentWidth,
931+
contentHeight
932+
);
933+
934+
const lineWidth = forceLineWidth != null
935+
? forceLineWidth
936+
: (tSpanHasStroke(style) ? style.lineWidth : 0);
937+
if (lineWidth > 0) {
938+
rect.x -= lineWidth / 2;
939+
rect.y -= lineWidth / 2;
940+
rect.width += lineWidth;
941+
rect.height += lineWidth;
942+
}
943+
944+
return rect;
945+
}
946+
947+
export function tSpanHasStroke(style: TSpanStyleProps): boolean {
948+
const stroke = style.stroke;
949+
return stroke != null && stroke !== 'none' && style.lineWidth > 0;
950+
}
951+

0 commit comments

Comments
 (0)