Skip to content

Commit 613b5dc

Browse files
author
Utkarsh Patel
committed
fix(text): improve overflow fit behavior
1 parent b013719 commit 613b5dc

3 files changed

Lines changed: 94 additions & 8 deletions

File tree

src/graphic/Text.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export interface TextStyleProps extends TextStylePropsPart {
178178
* truncate: truncate the text and show ellipsis
179179
* Do nothing if not set
180180
*/
181-
overflow?: 'break' | 'breakAll' | 'truncate' | 'none'
181+
overflow?: 'break' | 'breakAll' | 'truncate' | 'fit' | 'none'
182182

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

638+
// Apply horizontal scaling when overflow is 'fit'.
639+
// The scaleX transform stretches or compresses text glyphs to exactly fill the target width.
640+
if (contentBlock.fitScaleX != null && contentBlock.fitScaleX !== 1) {
641+
el.scaleX = contentBlock.fitScaleX;
642+
el.scaleY = 1;
643+
// Adjust the x-origin so that the text scales from the correct anchor point
644+
// based on textAlign. For 'left' alignment, origin is the text x position.
645+
// For 'center', the origin should be the center; for 'right', the right edge.
646+
el.originX = subElStyle.x;
647+
el.originY = subElStyle.y;
648+
}
649+
else {
650+
// Reset to avoid stale transforms from previous updates
651+
el.scaleX = 1;
652+
el.scaleY = 1;
653+
}
654+
638655
textY += lineHeight;
639656

640657
// Always set tspan bounding rect to guarantee the consistency if users lays out based
@@ -728,7 +745,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
728745
leftIndex < tokenCount
729746
&& (token = tokens[leftIndex], !token.align || token.align === 'left')
730747
) {
731-
this._placeToken(token, style, lineHeight, lineTop, lineXLeft, 'left', bgColorDrawn);
748+
this._placeToken(token, style, lineHeight, lineTop, lineXLeft, 'left', bgColorDrawn, contentBlock.fitScaleX);
732749
remainedWidth -= token.width;
733750
lineXLeft += token.width;
734751
leftIndex++;
@@ -738,7 +755,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
738755
rightIndex >= 0
739756
&& (token = tokens[rightIndex], token.align === 'right')
740757
) {
741-
this._placeToken(token, style, lineHeight, lineTop, lineXRight, 'right', bgColorDrawn);
758+
this._placeToken(token, style, lineHeight, lineTop, lineXRight, 'right', bgColorDrawn, contentBlock.fitScaleX);
742759
remainedWidth -= token.width;
743760
lineXRight -= token.width;
744761
rightIndex--;
@@ -751,7 +768,7 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
751768
// Consider width specified by user, use 'center' rather than 'left'.
752769
this._placeToken(
753770
token, style, lineHeight, lineTop,
754-
lineXLeft + token.width / 2, 'center', bgColorDrawn
771+
lineXLeft + token.width / 2, 'center', bgColorDrawn, contentBlock.fitScaleX
755772
);
756773
lineXLeft += token.width;
757774
leftIndex++;
@@ -768,7 +785,8 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
768785
lineTop: number,
769786
x: number,
770787
textAlign: string,
771-
parentBgColorDrawn: boolean
788+
parentBgColorDrawn: boolean,
789+
fitScaleX?: number
772790
) {
773791
const tokenStyle = style.rich[token.styleName] || {};
774792
tokenStyle.text = token.text;
@@ -865,6 +883,18 @@ class ZRText extends Displayable<TextProps> implements GroupLike {
865883
subElStyle.fill = textFill;
866884
}
867885

886+
// Apply horizontal scaling when overflow is 'fit'.
887+
if (fitScaleX != null && fitScaleX !== 1) {
888+
el.scaleX = fitScaleX;
889+
el.scaleY = 1;
890+
el.originX = subElStyle.x;
891+
el.originY = subElStyle.y;
892+
}
893+
else {
894+
el.scaleX = 1;
895+
el.scaleY = 1;
896+
}
897+
868898
// NOTE: Should not call dirtyStyle after setBoundingRect. Or it will be cleared.
869899
el.setBoundingRect(tSpanCreateBoundingRect2(
870900
subElStyle,

src/graphic/helper/parseText.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ export interface PlainTextContentBlock {
208208
// Be `true` if and only if the result text is modified due to overflow, due to
209209
// settings on either `overflow` or `lineOverflow`
210210
isTruncated: boolean
211+
212+
// Horizontal scale factor when overflow is 'fit'.
213+
// Text glyphs will be horizontally scaled by this factor to exactly fill the target width.
214+
fitScaleX?: number
211215
}
212216

213217
export function parsePlainText(
@@ -302,6 +306,13 @@ export function parsePlainText(
302306
outerHeight += paddingV;
303307
outerWidth += paddingH;
304308

309+
// When overflow is 'fit', compute the horizontal scale factor to stretch/compress
310+
// the text glyphs so they exactly fill the target width. No truncation or wrapping is applied.
311+
let fitScaleX: number;
312+
if (overflow === 'fit' && contentWidth > 0) {
313+
fitScaleX = width / contentWidth;
314+
}
315+
305316
return {
306317
lines: lines,
307318
height: height,
@@ -312,7 +323,8 @@ export function parsePlainText(
312323
contentWidth: contentWidth,
313324
contentHeight: contentHeight,
314325
width: width,
315-
isTruncated: isTruncated
326+
isTruncated: isTruncated,
327+
fitScaleX: fitScaleX
316328
};
317329
}
318330

@@ -370,6 +382,8 @@ export class RichTextContentBlock {
370382
// Be `true` if and only if the result text is modified due to overflow, due to
371383
// settings on either `overflow` or `lineOverflow`
372384
isTruncated: boolean = false
385+
// Horizontal scale factor when overflow is 'fit'.
386+
fitScaleX?: number
373387
}
374388

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

593+
// When overflow is 'fit', compute the horizontal scale factor to stretch/compress
594+
// the text glyphs so they exactly fill the target width.
595+
if (overflow === 'fit' && topWidth != null && calculatedWidth > 0) {
596+
contentBlock.fitScaleX = topWidth / calculatedWidth;
597+
}
598+
579599
return contentBlock;
580600
}
581601

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

test/text.html

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,43 @@
584584
font: '12px Arial'
585585
}
586586
});
587+
588+
589+
590+
591+
createText({
592+
x: 100,
593+
y: 1700,
594+
style: {
595+
x: 0,
596+
y: 0,
597+
width: 120,
598+
height: 40,
599+
text: 'overflow: fit - long text example',
600+
overflow: 'fit',
601+
backgroundColor: '#ddd',
602+
fill: '#000',
603+
font: '20px Arial'
604+
}
605+
});
606+
607+
createText({
608+
x: 100,
609+
y: 1780,
610+
style: {
611+
x: 0,
612+
y: 0,
613+
width: 0,
614+
height: 40,
615+
text: 'overflow: fit - zero width',
616+
overflow: 'fit',
617+
backgroundColor: '#fdd',
618+
fill: '#000',
619+
font: '20px Arial'
620+
}
621+
});
622+
623+
587624
createText({
588625
x: 100,
589626
y: 1500,
@@ -653,4 +690,4 @@
653690

654691
</script>
655692
</body>
656-
</html>
693+
</html>

0 commit comments

Comments
 (0)