Skip to content
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
5 changes: 4 additions & 1 deletion src/contain/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function measureWidth(fontMeasureInfo: FontMeasureInfo, text: string): nu


/**
*
* @deprecated See `getBoundingRect`.
* Get bounding rect for inner usage(TSpan)
* Which not include text newline.
*/
Expand All @@ -128,6 +128,9 @@ export function innerGetBoundingRect(
}

/**
* @deprecated Use `(new Text(...)).getBoundingRect()` or `(new TSpan(...)).getBoundingRect()` instead.
* This method behaves differently from `Text#getBoundingRect()` - e.g., it does not support the overflow
* strategy, and only has single line height even if multiple lines.
*
* Get bounding rect for outer usage. Compatitable with old implementation
* Which includes text newline.
Expand Down
33 changes: 4 additions & 29 deletions src/graphic/TSpan.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Displayable, { DisplayableProps, DisplayableStatePropNames } from './Displayable';
import { getBoundingRect } from '../contain/text';
import BoundingRect from '../core/BoundingRect';
import { PathStyleProps, DEFAULT_PATH_STYLE } from './Path';
import { createObject, defaults } from '../core/util';
import { FontStyle, FontWeight, TextAlign, TextVerticalAlign } from '../core/types';
import { FontStyle, FontWeight } from '../core/types';
import { DEFAULT_FONT } from '../core/platform';
import { tSpanCreateBoundingRect, tSpanHasStroke } from './helper/parseText';

export interface TSpanStyleProps extends PathStyleProps {

Expand Down Expand Up @@ -53,9 +53,7 @@ class TSpan extends Displayable<TSpanProps> {
style: TSpanStyleProps

hasStroke() {
const style = this.style;
const stroke = style.stroke;
return stroke != null && stroke !== 'none' && style.lineWidth > 0;
return tSpanHasStroke(this.style);
}

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

getBoundingRect(): BoundingRect {
const style = this.style;

if (!this._rect) {
let text = style.text;
text != null ? (text += '') : (text = '');

const rect = getBoundingRect(
text,
style.font,
style.textAlign as TextAlign,
style.textBaseline as TextVerticalAlign
);

rect.x += style.x || 0;
rect.y += style.y || 0;

if (this.hasStroke()) {
const w = style.lineWidth;
rect.x -= w / 2;
rect.y -= w / 2;
rect.width += w;
rect.height += w;
}

this._rect = rect;
this._rect = tSpanCreateBoundingRect(this.style);
}

return this._rect;
Expand Down
72 changes: 42 additions & 30 deletions src/graphic/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
TextAlign, TextVerticalAlign, ImageLike, Dictionary, MapToType, FontWeight, FontStyle, NullUndefined
} from '../core/types';
import {
parseRichText, parsePlainText, CalcInnerTextOverflowAreaOut, calcInnerTextOverflowArea
parseRichText, parsePlainText, CalcInnerTextOverflowAreaOut, calcInnerTextOverflowArea,
tSpanCreateBoundingRect2,
} from './helper/parseText';
import TSpan, { TSpanStyleProps } from './TSpan';
import { retrieve2, each, normalizeCssArray, trim, retrieve3, extend, keys, defaults } from '../core/util';
Expand Down Expand Up @@ -332,7 +333,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
}
}

updateTransform() {
updateTransform() {
const innerTransformable = this.innerTransformable;
if (innerTransformable) {
innerTransformable.updateTransform();
Expand Down Expand Up @@ -528,7 +529,6 @@ class ZRText extends Displayable<TextProps> implements GroupLike {

const outerHeight = contentBlock.outerHeight;
const outerWidth = contentBlock.outerWidth;
const contentWidth = contentBlock.contentWidth;

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

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

let defaultLineWidth = 0;
let usingDefaultStroke = false;
let useDefaultFill = false;
const textFill = getFill(
'fill' in style
Expand All @@ -580,16 +588,12 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
// we give the auto lineWidth to display the given stoke color.
&& (!defaultStyle.autoStroke || useDefaultFill)
)
? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke)
? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, usingDefaultStroke = true, defaultStyle.stroke)
: null
);

const hasShadow = style.textShadowBlur > 0;

const fixedBoundingRect = style.width != null
&& (style.overflow === 'truncate' || style.overflow === 'break' || style.overflow === 'breakAll');
const calculatedLineHeight = contentBlock.calculatedLineHeight;

for (let i = 0; i < textLines.length; i++) {
const el = this._getOrCreateChild(TSpan);
// Always create new style.
Expand Down Expand Up @@ -633,19 +637,27 @@ class ZRText extends Displayable<TextProps> implements GroupLike {

textY += lineHeight;

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

Expand Down Expand Up @@ -801,6 +813,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
const defaultStyle = this._defaultStyle;
let useDefaultFill = false;
let defaultLineWidth = 0;
let usingDefaultStroke = false;
const textFill = getFill(
'fill' in tokenStyle ? tokenStyle.fill
: 'fill' in style ? style.fill
Expand All @@ -812,9 +825,9 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
: (
!bgColorDrawn
&& !parentBgColorDrawn
// See the strategy explained above.
// See the strategy explained `_updatePlainTexts`.
&& (!defaultStyle.autoStroke || useDefaultFill)
) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke)
) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, usingDefaultStroke = true, defaultStyle.stroke)
: null
);

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

const textWidth = token.contentWidth;
const textHeight = token.contentHeight;
// NOTE: Should not call dirtyStyle after setBoundingRect. Or it will be cleared.
el.setBoundingRect(new BoundingRect(
adjustTextX(subElStyle.x, textWidth, subElStyle.textAlign as TextAlign),
adjustTextY(subElStyle.y, textHeight, subElStyle.textBaseline as TextVerticalAlign),
textWidth,
textHeight
el.setBoundingRect(tSpanCreateBoundingRect2(
subElStyle,
token.contentWidth,
token.contentHeight,
// See the strategy explained `_updatePlainTexts`.
usingDefaultStroke ? 0 : null
));
}

Expand Down
69 changes: 64 additions & 5 deletions src/graphic/helper/parseText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
reduce,
} from '../../core/util';
import { TextAlign, TextVerticalAlign, ImageLike, Dictionary, NullUndefined } from '../../core/types';
import { DefaultTextStyle, TextStyleProps } from '../Text';
import type { DefaultTextStyle, TextStyleProps } from '../Text';
import type { TSpanStyleProps } from '../TSpan';
import {
adjustTextX,
adjustTextY,
Expand Down Expand Up @@ -210,12 +211,12 @@ export interface PlainTextContentBlock {
}

export function parsePlainText(
text: string,
rawText: unknown,
style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
defaultOuterWidth: number | NullUndefined,
defaultOuterHeight: number | NullUndefined
): PlainTextContentBlock {
text != null && (text += '');
const text = formatText(rawText);

// textPadding has been normalized
const overflow = style.overflow;
Expand Down Expand Up @@ -382,15 +383,15 @@ type WrapInfo = {
* If styleName is undefined, it is plain text.
*/
export function parseRichText(
text: string,
rawText: unknown,
style: Omit<TextStyleProps, 'align' | 'verticalAlign'>, // Exclude props in DefaultTextStyle
defaultOuterWidth: number | NullUndefined,
defaultOuterHeight: number | NullUndefined,
topTextAlign: TextAlign
): RichTextContentBlock {
const contentBlock = new RichTextContentBlock();

text != null && (text += '');
const text = formatText(rawText);
if (!text) {
return contentBlock;
}
Expand Down Expand Up @@ -890,3 +891,61 @@ export type CalcInnerTextOverflowAreaOut = {
outerHeight: number | NullUndefined
};

// For backward compatibility, and possibly loose type.
function formatText(text: unknown): string {
return text != null ? (text += '') : (text = '');
}

export function tSpanCreateBoundingRect(
style: Pick<TSpanStyleProps, 'text' | 'font' | 'x' | 'y' | 'textAlign' | 'textBaseline' | 'lineWidth'>,
): BoundingRect {
// Should follow the same way as `parsePlainText` to guarantee the consistency of the result.
const text = formatText(style.text);
const font = style.font;
const contentWidth = measureWidth(ensureFontMeasureInfo(font), text);
const contentHeight = getLineHeight(font);

return tSpanCreateBoundingRect2(
style,
contentWidth,
contentHeight,
null
);
}

export function tSpanCreateBoundingRect2(
style: Pick<TSpanStyleProps, 'x' | 'y' | 'textAlign' | 'textBaseline' | 'lineWidth'>,
contentWidth: number,
contentHeight: number,
forceLineWidth: number | NullUndefined,
): BoundingRect {
const rect = new BoundingRect(
adjustTextX(style.x || 0, contentWidth, style.textAlign as TextAlign),
adjustTextY(style.y || 0, contentHeight, style.textBaseline as TextVerticalAlign),
/**
* Text boundary should be the real text width.
* Otherwise, there will be extra space in the
* bounding rect calculated.
*/
contentWidth,
contentHeight
);

const lineWidth = forceLineWidth != null
? forceLineWidth
: (tSpanHasStroke(style) ? style.lineWidth : 0);
if (lineWidth > 0) {
rect.x -= lineWidth / 2;
rect.y -= lineWidth / 2;
rect.width += lineWidth;
rect.height += lineWidth;
}

return rect;
}

export function tSpanHasStroke(style: TSpanStyleProps): boolean {
const stroke = style.stroke;
return stroke != null && stroke !== 'none' && style.lineWidth > 0;
}

Loading