Skip to content
Open
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
40 changes: 35 additions & 5 deletions src/graphic/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export interface TextStyleProps extends TextStylePropsPart {
* truncate: truncate the text and show ellipsis
* Do nothing if not set
*/
overflow?: 'break' | 'breakAll' | 'truncate' | 'none'
overflow?: 'break' | 'breakAll' | 'truncate' | 'fit' | 'none'

/**
* Strategy when text lines exceeds textHeight.
Expand Down Expand Up @@ -635,6 +635,23 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
subElStyle.font = textFont;
setSeparateFont(subElStyle, style);

// Apply horizontal scaling when overflow is 'fit'.
// The scaleX transform stretches or compresses text glyphs to exactly fill the target width.
if (contentBlock.fitScaleX != null && contentBlock.fitScaleX !== 1) {
el.scaleX = contentBlock.fitScaleX;
el.scaleY = 1;
// Adjust the x-origin so that the text scales from the correct anchor point
// based on textAlign. For 'left' alignment, origin is the text x position.
// For 'center', the origin should be the center; for 'right', the right edge.
el.originX = subElStyle.x;
el.originY = subElStyle.y;
}
else {
// Reset to avoid stale transforms from previous updates
el.scaleX = 1;
el.scaleY = 1;
}

textY += lineHeight;

// Always set tspan bounding rect to guarantee the consistency if users lays out based
Expand Down Expand Up @@ -728,7 +745,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
leftIndex < tokenCount
&& (token = tokens[leftIndex], !token.align || token.align === 'left')
) {
this._placeToken(token, style, lineHeight, lineTop, lineXLeft, 'left', bgColorDrawn);
this._placeToken(token, style, lineHeight, lineTop, lineXLeft, 'left', bgColorDrawn, contentBlock.fitScaleX);
remainedWidth -= token.width;
lineXLeft += token.width;
leftIndex++;
Expand All @@ -738,7 +755,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
rightIndex >= 0
&& (token = tokens[rightIndex], token.align === 'right')
) {
this._placeToken(token, style, lineHeight, lineTop, lineXRight, 'right', bgColorDrawn);
this._placeToken(token, style, lineHeight, lineTop, lineXRight, 'right', bgColorDrawn, contentBlock.fitScaleX);
remainedWidth -= token.width;
lineXRight -= token.width;
rightIndex--;
Expand All @@ -751,7 +768,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
// Consider width specified by user, use 'center' rather than 'left'.
this._placeToken(
token, style, lineHeight, lineTop,
lineXLeft + token.width / 2, 'center', bgColorDrawn
lineXLeft + token.width / 2, 'center', bgColorDrawn, contentBlock.fitScaleX
);
lineXLeft += token.width;
leftIndex++;
Expand All @@ -768,7 +785,8 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
lineTop: number,
x: number,
textAlign: string,
parentBgColorDrawn: boolean
parentBgColorDrawn: boolean,
fitScaleX?: number
) {
const tokenStyle = style.rich[token.styleName] || {};
tokenStyle.text = token.text;
Expand Down Expand Up @@ -865,6 +883,18 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
subElStyle.fill = textFill;
}

// Apply horizontal scaling when overflow is 'fit'.
if (fitScaleX != null && fitScaleX !== 1) {
el.scaleX = fitScaleX;
el.scaleY = 1;
el.originX = subElStyle.x;
el.originY = subElStyle.y;
}
else {
el.scaleX = 1;
el.scaleY = 1;
}

// NOTE: Should not call dirtyStyle after setBoundingRect. Or it will be cleared.
el.setBoundingRect(tSpanCreateBoundingRect2(
subElStyle,
Expand Down
23 changes: 21 additions & 2 deletions src/graphic/helper/parseText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ export interface PlainTextContentBlock {
// Be `true` if and only if the result text is modified due to overflow, due to
// settings on either `overflow` or `lineOverflow`
isTruncated: boolean

// Horizontal scale factor when overflow is 'fit'.
// Text glyphs will be horizontally scaled by this factor to exactly fill the target width.
fitScaleX?: number
}

export function parsePlainText(
Expand Down Expand Up @@ -302,6 +306,13 @@ export function parsePlainText(
outerHeight += paddingV;
outerWidth += paddingH;

// When overflow is 'fit', compute the horizontal scale factor to stretch/compress
// the text glyphs so they exactly fill the target width. No truncation or wrapping is applied.
let fitScaleX: number;
if (overflow === 'fit' && contentWidth > 0) {
fitScaleX = width / contentWidth;
}

return {
lines: lines,
height: height,
Expand All @@ -312,7 +323,8 @@ export function parsePlainText(
contentWidth: contentWidth,
contentHeight: contentHeight,
width: width,
isTruncated: isTruncated
isTruncated: isTruncated,
fitScaleX: fitScaleX
};
}

Expand Down Expand Up @@ -370,6 +382,8 @@ export class RichTextContentBlock {
// 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
// Horizontal scale factor when overflow is 'fit'.
fitScaleX?: number
}

type WrapInfo = {
Expand Down Expand Up @@ -576,6 +590,12 @@ export function parseRichText(
token.width = parseInt(percentWidth, 10) / 100 * contentBlock.width;
}

// When overflow is 'fit', compute the horizontal scale factor to stretch/compress
// the text glyphs so they exactly fill the target width.
if (overflow === 'fit' && topWidth != null && calculatedWidth > 0) {
contentBlock.fitScaleX = topWidth / calculatedWidth;
}

return contentBlock;
}

Expand Down Expand Up @@ -948,4 +968,3 @@ export function tSpanHasStroke(style: TSpanStyleProps): boolean {
const stroke = style.stroke;
return stroke != null && stroke !== 'none' && style.lineWidth > 0;
}

39 changes: 38 additions & 1 deletion test/text.html
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,43 @@
font: '12px Arial'
}
});




createText({
x: 100,
y: 1700,
style: {
x: 0,
y: 0,
width: 120,
height: 40,
text: 'overflow: fit - long text example',
overflow: 'fit',
backgroundColor: '#ddd',
fill: '#000',
font: '20px Arial'
}
});

createText({
x: 100,
y: 1780,
style: {
x: 0,
y: 0,
width: 0,
height: 40,
text: 'overflow: fit - zero width',
overflow: 'fit',
backgroundColor: '#fdd',
fill: '#000',
font: '20px Arial'
}
});


createText({
x: 100,
y: 1500,
Expand Down Expand Up @@ -653,4 +690,4 @@

</script>
</body>
</html>
</html>