Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
fd28045
Merge pull request #899 from ecomfe/master
pissang Mar 16, 2022
2e49c4d
Merge pull request #953 from ecomfe/master
plainheart Sep 29, 2022
cb142ea
Merge pull request #964 from ecomfe/master
plainheart Oct 19, 2022
bf67f53
Merge pull request #979 from ecomfe/master
Ovilia Nov 28, 2022
845cfb5
Merge pull request #1001 from ecomfe/master
pissang Mar 26, 2023
c351ab0
Merge pull request #1063 from ecomfe/master
Ovilia Feb 1, 2024
d43c843
Merge pull request #1084 from ecomfe/master
plainheart Jul 1, 2024
3af925f
Merge pull request #1088 from ecomfe/master
Ovilia Jul 16, 2024
786638b
Merge pull request #1090 from ecomfe/master
plainheart Jul 17, 2024
c1f5e8e
Merge branch 'master' into v6
Ovilia Nov 12, 2024
615fcf6
Merge branch 'master' into v6
Ovilia Jan 20, 2025
be48e12
feat(OBB): (1) Fix OBB calculation incorrect in some angles when over…
100pah Mar 7, 2025
c2682f7
(feat) OBB support touchThreshold.
100pah Mar 12, 2025
f4a26c5
(feat): OBB support non-bidirectional.
100pah Mar 13, 2025
36b2c17
(ts) Fix parameter optional.
100pah Mar 13, 2025
ea31d0e
(test) Forgot to change the dat link back.
100pah Mar 14, 2025
ccb0ba0
Merge pull request #1115 from ecomfe/feat/obb-direction
Ovilia Mar 31, 2025
5053c91
(ts): Support text margin array.
100pah Apr 5, 2025
efb13f2
bug(obb): Fix OBB in extreme value and add test cases.
100pah Apr 22, 2025
ccc6e88
Merge branch 'v6' into feat/obb-direction
100pah Apr 22, 2025
c057c9a
test: Enhance test utilities.
100pah Apr 28, 2025
1f0758e
test: Clarify transform test case.
100pah Apr 28, 2025
456272b
chore: Clarify comments.
100pah Apr 28, 2025
0508fdc
fix&feat(BoundingRect, OBB): (1) Support the same features from OBB (…
100pah Apr 28, 2025
bb47510
feat(color): support function for modifyHSL
Ovilia May 12, 2025
bab415b
fix(color): fix implementation of modifyHSL
Ovilia May 12, 2025
f7a821b
Merge pull request #1118 from ecomfe/feat-color
100pah May 12, 2025
31a654f
Merge pull request #1121 from ecomfe/master
100pah May 31, 2025
e9a389f
feat: Support `ignoreHostSilent` for some scenarios that textContent …
100pah Jun 2, 2025
55ba4b2
fix(overflow): lineOverflow need to update contentHeight.
100pah Jun 2, 2025
725a87c
fix(wrap): Fix that some branches cause null pointer error.
100pah Jun 5, 2025
54d354e
Merge branch 'fix/wrap-causes-npe' into feat/ignore-host-silent
100pah Jun 5, 2025
f86f96b
chore(text): (1) clarify plain text parsing: if text.style.width spec…
100pah Jun 5, 2025
3c54b35
(test): enrich test case.
100pah Jun 5, 2025
fabe0ca
perf(trancate): (1) Make text overflow truncate more precise (conside…
100pah Jun 6, 2025
5d80fdd
feat(boundingRect): Support that touchThreshold cause negative size t…
100pah Jun 7, 2025
8eeaa03
feat(boundingRect): (1) Support get intersection rect. (2) Add some s…
100pah Jun 7, 2025
ca2381c
Merge remote-tracking branch 'origin/feat/obb-direction' into feat/ig…
100pah Jun 7, 2025
58c9f72
chore(ts): Add checktype command.
100pah Jun 7, 2025
3ce7eef
test: fix dat.gui reference error introduced by the previous commit, …
100pah Jun 7, 2025
0358e3f
Merge remote-tracking branch 'origin/feat/obb-direction' into feat/ig…
100pah Jun 7, 2025
08b0fc9
feat(boundingRect): support clamp in intersect rect.
100pah Jun 8, 2025
6219dbb
perf(clipPath): Handy optimize clipPath.
100pah Jun 8, 2025
82f8bd1
feat(rectText): Support `textConfig.autoOverflowArea`, which auto cal…
100pah Jun 8, 2025
5721d2c
chore: remove unnecessary import.
100pah Jun 9, 2025
4ebf34d
Merge pull request #1122 from ecomfe/feat/ignore-host-silent
100pah Jun 9, 2025
31d263d
tweak(BoundingRect): Add a static method `contain`.
100pah Jun 14, 2025
1368d71
feat: may be an approach to fix #1105 (available custom path in compo…
100pah Jun 16, 2025
120ba2b
fix cr suggestion
100pah Jun 17, 2025
3a61cd5
Merge pull request #1125 from ecomfe/fix-path-2
100pah Jun 17, 2025
490a7b3
Merge pull request #1126 from ecomfe/v6
Ovilia Jun 21, 2025
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"watch:bundle": "node build/build.js --watch",
"watch:lib": "npx tsc-watch -m ES2015 --outDir lib --synchronousWatchDirectory --onSuccess \"node build/processLib.js\"",
"test": "npx jest --config test/ut/jest.config.js",
"lint": "npx eslint src/**/*.ts"
"lint": "npx eslint src/**/*.ts",
"checktype": "tsc --noEmit"
},
"license": "BSD-3-Clause",
"types": "index.d.ts",
Expand Down
85 changes: 69 additions & 16 deletions src/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import Point from './core/Point';
import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config';
import { parse, stringify } from './tool/color';
import { REDRAW_BIT } from './graphic/constants';
import { invert } from './core/matrix';

export interface ElementAnimateConfig {
duration?: number
Expand Down Expand Up @@ -81,7 +82,11 @@ export interface ElementTextConfig {

/**
* Rect that text will be positioned.
* Default to be the rect of element.
* Default to be the boundingRect of the host element.
* The coords of `layoutRect` is based on the target element, but not global.
*
* [NOTICE]: boundingRect includes `lineWidth`, which is inconsistent with
* the general element placement principle, where `lineWidth` is not counted.
*/
layoutRect?: RectLike

Expand Down Expand Up @@ -109,6 +114,10 @@ export interface ElementTextConfig {

/**
* If use local user space. Which will apply host's transform
*
* [NOTICE]: If the host element may rotate to non-parallel to screen x/y,
* need to use `local:true`, otherwise the transformed layout rect may not be expected.
*
* @default false
*/
local?: boolean
Expand Down Expand Up @@ -166,6 +175,16 @@ export interface ElementTextConfig {
* In case position is not using builtin `inside` hints.
*/
inside?: boolean

/**
* Auto calculate overflow area by `textConfig.layoutRect` (if any) or `host.boundingRect`.
* It makes sense only if label is inside. It ensure the text does not overflow the host.
* Useful in `text.style.overflow` and `text.style.lineOverflow`.
*
* If `textConfig.rotation` or `text.rotation exists`, it works correctly only when the rotated text is parallel
* to its host (i.e. 0, PI/2, PI, PI*3/2, 2*PI, ...). Do not supported other cases until a real scenario arises.
*/
autoOverflowArea?: boolean
}
export interface ElementTextGuideLineConfig {
/**
Expand Down Expand Up @@ -238,6 +257,7 @@ export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial
draggable?: boolean | 'horizontal' | 'vertical'

silent?: boolean
ignoreHostSilent?: boolean

ignoreClip?: boolean
globalScaleRatio?: number
Expand Down Expand Up @@ -277,8 +297,9 @@ export type ElementCalculateTextPosition = (
rect: RectLike
) => TextPositionCalculationResult;

let tmpTextPosCalcRes = {} as TextPositionCalculationResult;
let tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
const tmpTextPosCalcRes = {} as TextPositionCalculationResult;
const tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
const tmpInnerTextTrans: number[] = [];

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Element<Props extends ElementProps = ElementProps> extends Transformable,
Expand Down Expand Up @@ -313,6 +334,14 @@ class Element<Props extends ElementProps = ElementProps> {
*/
silent: boolean

/**
* When this element has `__hostTarget` (e.g., this is a `textContent`), whether
* its silent is controlled by that host silent. They may need separate silent
* settings. e.g., the host do not have `fill` but only `stroke`, or their mouse
* events serve for different features.
*/
ignoreHostSilent: boolean

/**
* 是否是 Group
*/
Expand Down Expand Up @@ -368,6 +397,8 @@ class Element<Props extends ElementProps = ElementProps> {
*/
__inHover: boolean

__clipPaths?: Path[]

/**
* path to clip the elements and its children, if it is a group.
* @see http://www.w3.org/TR/2dcontext/#clipping-region
Expand Down Expand Up @@ -511,9 +542,12 @@ class Element<Props extends ElementProps = ElementProps> {
// Reset x/y/rotation
innerTransformable.copyTransform(textEl);

// Force set attached text's position if `position` is in config.
if (textConfig.position != null) {
let layoutRect = tmpBoundingRect;
const hasPosition = textConfig.position != null;
const autoOverflowArea = textConfig.autoOverflowArea;

let layoutRect: BoundingRect;
if (autoOverflowArea || hasPosition) {
layoutRect = tmpBoundingRect;
if (textConfig.layoutRect) {
layoutRect.copy(textConfig.layoutRect);
}
Expand All @@ -523,7 +557,10 @@ class Element<Props extends ElementProps = ElementProps> {
if (!isLocal) {
layoutRect.applyTransform(this.transform);
}
}

// Force set attached text's position if `position` is in config.
if (hasPosition) {
if (this.calculateTextPosition) {
this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
}
Expand Down Expand Up @@ -578,11 +615,27 @@ class Element<Props extends ElementProps = ElementProps> {
}
}

const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {});

if (autoOverflowArea) {
const overflowRect = innerTextDefaultStyle.overflowRect =
innerTextDefaultStyle.overflowRect || new BoundingRect(0, 0, 0, 0);
innerTransformable.getLocalTransform(tmpInnerTextTrans);
invert(tmpInnerTextTrans, tmpInnerTextTrans);
BoundingRect.copy(overflowRect, layoutRect);
// If transform to a non-orthogonal state (e.g. rotate PI/3), the result of this "apply"
// is not expected. But we don't need to address it until a real scenario arises.
overflowRect.applyTransform(tmpInnerTextTrans);
}
else {
innerTextDefaultStyle.overflowRect = null;
}
// [CAUTION] Do not change `innerTransformable` below.

// Calculate text color
const isInside = textConfig.inside == null // Force to be inside or not.
? (typeof textConfig.position === 'string' && textConfig.position.indexOf('inside') >= 0)
: textConfig.inside;
const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {});

let textFill;
let textStroke;
Expand Down Expand Up @@ -1021,16 +1074,16 @@ class Element<Props extends ElementProps = ElementProps> {
* Return if el.silent or any ancestor element has silent true.
*/
isSilent() {
let isSilent = this.silent;
let ancestor = this.parent;
while (!isSilent && ancestor) {
if (ancestor.silent) {
isSilent = true;
break;
// Follow the logic of `Handler.ts`#`isHover`.
let el: Element = this;
while (el) {
if (el.silent) {
return true;
}
ancestor = ancestor.parent;
const hostEl = el.__hostTarget;
el = hostEl ? (el.ignoreHostSilent ? null : hostEl) : el.parent;
}
return isSilent;
return false;
}

/**
Expand Down Expand Up @@ -1637,6 +1690,7 @@ class Element<Props extends ElementProps = ElementProps> {

elProto.ignore =
elProto.silent =
elProto.ignoreHostSilent =
elProto.isGroup =
elProto.draggable =
elProto.dragging =
Expand Down Expand Up @@ -2026,5 +2080,4 @@ function animateToShallow<T>(
}
}


export default Element;
2 changes: 1 addition & 1 deletion src/Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ function isHover(displayable: Displayable, x: number, y: number) {
// Consider when el is textContent, also need to be silent
// if any of its host el and its ancestors is silent.
const hostEl = el.__hostTarget;
el = hostEl ? hostEl : el.parent;
el = hostEl ? (el.ignoreHostSilent ? null : hostEl) : el.parent;
}
return isSilent ? SILENT : true;
}
Expand Down
49 changes: 25 additions & 24 deletions src/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import timsort from './core/timsort';
import Displayable from './graphic/Displayable';
import Path from './graphic/Path';
import { REDRAW_BIT } from './graphic/constants';
import { NullUndefined } from './core/types';

let invalidZErrorLogged = false;
function logInvalidZError() {
Expand Down Expand Up @@ -83,7 +84,7 @@ export default class Storage {

private _updateAndAddDisplayable(
el: Element,
clipPaths: Path[],
parentClipPaths: Path[] | NullUndefined,
includeIgnore?: boolean
) {
if (el.ignore && !includeIgnore) {
Expand All @@ -95,18 +96,21 @@ export default class Storage {
el.afterUpdate();

const userSetClipPath = el.getClipPath();

if (el.ignoreClip) {
clipPaths = null;
}
else if (userSetClipPath) {

// FIXME 效率影响
if (clipPaths) {
clipPaths = clipPaths.slice();
const parentHasClipPaths = parentClipPaths && parentClipPaths.length;
let clipPathIdx = 0;
let thisClipPaths = el.__clipPaths;

if (!el.ignoreClip
&& (parentHasClipPaths || userSetClipPath)
) { // has clipPath in this pass
if (!thisClipPaths) {
thisClipPaths = el.__clipPaths = [];
}
else {
clipPaths = [];
if (parentHasClipPaths) {
// PENDING: performance?
for (let idx = 0; idx < parentClipPaths.length; idx++) {
thisClipPaths[clipPathIdx++] = parentClipPaths[idx];
}
}

let currentClipPath = userSetClipPath;
Expand All @@ -118,13 +122,17 @@ export default class Storage {
currentClipPath.parent = parentClipPath as Group;
currentClipPath.updateTransform();

clipPaths.push(currentClipPath);
thisClipPaths[clipPathIdx++] = currentClipPath;

parentClipPath = currentClipPath;
currentClipPath = currentClipPath.getClipPath();
}
}

if (thisClipPaths) { // Remove other old clipPath in array.
thisClipPaths.length = clipPathIdx;
}

// ZRText and Group and combining morphing Path may use children
if ((el as GroupLike).childrenRef) {
const children = (el as GroupLike).childrenRef();
Expand All @@ -137,7 +145,7 @@ export default class Storage {
child.__dirty |= REDRAW_BIT;
}

this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
this._updateAndAddDisplayable(child, thisClipPaths, includeIgnore);
}

// Mark group clean here
Expand All @@ -146,13 +154,6 @@ export default class Storage {
}
else {
const disp = el as Displayable;
// Element is displayable
if (clipPaths && clipPaths.length) {
disp.__clipPaths = clipPaths;
}
else if (disp.__clipPaths && disp.__clipPaths.length > 0) {
disp.__clipPaths = [];
}

// Avoid invalid z, z2, zlevel cause sorting error.
if (isNaN(disp.z)) {
Expand All @@ -174,18 +175,18 @@ export default class Storage {
// Add decal
const decalEl = (el as Path).getDecalElement && (el as Path).getDecalElement();
if (decalEl) {
this._updateAndAddDisplayable(decalEl, clipPaths, includeIgnore);
this._updateAndAddDisplayable(decalEl, thisClipPaths, includeIgnore);
}

// Add attached text element and guide line.
const textGuide = el.getTextGuideLine();
if (textGuide) {
this._updateAndAddDisplayable(textGuide, clipPaths, includeIgnore);
this._updateAndAddDisplayable(textGuide, thisClipPaths, includeIgnore);
}

const textEl = el.getTextContent();
if (textEl) {
this._updateAndAddDisplayable(textEl, clipPaths, includeIgnore);
this._updateAndAddDisplayable(textEl, thisClipPaths, includeIgnore);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/canvas/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export function getCanvasGradient(this: void, ctx: CanvasRenderingContext2D, obj
return canvasGradient;
}

// [CAVEAT] Assume the clipPaths array is never modified during a batch of `isClipPathChanged` calling.
export function isClipPathChanged(clipPaths: Path[], prevClipPaths: Path[]): boolean {
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
return false;
}
Expand Down
Loading