@@ -31,6 +31,7 @@ import Point from './core/Point';
3131import { LIGHT_LABEL_COLOR , DARK_LABEL_COLOR } from './config' ;
3232import { parse , stringify } from './tool/color' ;
3333import { REDRAW_BIT } from './graphic/constants' ;
34+ import { invert } from './core/matrix' ;
3435
3536export interface ElementAnimateConfig {
3637 duration ?: number
@@ -81,7 +82,11 @@ export interface ElementTextConfig {
8182
8283 /**
8384 * Rect that text will be positioned.
84- * Default to be the rect of element.
85+ * Default to be the boundingRect of the host element.
86+ * The coords of `layoutRect` is based on the target element, but not global.
87+ *
88+ * [NOTICE]: boundingRect includes `lineWidth`, which is inconsistent with
89+ * the general element placement principle, where `lineWidth` is not counted.
8590 */
8691 layoutRect ?: RectLike
8792
@@ -109,6 +114,10 @@ export interface ElementTextConfig {
109114
110115 /**
111116 * If use local user space. Which will apply host's transform
117+ *
118+ * [NOTICE]: If the host element may rotate to non-parallel to screen x/y,
119+ * need to use `local:true`, otherwise the transformed layout rect may not be expected.
120+ *
112121 * @default false
113122 */
114123 local ?: boolean
@@ -166,6 +175,16 @@ export interface ElementTextConfig {
166175 * In case position is not using builtin `inside` hints.
167176 */
168177 inside ?: boolean
178+
179+ /**
180+ * Auto calculate overflow area by `textConfig.layoutRect` (if any) or `host.boundingRect`.
181+ * It makes sense only if label is inside. It ensure the text does not overflow the host.
182+ * Useful in `text.style.overflow` and `text.style.lineOverflow`.
183+ *
184+ * If `textConfig.rotation` or `text.rotation exists`, it works correctly only when the rotated text is parallel
185+ * to its host (i.e. 0, PI/2, PI, PI*3/2, 2*PI, ...). Do not supported other cases until a real scenario arises.
186+ */
187+ autoOverflowArea ?: boolean
169188}
170189export interface ElementTextGuideLineConfig {
171190 /**
@@ -238,6 +257,7 @@ export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial
238257 draggable ?: boolean | 'horizontal' | 'vertical'
239258
240259 silent ?: boolean
260+ ignoreHostSilent ?: boolean
241261
242262 ignoreClip ?: boolean
243263 globalScaleRatio ?: number
@@ -277,8 +297,9 @@ export type ElementCalculateTextPosition = (
277297 rect : RectLike
278298) => TextPositionCalculationResult ;
279299
280- let tmpTextPosCalcRes = { } as TextPositionCalculationResult ;
281- let tmpBoundingRect = new BoundingRect ( 0 , 0 , 0 , 0 ) ;
300+ const tmpTextPosCalcRes = { } as TextPositionCalculationResult ;
301+ const tmpBoundingRect = new BoundingRect ( 0 , 0 , 0 , 0 ) ;
302+ const tmpInnerTextTrans : number [ ] = [ ] ;
282303
283304// eslint-disable-next-line @typescript-eslint/no-unused-vars
284305interface Element < Props extends ElementProps = ElementProps > extends Transformable ,
@@ -313,6 +334,14 @@ class Element<Props extends ElementProps = ElementProps> {
313334 */
314335 silent : boolean
315336
337+ /**
338+ * When this element has `__hostTarget` (e.g., this is a `textContent`), whether
339+ * its silent is controlled by that host silent. They may need separate silent
340+ * settings. e.g., the host do not have `fill` but only `stroke`, or their mouse
341+ * events serve for different features.
342+ */
343+ ignoreHostSilent : boolean
344+
316345 /**
317346 * 是否是 Group
318347 */
@@ -368,6 +397,8 @@ class Element<Props extends ElementProps = ElementProps> {
368397 */
369398 __inHover : boolean
370399
400+ __clipPaths ?: Path [ ]
401+
371402 /**
372403 * path to clip the elements and its children, if it is a group.
373404 * @see http://www.w3.org/TR/2dcontext/#clipping-region
@@ -511,9 +542,12 @@ class Element<Props extends ElementProps = ElementProps> {
511542 // Reset x/y/rotation
512543 innerTransformable . copyTransform ( textEl ) ;
513544
514- // Force set attached text's position if `position` is in config.
515- if ( textConfig . position != null ) {
516- let layoutRect = tmpBoundingRect ;
545+ const hasPosition = textConfig . position != null ;
546+ const autoOverflowArea = textConfig . autoOverflowArea ;
547+
548+ let layoutRect : BoundingRect ;
549+ if ( autoOverflowArea || hasPosition ) {
550+ layoutRect = tmpBoundingRect ;
517551 if ( textConfig . layoutRect ) {
518552 layoutRect . copy ( textConfig . layoutRect ) ;
519553 }
@@ -523,7 +557,10 @@ class Element<Props extends ElementProps = ElementProps> {
523557 if ( ! isLocal ) {
524558 layoutRect . applyTransform ( this . transform ) ;
525559 }
560+ }
526561
562+ // Force set attached text's position if `position` is in config.
563+ if ( hasPosition ) {
527564 if ( this . calculateTextPosition ) {
528565 this . calculateTextPosition ( tmpTextPosCalcRes , textConfig , layoutRect ) ;
529566 }
@@ -578,11 +615,27 @@ class Element<Props extends ElementProps = ElementProps> {
578615 }
579616 }
580617
618+ const innerTextDefaultStyle = this . _innerTextDefaultStyle || ( this . _innerTextDefaultStyle = { } ) ;
619+
620+ if ( autoOverflowArea ) {
621+ const overflowRect = innerTextDefaultStyle . overflowRect =
622+ innerTextDefaultStyle . overflowRect || new BoundingRect ( 0 , 0 , 0 , 0 ) ;
623+ innerTransformable . getLocalTransform ( tmpInnerTextTrans ) ;
624+ invert ( tmpInnerTextTrans , tmpInnerTextTrans ) ;
625+ BoundingRect . copy ( overflowRect , layoutRect ) ;
626+ // If transform to a non-orthogonal state (e.g. rotate PI/3), the result of this "apply"
627+ // is not expected. But we don't need to address it until a real scenario arises.
628+ overflowRect . applyTransform ( tmpInnerTextTrans ) ;
629+ }
630+ else {
631+ innerTextDefaultStyle . overflowRect = null ;
632+ }
633+ // [CAUTION] Do not change `innerTransformable` below.
634+
581635 // Calculate text color
582636 const isInside = textConfig . inside == null // Force to be inside or not.
583637 ? ( typeof textConfig . position === 'string' && textConfig . position . indexOf ( 'inside' ) >= 0 )
584638 : textConfig . inside ;
585- const innerTextDefaultStyle = this . _innerTextDefaultStyle || ( this . _innerTextDefaultStyle = { } ) ;
586639
587640 let textFill ;
588641 let textStroke ;
@@ -1021,16 +1074,16 @@ class Element<Props extends ElementProps = ElementProps> {
10211074 * Return if el.silent or any ancestor element has silent true.
10221075 */
10231076 isSilent ( ) {
1024- let isSilent = this . silent ;
1025- let ancestor = this . parent ;
1026- while ( ! isSilent && ancestor ) {
1027- if ( ancestor . silent ) {
1028- isSilent = true ;
1029- break ;
1077+ // Follow the logic of `Handler.ts`#`isHover`.
1078+ let el : Element = this ;
1079+ while ( el ) {
1080+ if ( el . silent ) {
1081+ return true ;
10301082 }
1031- ancestor = ancestor . parent ;
1083+ const hostEl = el . __hostTarget ;
1084+ el = hostEl ? ( el . ignoreHostSilent ? null : hostEl ) : el . parent ;
10321085 }
1033- return isSilent ;
1086+ return false ;
10341087 }
10351088
10361089 /**
@@ -1637,6 +1690,7 @@ class Element<Props extends ElementProps = ElementProps> {
16371690
16381691 elProto . ignore =
16391692 elProto . silent =
1693+ elProto . ignoreHostSilent =
16401694 elProto . isGroup =
16411695 elProto . draggable =
16421696 elProto . dragging =
@@ -2026,5 +2080,4 @@ function animateToShallow<T>(
20262080 }
20272081}
20282082
2029-
20302083export default Element ;
0 commit comments