zrender-nightly 5.7.0-dev.20250620 → 5.7.0-dev.20250622
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/prepublish.js +20 -0
- package/dist/zrender.js +563 -277
- package/dist/zrender.js.map +1 -1
- package/dist/zrender.min.js +1 -1
- package/lib/Element.d.ts +4 -0
- package/lib/Element.js +34 -16
- package/lib/Handler.js +1 -1
- package/lib/Storage.js +20 -20
- package/lib/canvas/graphic.js +1 -1
- package/lib/contain/text.d.ts +14 -2
- package/lib/contain/text.js +65 -15
- package/lib/core/BoundingRect.d.ts +25 -3
- package/lib/core/BoundingRect.js +182 -76
- package/lib/core/OrientedBoundingRect.d.ts +2 -2
- package/lib/core/OrientedBoundingRect.js +50 -34
- package/lib/core/PathProxy.d.ts +1 -0
- package/lib/core/PathProxy.js +16 -1
- package/lib/core/dom.d.ts +1 -0
- package/lib/core/dom.js +17 -0
- package/lib/core/env.js +15 -10
- package/lib/core/types.d.ts +1 -0
- package/lib/core/util.d.ts +1 -0
- package/lib/core/util.js +2 -1
- package/lib/graphic/Displayable.js +1 -1
- package/lib/graphic/Text.d.ts +4 -2
- package/lib/graphic/Text.js +23 -14
- package/lib/graphic/helper/parseText.d.ts +13 -4
- package/lib/graphic/helper/parseText.js +110 -54
- package/lib/svg-legacy/helper/ClippathManager.js +6 -6
- package/lib/tool/color.d.ts +3 -1
- package/lib/tool/color.js +6 -6
- package/lib/tool/parseSVG.js +11 -0
- package/lib/tool/path.js +7 -4
- package/lib/zrender.d.ts +1 -1
- package/lib/zrender.js +1 -1
- package/package.json +3 -2
- package/src/Element.ts +69 -16
- package/src/Handler.ts +1 -1
- package/src/Storage.ts +25 -24
- package/src/canvas/graphic.ts +1 -1
- package/src/canvas/helper.ts +1 -1
- package/src/contain/text.ts +103 -19
- package/src/core/BoundingRect.ts +308 -87
- package/src/core/OrientedBoundingRect.ts +86 -46
- package/src/core/PathProxy.ts +17 -1
- package/src/core/Transformable.ts +2 -0
- package/src/core/dom.ts +24 -0
- package/src/core/env.ts +31 -24
- package/src/core/matrix.ts +2 -1
- package/src/core/types.ts +2 -0
- package/src/core/util.ts +4 -2
- package/src/graphic/Displayable.ts +1 -3
- package/src/graphic/Group.ts +2 -0
- package/src/graphic/Text.ts +68 -21
- package/src/graphic/helper/parseText.ts +211 -83
- package/src/svg-legacy/helper/ClippathManager.ts +5 -5
- package/src/tool/color.ts +15 -11
- package/src/tool/parseSVG.ts +12 -1
- package/src/tool/path.ts +9 -4
- package/src/zrender.ts +1 -1
package/src/Element.ts
CHANGED
@@ -31,6 +31,7 @@ import Point from './core/Point';
|
|
31
31
|
import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config';
|
32
32
|
import { parse, stringify } from './tool/color';
|
33
33
|
import { REDRAW_BIT } from './graphic/constants';
|
34
|
+
import { invert } from './core/matrix';
|
34
35
|
|
35
36
|
export interface ElementAnimateConfig {
|
36
37
|
duration?: number
|
@@ -81,7 +82,11 @@ export interface ElementTextConfig {
|
|
81
82
|
|
82
83
|
/**
|
83
84
|
* Rect that text will be positioned.
|
84
|
-
* Default to be the
|
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.
|
85
90
|
*/
|
86
91
|
layoutRect?: RectLike
|
87
92
|
|
@@ -109,6 +114,10 @@ export interface ElementTextConfig {
|
|
109
114
|
|
110
115
|
/**
|
111
116
|
* 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
|
+
*
|
112
121
|
* @default false
|
113
122
|
*/
|
114
123
|
local?: boolean
|
@@ -166,6 +175,16 @@ export interface ElementTextConfig {
|
|
166
175
|
* In case position is not using builtin `inside` hints.
|
167
176
|
*/
|
168
177
|
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
|
169
188
|
}
|
170
189
|
export interface ElementTextGuideLineConfig {
|
171
190
|
/**
|
@@ -238,6 +257,7 @@ export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial
|
|
238
257
|
draggable?: boolean | 'horizontal' | 'vertical'
|
239
258
|
|
240
259
|
silent?: boolean
|
260
|
+
ignoreHostSilent?: boolean
|
241
261
|
|
242
262
|
ignoreClip?: boolean
|
243
263
|
globalScaleRatio?: number
|
@@ -277,8 +297,9 @@ export type ElementCalculateTextPosition = (
|
|
277
297
|
rect: RectLike
|
278
298
|
) => TextPositionCalculationResult;
|
279
299
|
|
280
|
-
|
281
|
-
|
300
|
+
const tmpTextPosCalcRes = {} as TextPositionCalculationResult;
|
301
|
+
const tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
|
302
|
+
const tmpInnerTextTrans: number[] = [];
|
282
303
|
|
283
304
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
284
305
|
interface Element<Props extends ElementProps = ElementProps> extends Transformable,
|
@@ -313,6 +334,14 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
313
334
|
*/
|
314
335
|
silent: boolean
|
315
336
|
|
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
|
+
|
316
345
|
/**
|
317
346
|
* 是否是 Group
|
318
347
|
*/
|
@@ -368,6 +397,8 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
368
397
|
*/
|
369
398
|
__inHover: boolean
|
370
399
|
|
400
|
+
__clipPaths?: Path[]
|
401
|
+
|
371
402
|
/**
|
372
403
|
* path to clip the elements and its children, if it is a group.
|
373
404
|
* @see http://www.w3.org/TR/2dcontext/#clipping-region
|
@@ -511,9 +542,12 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
511
542
|
// Reset x/y/rotation
|
512
543
|
innerTransformable.copyTransform(textEl);
|
513
544
|
|
514
|
-
|
515
|
-
|
516
|
-
|
545
|
+
const hasPosition = textConfig.position != null;
|
546
|
+
const autoOverflowArea = textConfig.autoOverflowArea;
|
547
|
+
|
548
|
+
let layoutRect: BoundingRect;
|
549
|
+
if (autoOverflowArea || hasPosition) {
|
550
|
+
layoutRect = tmpBoundingRect;
|
517
551
|
if (textConfig.layoutRect) {
|
518
552
|
layoutRect.copy(textConfig.layoutRect);
|
519
553
|
}
|
@@ -523,7 +557,10 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
523
557
|
if (!isLocal) {
|
524
558
|
layoutRect.applyTransform(this.transform);
|
525
559
|
}
|
560
|
+
}
|
526
561
|
|
562
|
+
// Force set attached text's position if `position` is in config.
|
563
|
+
if (hasPosition) {
|
527
564
|
if (this.calculateTextPosition) {
|
528
565
|
this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
|
529
566
|
}
|
@@ -578,11 +615,27 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
578
615
|
}
|
579
616
|
}
|
580
617
|
|
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
|
+
|
581
635
|
// Calculate text color
|
582
636
|
const isInside = textConfig.inside == null // Force to be inside or not.
|
583
637
|
? (typeof textConfig.position === 'string' && textConfig.position.indexOf('inside') >= 0)
|
584
638
|
: textConfig.inside;
|
585
|
-
const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {});
|
586
639
|
|
587
640
|
let textFill;
|
588
641
|
let textStroke;
|
@@ -1021,16 +1074,16 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
1021
1074
|
* Return if el.silent or any ancestor element has silent true.
|
1022
1075
|
*/
|
1023
1076
|
isSilent() {
|
1024
|
-
|
1025
|
-
let
|
1026
|
-
while (
|
1027
|
-
if (
|
1028
|
-
|
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;
|
1030
1082
|
}
|
1031
|
-
|
1083
|
+
const hostEl = el.__hostTarget;
|
1084
|
+
el = hostEl ? (el.ignoreHostSilent ? null : hostEl) : el.parent;
|
1032
1085
|
}
|
1033
|
-
return
|
1086
|
+
return false;
|
1034
1087
|
}
|
1035
1088
|
|
1036
1089
|
/**
|
@@ -1637,6 +1690,7 @@ class Element<Props extends ElementProps = ElementProps> {
|
|
1637
1690
|
|
1638
1691
|
elProto.ignore =
|
1639
1692
|
elProto.silent =
|
1693
|
+
elProto.ignoreHostSilent =
|
1640
1694
|
elProto.isGroup =
|
1641
1695
|
elProto.draggable =
|
1642
1696
|
elProto.dragging =
|
@@ -2026,5 +2080,4 @@ function animateToShallow<T>(
|
|
2026
2080
|
}
|
2027
2081
|
}
|
2028
2082
|
|
2029
|
-
|
2030
2083
|
export default Element;
|
package/src/Handler.ts
CHANGED
@@ -504,7 +504,7 @@ function isHover(displayable: Displayable, x: number, y: number) {
|
|
504
504
|
// Consider when el is textContent, also need to be silent
|
505
505
|
// if any of its host el and its ancestors is silent.
|
506
506
|
const hostEl = el.__hostTarget;
|
507
|
-
el = hostEl ? hostEl : el.parent;
|
507
|
+
el = hostEl ? (el.ignoreHostSilent ? null : hostEl) : el.parent;
|
508
508
|
}
|
509
509
|
return isSilent ? SILENT : true;
|
510
510
|
}
|
package/src/Storage.ts
CHANGED
@@ -8,6 +8,7 @@ import timsort from './core/timsort';
|
|
8
8
|
import Displayable from './graphic/Displayable';
|
9
9
|
import Path from './graphic/Path';
|
10
10
|
import { REDRAW_BIT } from './graphic/constants';
|
11
|
+
import { NullUndefined } from './core/types';
|
11
12
|
|
12
13
|
let invalidZErrorLogged = false;
|
13
14
|
function logInvalidZError() {
|
@@ -83,7 +84,7 @@ export default class Storage {
|
|
83
84
|
|
84
85
|
private _updateAndAddDisplayable(
|
85
86
|
el: Element,
|
86
|
-
|
87
|
+
parentClipPaths: Path[] | NullUndefined,
|
87
88
|
includeIgnore?: boolean
|
88
89
|
) {
|
89
90
|
if (el.ignore && !includeIgnore) {
|
@@ -95,18 +96,21 @@ export default class Storage {
|
|
95
96
|
el.afterUpdate();
|
96
97
|
|
97
98
|
const userSetClipPath = el.getClipPath();
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if (
|
106
|
-
|
99
|
+
const parentHasClipPaths = parentClipPaths && parentClipPaths.length;
|
100
|
+
let clipPathIdx = 0;
|
101
|
+
let thisClipPaths = el.__clipPaths;
|
102
|
+
|
103
|
+
if (!el.ignoreClip
|
104
|
+
&& (parentHasClipPaths || userSetClipPath)
|
105
|
+
) { // has clipPath in this pass
|
106
|
+
if (!thisClipPaths) {
|
107
|
+
thisClipPaths = el.__clipPaths = [];
|
107
108
|
}
|
108
|
-
|
109
|
-
|
109
|
+
if (parentHasClipPaths) {
|
110
|
+
// PENDING: performance?
|
111
|
+
for (let idx = 0; idx < parentClipPaths.length; idx++) {
|
112
|
+
thisClipPaths[clipPathIdx++] = parentClipPaths[idx];
|
113
|
+
}
|
110
114
|
}
|
111
115
|
|
112
116
|
let currentClipPath = userSetClipPath;
|
@@ -118,13 +122,17 @@ export default class Storage {
|
|
118
122
|
currentClipPath.parent = parentClipPath as Group;
|
119
123
|
currentClipPath.updateTransform();
|
120
124
|
|
121
|
-
|
125
|
+
thisClipPaths[clipPathIdx++] = currentClipPath;
|
122
126
|
|
123
127
|
parentClipPath = currentClipPath;
|
124
128
|
currentClipPath = currentClipPath.getClipPath();
|
125
129
|
}
|
126
130
|
}
|
127
131
|
|
132
|
+
if (thisClipPaths) { // Remove other old clipPath in array.
|
133
|
+
thisClipPaths.length = clipPathIdx;
|
134
|
+
}
|
135
|
+
|
128
136
|
// ZRText and Group and combining morphing Path may use children
|
129
137
|
if ((el as GroupLike).childrenRef) {
|
130
138
|
const children = (el as GroupLike).childrenRef();
|
@@ -137,7 +145,7 @@ export default class Storage {
|
|
137
145
|
child.__dirty |= REDRAW_BIT;
|
138
146
|
}
|
139
147
|
|
140
|
-
this._updateAndAddDisplayable(child,
|
148
|
+
this._updateAndAddDisplayable(child, thisClipPaths, includeIgnore);
|
141
149
|
}
|
142
150
|
|
143
151
|
// Mark group clean here
|
@@ -146,13 +154,6 @@ export default class Storage {
|
|
146
154
|
}
|
147
155
|
else {
|
148
156
|
const disp = el as Displayable;
|
149
|
-
// Element is displayable
|
150
|
-
if (clipPaths && clipPaths.length) {
|
151
|
-
disp.__clipPaths = clipPaths;
|
152
|
-
}
|
153
|
-
else if (disp.__clipPaths && disp.__clipPaths.length > 0) {
|
154
|
-
disp.__clipPaths = [];
|
155
|
-
}
|
156
157
|
|
157
158
|
// Avoid invalid z, z2, zlevel cause sorting error.
|
158
159
|
if (isNaN(disp.z)) {
|
@@ -174,18 +175,18 @@ export default class Storage {
|
|
174
175
|
// Add decal
|
175
176
|
const decalEl = (el as Path).getDecalElement && (el as Path).getDecalElement();
|
176
177
|
if (decalEl) {
|
177
|
-
this._updateAndAddDisplayable(decalEl,
|
178
|
+
this._updateAndAddDisplayable(decalEl, thisClipPaths, includeIgnore);
|
178
179
|
}
|
179
180
|
|
180
181
|
// Add attached text element and guide line.
|
181
182
|
const textGuide = el.getTextGuideLine();
|
182
183
|
if (textGuide) {
|
183
|
-
this._updateAndAddDisplayable(textGuide,
|
184
|
+
this._updateAndAddDisplayable(textGuide, thisClipPaths, includeIgnore);
|
184
185
|
}
|
185
186
|
|
186
187
|
const textEl = el.getTextContent();
|
187
188
|
if (textEl) {
|
188
|
-
this._updateAndAddDisplayable(textEl,
|
189
|
+
this._updateAndAddDisplayable(textEl, thisClipPaths, includeIgnore);
|
189
190
|
}
|
190
191
|
}
|
191
192
|
|
package/src/canvas/graphic.ts
CHANGED
@@ -152,7 +152,7 @@ function brushPath(ctx: CanvasRenderingContext2D, el: Path, style: PathStyleProp
|
|
152
152
|
strokePattern = (dirtyFlag || !el.__canvasStrokePattern)
|
153
153
|
? createCanvasPattern(ctx, stroke as ImagePatternObject, el)
|
154
154
|
: el.__canvasStrokePattern;
|
155
|
-
el.__canvasStrokePattern =
|
155
|
+
el.__canvasStrokePattern = strokePattern;
|
156
156
|
}
|
157
157
|
// Use the gradient or pattern
|
158
158
|
if (hasFillGradient) {
|
package/src/canvas/helper.ts
CHANGED
@@ -82,8 +82,8 @@ export function getCanvasGradient(this: void, ctx: CanvasRenderingContext2D, obj
|
|
82
82
|
return canvasGradient;
|
83
83
|
}
|
84
84
|
|
85
|
+
// [CAVEAT] Assume the clipPaths array is never modified during a batch of `isClipPathChanged` calling.
|
85
86
|
export function isClipPathChanged(clipPaths: Path[], prevClipPaths: Path[]): boolean {
|
86
|
-
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
|
87
87
|
if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
|
88
88
|
return false;
|
89
89
|
}
|
package/src/contain/text.ts
CHANGED
@@ -1,25 +1,110 @@
|
|
1
1
|
import BoundingRect, { RectLike } from '../core/BoundingRect';
|
2
|
-
import {
|
2
|
+
import { TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types';
|
3
3
|
import LRU from '../core/LRU';
|
4
4
|
import { DEFAULT_FONT, platformApi } from '../core/platform';
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
/**
|
7
|
+
* @deprecated But keep for possible outside usage.
|
8
|
+
* Use `ensureFontMeasureInfo` + `measureWidth` instead.
|
9
|
+
*/
|
8
10
|
export function getWidth(text: string, font: string): number {
|
11
|
+
return measureWidth(ensureFontMeasureInfo(font), text);
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface FontMeasureInfo {
|
15
|
+
font: string;
|
16
|
+
strWidthCache: LRU<number>;
|
17
|
+
// Key: char code, index: 0~127 (include 127)
|
18
|
+
asciiWidthMap: number[] | null | undefined;
|
19
|
+
asciiWidthMapTried: boolean;
|
20
|
+
// Default width char width used both in non-ascii and line height.
|
21
|
+
stWideCharWidth: number;
|
22
|
+
// Default asc char width
|
23
|
+
asciiCharWidth: number;
|
24
|
+
}
|
25
|
+
|
26
|
+
export function ensureFontMeasureInfo(font: string): FontMeasureInfo {
|
27
|
+
if (!_fontMeasureInfoCache) {
|
28
|
+
_fontMeasureInfoCache = new LRU(100);
|
29
|
+
}
|
9
30
|
font = font || DEFAULT_FONT;
|
10
|
-
let
|
11
|
-
if (!
|
12
|
-
|
31
|
+
let measureInfo = _fontMeasureInfoCache.get(font);
|
32
|
+
if (!measureInfo) {
|
33
|
+
measureInfo = {
|
34
|
+
font: font,
|
35
|
+
strWidthCache: new LRU(500),
|
36
|
+
asciiWidthMap: null, // Init lazily for performance.
|
37
|
+
asciiWidthMapTried: false,
|
38
|
+
// FIXME
|
39
|
+
// Other languages?
|
40
|
+
// FIXME
|
41
|
+
// Consider proportional font?
|
42
|
+
stWideCharWidth: platformApi.measureText('国', font).width,
|
43
|
+
asciiCharWidth: platformApi.measureText('a', font).width,
|
44
|
+
};
|
45
|
+
_fontMeasureInfoCache.put(font, measureInfo);
|
13
46
|
}
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
47
|
+
return measureInfo;
|
48
|
+
}
|
49
|
+
let _fontMeasureInfoCache: LRU<FontMeasureInfo>;
|
50
|
+
|
51
|
+
/**
|
52
|
+
* For getting more precise result in truncate.
|
53
|
+
* non-monospace font vary in char width.
|
54
|
+
* But if it is time consuming in some platform, return null/undefined.
|
55
|
+
* @return Key: char code, index: 0~127 (include 127)
|
56
|
+
*/
|
57
|
+
function tryCreateASCIIWidthMap(font: string): FontMeasureInfo['asciiWidthMap'] {
|
58
|
+
// PENDING: is it necessary? Re-examine it if bad case reported.
|
59
|
+
if (_getASCIIWidthMapLongCount >= GET_ASCII_WIDTH_LONG_COUNT_MAX) {
|
60
|
+
return;
|
18
61
|
}
|
62
|
+
font = font || DEFAULT_FONT;
|
63
|
+
const asciiWidthMap = [];
|
64
|
+
const start = +(new Date());
|
65
|
+
// 0~31 and 127 may also have width, and may vary in some fonts.
|
66
|
+
for (let code = 0; code <= 127; code++) {
|
67
|
+
asciiWidthMap[code] = platformApi.measureText(String.fromCharCode(code), font).width;
|
68
|
+
}
|
69
|
+
const cost = +(new Date()) - start;
|
70
|
+
if (cost > 16) {
|
71
|
+
_getASCIIWidthMapLongCount = GET_ASCII_WIDTH_LONG_COUNT_MAX;
|
72
|
+
}
|
73
|
+
else if (cost > 2) {
|
74
|
+
_getASCIIWidthMapLongCount++;
|
75
|
+
}
|
76
|
+
return asciiWidthMap;
|
77
|
+
}
|
78
|
+
let _getASCIIWidthMapLongCount: number = 0;
|
79
|
+
const GET_ASCII_WIDTH_LONG_COUNT_MAX = 5;
|
19
80
|
|
81
|
+
/**
|
82
|
+
* Hot path, performance sensitive.
|
83
|
+
*/
|
84
|
+
export function measureCharWidth(fontMeasureInfo: FontMeasureInfo, charCode: number): number {
|
85
|
+
if (!fontMeasureInfo.asciiWidthMapTried) {
|
86
|
+
fontMeasureInfo.asciiWidthMap = tryCreateASCIIWidthMap(fontMeasureInfo.font);
|
87
|
+
fontMeasureInfo.asciiWidthMapTried = true;
|
88
|
+
}
|
89
|
+
return (0 <= charCode && charCode <= 127)
|
90
|
+
? (fontMeasureInfo.asciiWidthMap != null
|
91
|
+
? fontMeasureInfo.asciiWidthMap[charCode]
|
92
|
+
: fontMeasureInfo.asciiCharWidth
|
93
|
+
)
|
94
|
+
: fontMeasureInfo.stWideCharWidth;
|
95
|
+
}
|
96
|
+
|
97
|
+
export function measureWidth(fontMeasureInfo: FontMeasureInfo, text: string): number {
|
98
|
+
const strWidthCache = fontMeasureInfo.strWidthCache;
|
99
|
+
let width = strWidthCache.get(text);
|
100
|
+
if (width == null) {
|
101
|
+
width = platformApi.measureText(text, fontMeasureInfo.font).width;
|
102
|
+
strWidthCache.put(text, width);
|
103
|
+
}
|
20
104
|
return width;
|
21
105
|
}
|
22
106
|
|
107
|
+
|
23
108
|
/**
|
24
109
|
*
|
25
110
|
* Get bounding rect for inner usage(TSpan)
|
@@ -31,7 +116,7 @@ export function innerGetBoundingRect(
|
|
31
116
|
textAlign?: TextAlign,
|
32
117
|
textBaseline?: TextVerticalAlign
|
33
118
|
): BoundingRect {
|
34
|
-
const width =
|
119
|
+
const width = measureWidth(ensureFontMeasureInfo(font), text);
|
35
120
|
const height = getLineHeight(font);
|
36
121
|
|
37
122
|
const x = adjustTextX(0, width, textAlign);
|
@@ -68,31 +153,30 @@ export function getBoundingRect(
|
|
68
153
|
}
|
69
154
|
}
|
70
155
|
|
71
|
-
export function adjustTextX(x: number, width: number, textAlign: TextAlign): number {
|
156
|
+
export function adjustTextX(x: number, width: number, textAlign: TextAlign, inverse?: boolean): number {
|
72
157
|
// TODO Right to left language
|
73
158
|
if (textAlign === 'right') {
|
74
|
-
x -= width;
|
159
|
+
!inverse ? (x -= width) : (x += width);
|
75
160
|
}
|
76
161
|
else if (textAlign === 'center') {
|
77
|
-
x -= width / 2;
|
162
|
+
!inverse ? (x -= width / 2) : (x += width / 2);
|
78
163
|
}
|
79
164
|
return x;
|
80
165
|
}
|
81
166
|
|
82
|
-
export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign): number {
|
167
|
+
export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign, inverse?: boolean): number {
|
83
168
|
if (verticalAlign === 'middle') {
|
84
|
-
y -= height / 2;
|
169
|
+
!inverse ? (y -= height / 2) : (y += height / 2);
|
85
170
|
}
|
86
171
|
else if (verticalAlign === 'bottom') {
|
87
|
-
y -= height;
|
172
|
+
!inverse ? (y -= height) : (y += height);
|
88
173
|
}
|
89
174
|
return y;
|
90
175
|
}
|
91
176
|
|
92
|
-
|
93
177
|
export function getLineHeight(font?: string): number {
|
94
178
|
// FIXME A rough approach.
|
95
|
-
return
|
179
|
+
return ensureFontMeasureInfo(font).stWideCharWidth;
|
96
180
|
}
|
97
181
|
|
98
182
|
export function measureText(text: string, font?: string): {
|