@meta2d/core 1.0.55 → 1.0.56

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.
Files changed (302) hide show
  1. package/README.md +13 -0
  2. package/package.build.json +39 -0
  3. package/package.json +38 -38
  4. package/src/canvas/canvas.ts +8639 -0
  5. package/src/canvas/canvasImage.ts +525 -0
  6. package/src/canvas/canvasTemplate.ts +257 -0
  7. package/src/canvas/magnifierCanvas.ts +142 -0
  8. package/src/canvas/offscreen.ts +14 -0
  9. package/src/core.ts +5128 -0
  10. package/src/data.ts +86 -0
  11. package/src/diagrams/arrow.ts +50 -0
  12. package/src/diagrams/circle.ts +19 -0
  13. package/src/diagrams/cloud.ts +34 -0
  14. package/src/diagrams/cube.ts +94 -0
  15. package/src/diagrams/diamond.ts +14 -0
  16. package/src/diagrams/file.ts +19 -0
  17. package/src/diagrams/gif.ts +105 -0
  18. package/src/diagrams/{hexagon.js → hexagon.ts} +19 -14
  19. package/src/diagrams/iframe.ts +365 -0
  20. package/src/diagrams/{index.js → index.ts} +36 -34
  21. package/src/diagrams/line/arrow.ts +175 -0
  22. package/src/diagrams/line/curve.ts +260 -0
  23. package/src/diagrams/line/line.ts +409 -0
  24. package/src/diagrams/line/polyline.ts +676 -0
  25. package/src/diagrams/line/smooth.ts +133 -0
  26. package/src/diagrams/message.ts +17 -0
  27. package/src/diagrams/mindLine.ts +31 -0
  28. package/src/diagrams/mindNode.ts +177 -0
  29. package/src/diagrams/panel.ts +149 -0
  30. package/src/diagrams/pentagon.ts +48 -0
  31. package/src/diagrams/pentagram.ts +63 -0
  32. package/src/diagrams/people.ts +23 -0
  33. package/src/diagrams/rectangle.ts +29 -0
  34. package/src/diagrams/svg/parse.ts +319 -0
  35. package/src/diagrams/svgPath.ts +53 -0
  36. package/src/diagrams/triangle.ts +43 -0
  37. package/src/diagrams/video.ts +202 -0
  38. package/src/dialog/dialog.ts +177 -0
  39. package/src/event/event.ts +142 -0
  40. package/src/map/map.ts +239 -0
  41. package/src/options.ts +205 -0
  42. package/src/pen/arrow.ts +259 -0
  43. package/src/pen/math.ts +253 -0
  44. package/src/pen/model.ts +785 -0
  45. package/src/pen/plugin.ts +57 -0
  46. package/src/pen/render.ts +3725 -0
  47. package/src/pen/text.ts +341 -0
  48. package/src/pen/utils.ts +21 -0
  49. package/src/point/point.ts +232 -0
  50. package/src/rect/rect.ts +507 -0
  51. package/src/rect/triangle.ts +16 -0
  52. package/src/scroll/scroll.ts +277 -0
  53. package/src/store/global.ts +38 -0
  54. package/src/store/store.ts +293 -0
  55. package/src/theme.ts +35 -0
  56. package/src/title/title.ts +115 -0
  57. package/src/tooltip/tooltip.ts +199 -0
  58. package/src/utils/clone.ts +79 -0
  59. package/src/utils/color.ts +126 -0
  60. package/src/utils/error.ts +7 -0
  61. package/src/utils/file.ts +46 -0
  62. package/src/utils/{index.d.ts → index.ts} +1 -0
  63. package/src/utils/math.ts +120 -0
  64. package/src/utils/object.ts +23 -0
  65. package/src/utils/padding.ts +48 -0
  66. package/src/utils/time.ts +25 -0
  67. package/src/utils/url.ts +30 -0
  68. package/src/utils/uuid.ts +15 -0
  69. package/index.js +0 -10
  70. package/index.js.map +0 -1
  71. package/src/canvas/canvas.d.ts +0 -456
  72. package/src/canvas/canvas.js +0 -8187
  73. package/src/canvas/canvas.js.map +0 -1
  74. package/src/canvas/canvasImage.d.ts +0 -28
  75. package/src/canvas/canvasImage.js +0 -503
  76. package/src/canvas/canvasImage.js.map +0 -1
  77. package/src/canvas/canvasTemplate.d.ts +0 -19
  78. package/src/canvas/canvasTemplate.js +0 -229
  79. package/src/canvas/canvasTemplate.js.map +0 -1
  80. package/src/canvas/index.js +0 -3
  81. package/src/canvas/index.js.map +0 -1
  82. package/src/canvas/magnifierCanvas.d.ts +0 -20
  83. package/src/canvas/magnifierCanvas.js +0 -101
  84. package/src/canvas/magnifierCanvas.js.map +0 -1
  85. package/src/canvas/offscreen.d.ts +0 -2
  86. package/src/canvas/offscreen.js +0 -14
  87. package/src/canvas/offscreen.js.map +0 -1
  88. package/src/core.d.ts +0 -479
  89. package/src/core.js +0 -5199
  90. package/src/core.js.map +0 -1
  91. package/src/data.d.ts +0 -34
  92. package/src/data.js +0 -85
  93. package/src/data.js.map +0 -1
  94. package/src/diagrams/arrow.d.ts +0 -4
  95. package/src/diagrams/arrow.js +0 -47
  96. package/src/diagrams/arrow.js.map +0 -1
  97. package/src/diagrams/circle.d.ts +0 -2
  98. package/src/diagrams/circle.js +0 -9
  99. package/src/diagrams/circle.js.map +0 -1
  100. package/src/diagrams/cloud.d.ts +0 -2
  101. package/src/diagrams/cloud.js +0 -12
  102. package/src/diagrams/cloud.js.map +0 -1
  103. package/src/diagrams/cube.d.ts +0 -2
  104. package/src/diagrams/cube.js +0 -70
  105. package/src/diagrams/cube.js.map +0 -1
  106. package/src/diagrams/diamond.d.ts +0 -2
  107. package/src/diagrams/diamond.js +0 -13
  108. package/src/diagrams/diamond.js.map +0 -1
  109. package/src/diagrams/file.d.ts +0 -2
  110. package/src/diagrams/file.js +0 -18
  111. package/src/diagrams/file.js.map +0 -1
  112. package/src/diagrams/gif.d.ts +0 -5
  113. package/src/diagrams/gif.js +0 -90
  114. package/src/diagrams/gif.js.map +0 -1
  115. package/src/diagrams/hexagon.d.ts +0 -2
  116. package/src/diagrams/hexagon.js.map +0 -1
  117. package/src/diagrams/iframe.d.ts +0 -2
  118. package/src/diagrams/iframe.js +0 -356
  119. package/src/diagrams/iframe.js.map +0 -1
  120. package/src/diagrams/index.d.ts +0 -71
  121. package/src/diagrams/index.js.map +0 -1
  122. package/src/diagrams/line/arrow.d.ts +0 -2
  123. package/src/diagrams/line/arrow.js +0 -128
  124. package/src/diagrams/line/arrow.js.map +0 -1
  125. package/src/diagrams/line/curve.d.ts +0 -16
  126. package/src/diagrams/line/curve.js +0 -236
  127. package/src/diagrams/line/curve.js.map +0 -1
  128. package/src/diagrams/line/index.js +0 -6
  129. package/src/diagrams/line/index.js.map +0 -1
  130. package/src/diagrams/line/line.d.ts +0 -42
  131. package/src/diagrams/line/line.js +0 -431
  132. package/src/diagrams/line/line.js.map +0 -1
  133. package/src/diagrams/line/polyline.d.ts +0 -10
  134. package/src/diagrams/line/polyline.js +0 -657
  135. package/src/diagrams/line/polyline.js.map +0 -1
  136. package/src/diagrams/line/smooth.d.ts +0 -3
  137. package/src/diagrams/line/smooth.js +0 -174
  138. package/src/diagrams/line/smooth.js.map +0 -1
  139. package/src/diagrams/message.d.ts +0 -2
  140. package/src/diagrams/message.js +0 -15
  141. package/src/diagrams/message.js.map +0 -1
  142. package/src/diagrams/mindLine.d.ts +0 -3
  143. package/src/diagrams/mindLine.js +0 -31
  144. package/src/diagrams/mindLine.js.map +0 -1
  145. package/src/diagrams/mindNode.d.ts +0 -3
  146. package/src/diagrams/mindNode.js +0 -189
  147. package/src/diagrams/mindNode.js.map +0 -1
  148. package/src/diagrams/panel.d.ts +0 -2
  149. package/src/diagrams/panel.js +0 -131
  150. package/src/diagrams/panel.js.map +0 -1
  151. package/src/diagrams/pentagon.d.ts +0 -3
  152. package/src/diagrams/pentagon.js +0 -46
  153. package/src/diagrams/pentagon.js.map +0 -1
  154. package/src/diagrams/pentagram.d.ts +0 -3
  155. package/src/diagrams/pentagram.js +0 -77
  156. package/src/diagrams/pentagram.js.map +0 -1
  157. package/src/diagrams/people.d.ts +0 -2
  158. package/src/diagrams/people.js +0 -19
  159. package/src/diagrams/people.js.map +0 -1
  160. package/src/diagrams/rectangle.d.ts +0 -3
  161. package/src/diagrams/rectangle.js +0 -26
  162. package/src/diagrams/rectangle.js.map +0 -1
  163. package/src/diagrams/svg/parse.d.ts +0 -15
  164. package/src/diagrams/svg/parse.js +0 -326
  165. package/src/diagrams/svg/parse.js.map +0 -1
  166. package/src/diagrams/svgPath.d.ts +0 -2
  167. package/src/diagrams/svgPath.js +0 -30
  168. package/src/diagrams/svgPath.js.map +0 -1
  169. package/src/diagrams/triangle.d.ts +0 -3
  170. package/src/diagrams/triangle.js +0 -41
  171. package/src/diagrams/triangle.js.map +0 -1
  172. package/src/diagrams/video.d.ts +0 -5
  173. package/src/diagrams/video.js +0 -185
  174. package/src/diagrams/video.js.map +0 -1
  175. package/src/dialog/dialog.d.ts +0 -21
  176. package/src/dialog/dialog.js +0 -98
  177. package/src/dialog/dialog.js.map +0 -1
  178. package/src/dialog/index.js +0 -2
  179. package/src/dialog/index.js.map +0 -1
  180. package/src/event/event.d.ts +0 -102
  181. package/src/event/event.js +0 -22
  182. package/src/event/event.js.map +0 -1
  183. package/src/event/index.js +0 -2
  184. package/src/event/index.js.map +0 -1
  185. package/src/map/index.js +0 -2
  186. package/src/map/index.js.map +0 -1
  187. package/src/map/map.d.ts +0 -21
  188. package/src/map/map.js +0 -210
  189. package/src/map/map.js.map +0 -1
  190. package/src/options.d.ts +0 -130
  191. package/src/options.js +0 -80
  192. package/src/options.js.map +0 -1
  193. package/src/pen/arrow.d.ts +0 -4
  194. package/src/pen/arrow.js +0 -188
  195. package/src/pen/arrow.js.map +0 -1
  196. package/src/pen/index.js +0 -7
  197. package/src/pen/index.js.map +0 -1
  198. package/src/pen/math.d.ts +0 -28
  199. package/src/pen/math.js +0 -304
  200. package/src/pen/math.js.map +0 -1
  201. package/src/pen/model.d.ts +0 -514
  202. package/src/pen/model.js +0 -210
  203. package/src/pen/model.js.map +0 -1
  204. package/src/pen/plugin.d.ts +0 -5
  205. package/src/pen/plugin.js +0 -88
  206. package/src/pen/plugin.js.map +0 -1
  207. package/src/pen/render.d.ts +0 -147
  208. package/src/pen/render.js +0 -3236
  209. package/src/pen/render.js.map +0 -1
  210. package/src/pen/text.d.ts +0 -8
  211. package/src/pen/text.js +0 -375
  212. package/src/pen/text.js.map +0 -1
  213. package/src/pen/utils.d.ts +0 -2
  214. package/src/pen/utils.js +0 -41
  215. package/src/pen/utils.js.map +0 -1
  216. package/src/point/index.js +0 -2
  217. package/src/point/index.js.map +0 -1
  218. package/src/point/point.d.ts +0 -65
  219. package/src/point/point.js +0 -179
  220. package/src/point/point.js.map +0 -1
  221. package/src/rect/index.js +0 -2
  222. package/src/rect/index.js.map +0 -1
  223. package/src/rect/rect.d.ts +0 -52
  224. package/src/rect/rect.js +0 -486
  225. package/src/rect/rect.js.map +0 -1
  226. package/src/rect/triangle.d.ts +0 -2
  227. package/src/rect/triangle.js +0 -10
  228. package/src/rect/triangle.js.map +0 -1
  229. package/src/scroll/index.js +0 -2
  230. package/src/scroll/index.js.map +0 -1
  231. package/src/scroll/scroll.d.ts +0 -35
  232. package/src/scroll/scroll.js +0 -221
  233. package/src/scroll/scroll.js.map +0 -1
  234. package/src/store/global.d.ts +0 -25
  235. package/src/store/global.js +0 -18
  236. package/src/store/global.js.map +0 -1
  237. package/src/store/index.js +0 -3
  238. package/src/store/index.js.map +0 -1
  239. package/src/store/store.d.ts +0 -228
  240. package/src/store/store.js +0 -121
  241. package/src/store/store.js.map +0 -1
  242. package/src/theme.d.ts +0 -13
  243. package/src/theme.js +0 -23
  244. package/src/theme.js.map +0 -1
  245. package/src/title/index.js +0 -2
  246. package/src/title/index.js.map +0 -1
  247. package/src/title/title.d.ts +0 -30
  248. package/src/title/title.js +0 -99
  249. package/src/title/title.js.map +0 -1
  250. package/src/tooltip/index.js +0 -2
  251. package/src/tooltip/index.js.map +0 -1
  252. package/src/tooltip/tooltip.d.ts +0 -40
  253. package/src/tooltip/tooltip.js +0 -167
  254. package/src/tooltip/tooltip.js.map +0 -1
  255. package/src/utils/browser.d.ts +0 -1
  256. package/src/utils/browser.js +0 -4
  257. package/src/utils/browser.js.map +0 -1
  258. package/src/utils/clone.d.ts +0 -8
  259. package/src/utils/clone.js +0 -88
  260. package/src/utils/clone.js.map +0 -1
  261. package/src/utils/color.d.ts +0 -3
  262. package/src/utils/color.js +0 -126
  263. package/src/utils/color.js.map +0 -1
  264. package/src/utils/error.d.ts +0 -2
  265. package/src/utils/error.js +0 -6
  266. package/src/utils/error.js.map +0 -1
  267. package/src/utils/file.d.ts +0 -3
  268. package/src/utils/file.js +0 -92
  269. package/src/utils/file.js.map +0 -1
  270. package/src/utils/index.js +0 -9
  271. package/src/utils/index.js.map +0 -1
  272. package/src/utils/math.d.ts +0 -18
  273. package/src/utils/math.js +0 -152
  274. package/src/utils/math.js.map +0 -1
  275. package/src/utils/object.d.ts +0 -2
  276. package/src/utils/object.js +0 -21
  277. package/src/utils/object.js.map +0 -1
  278. package/src/utils/padding.d.ts +0 -7
  279. package/src/utils/padding.js +0 -47
  280. package/src/utils/padding.js.map +0 -1
  281. package/src/utils/time.d.ts +0 -1
  282. package/src/utils/time.js +0 -17
  283. package/src/utils/time.js.map +0 -1
  284. package/src/utils/url.d.ts +0 -4
  285. package/src/utils/url.js +0 -27
  286. package/src/utils/url.js.map +0 -1
  287. package/src/utils/uuid.d.ts +0 -4
  288. package/src/utils/uuid.js +0 -13
  289. package/src/utils/uuid.js.map +0 -1
  290. /package/{index.d.ts → index.ts} +0 -0
  291. /package/src/canvas/{index.d.ts → index.ts} +0 -0
  292. /package/src/diagrams/line/{index.d.ts → index.ts} +0 -0
  293. /package/src/dialog/{index.d.ts → index.ts} +0 -0
  294. /package/src/event/{index.d.ts → index.ts} +0 -0
  295. /package/src/map/{index.d.ts → index.ts} +0 -0
  296. /package/src/pen/{index.d.ts → index.ts} +0 -0
  297. /package/src/point/{index.d.ts → index.ts} +0 -0
  298. /package/src/rect/{index.d.ts → index.ts} +0 -0
  299. /package/src/scroll/{index.d.ts → index.ts} +0 -0
  300. /package/src/store/{index.d.ts → index.ts} +0 -0
  301. /package/src/title/{index.d.ts → index.ts} +0 -0
  302. /package/src/tooltip/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,3725 @@
1
+ import {
2
+ CanvasLayer,
3
+ ColorStop,
4
+ IValue,
5
+ LineAnimateType,
6
+ LockState,
7
+ Pen,
8
+ } from './model';
9
+ import { drawArrow, getLineRect, getSplitAnchor } from '../diagrams';
10
+ import { Direction, inheritanceProps } from '../data';
11
+ import {
12
+ calcRotate,
13
+ distance,
14
+ facePoint,
15
+ Point,
16
+ rotatePoint,
17
+ scalePoint,
18
+ translatePoint,
19
+ TwoWay,
20
+ } from '../point';
21
+ import {
22
+ calcCenter,
23
+ calcRightBottom,
24
+ calcRelativePoint,
25
+ calcRelativeRect,
26
+ Rect,
27
+ rectInRect,
28
+ scaleRect,
29
+ translateRect,
30
+ calcPivot
31
+ } from '../rect';
32
+ import { globalStore, Meta2dStore } from '../store';
33
+ import { calcTextLines, calcTextDrawRect, calcTextRect } from './text';
34
+ import { deepClone } from '../utils/clone';
35
+ import { renderFromArrow, renderToArrow } from './arrow';
36
+ import { Gradient, isEqual, PenType } from '../pen';
37
+ import { pSBC, rgba } from '../utils';
38
+ import { Canvas } from '../canvas';
39
+
40
+ /**
41
+ * ancestor 是否是 pen 的祖先
42
+ * @param pen 当前画笔
43
+ * @param ancestor 祖先画笔
44
+ */
45
+ export function isAncestor(pen: Pen, ancestor: Pen) {
46
+ if (!pen || !ancestor) {
47
+ return false;
48
+ }
49
+ let parent = getParent(pen);
50
+ while (parent) {
51
+ if (parent.id === ancestor.id) {
52
+ return true;
53
+ }
54
+ parent = getParent(parent);
55
+ }
56
+ return false;
57
+ }
58
+
59
+ export function getParent(pen: Pen, root?: boolean): Pen {
60
+ if (!pen || !pen.parentId || !pen.calculative) {
61
+ return undefined;
62
+ }
63
+
64
+ const store = pen.calculative.canvas.store;
65
+ const parent = store.pens[pen.parentId];
66
+ if (!root) {
67
+ return parent;
68
+ }
69
+ return getParent(parent, root) || parent;
70
+ }
71
+
72
+ export function getAllChildren(pen: Pen, store: Meta2dStore): Pen[] {
73
+ if (!pen || !pen.children) {
74
+ return [];
75
+ }
76
+ const children: Pen[] = [];
77
+ pen.children.forEach((id) => {
78
+ const child = store.pens[id];
79
+ if (child) {
80
+ children.push(child);
81
+ children.push(...getAllChildren(child, store));
82
+ }
83
+ });
84
+ return children;
85
+ }
86
+
87
+ export function getAllFollowers(pen: Pen, store: Meta2dStore): Pen[] {
88
+ if (!pen || !pen.followers) {
89
+ return [];
90
+ }
91
+ const followers: Pen[] = [];
92
+ pen.followers.forEach((id) => {
93
+ const follower = store.pens[id];
94
+ if (follower&&!follower.parentId) {
95
+ followers.push(follower);
96
+ followers.push(...getAllFollowers(follower, store));
97
+ }
98
+ });
99
+ return followers;
100
+ }
101
+
102
+ function drawBkLinearGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
103
+ const { worldRect, gradientFromColor, gradientToColor, gradientAngle } =
104
+ pen.calculative;
105
+ return linearGradient(
106
+ ctx,
107
+ worldRect,
108
+ gradientFromColor,
109
+ gradientToColor,
110
+ gradientAngle
111
+ );
112
+ }
113
+
114
+ /**
115
+ * 避免副作用,把创建好后的径向渐变对象返回出来
116
+ * @param ctx 画布绘制对象
117
+ * @param pen 当前画笔
118
+ * @returns 径向渐变
119
+ */
120
+ function drawBkRadialGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
121
+ const { worldRect, gradientFromColor, gradientToColor, gradientRadius } =
122
+ pen.calculative;
123
+ if (!gradientFromColor || !gradientToColor) {
124
+ return;
125
+ }
126
+
127
+ const { width, height, center } = worldRect;
128
+ const { x: centerX, y: centerY } = center;
129
+ let r = width;
130
+ if (r < height) {
131
+ r = height;
132
+ }
133
+ r *= 0.5;
134
+ const grd = ctx.createRadialGradient(
135
+ centerX,
136
+ centerY,
137
+ r * (gradientRadius || 0),
138
+ centerX,
139
+ centerY,
140
+ r
141
+ );
142
+ grd.addColorStop(0, gradientFromColor);
143
+ grd.addColorStop(1, gradientToColor);
144
+
145
+ return grd;
146
+ }
147
+
148
+ function getLinearGradientPoints(
149
+ x1: number,
150
+ y1: number,
151
+ x2: number,
152
+ y2: number,
153
+ r: number
154
+ ) {
155
+ let slantAngle = 0;
156
+ slantAngle = Math.PI / 2 - Math.atan2(y2 - y1, x2 - x1);
157
+ const originX = (x1 + x2) / 2;
158
+ const originY = (y1 + y2) / 2;
159
+
160
+ const perpX1 = originX + r * Math.sin((90 * Math.PI) / 180 - slantAngle);
161
+ const perpY1 = originY + r * -Math.cos((90 * Math.PI) / 180 - slantAngle);
162
+
163
+ const perpX2 = originX + r * Math.sin((270 * Math.PI) / 180 - slantAngle);
164
+ const perpY2 = originY + r * -Math.cos((270 * Math.PI) / 180 - slantAngle);
165
+
166
+ return [perpX1, perpY1, perpX2, perpY2];
167
+ }
168
+
169
+ function getBkRadialGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
170
+ const { worldRect, gradientColors, gradientRadius } = pen.calculative;
171
+ if (!gradientColors) {
172
+ return;
173
+ }
174
+
175
+ const { width, height, center } = worldRect;
176
+ const { x: centerX, y: centerY } = center;
177
+ let r = width;
178
+ if (r < height) {
179
+ r = height;
180
+ }
181
+ r *= 0.5;
182
+ const { colors } = formatGradient(gradientColors);
183
+ const grd = ctx.createRadialGradient(
184
+ centerX,
185
+ centerY,
186
+ r * (gradientRadius || 0),
187
+ centerX,
188
+ centerY,
189
+ r
190
+ );
191
+ colors.forEach((stop) => {
192
+ grd.addColorStop(stop.i, stop.color);
193
+ });
194
+
195
+ return grd;
196
+ }
197
+
198
+ function getBkGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
199
+ const { x, y, ex, width, height, center } = pen.calculative.worldRect;
200
+ let points = [
201
+ { x: ex, y: y + height / 2 },
202
+ { x: x, y: y + height / 2 },
203
+ ];
204
+ const { angle, colors } = formatGradient(pen.calculative.gradientColors);
205
+ let r = getGradientR(angle, width, height);
206
+ points.forEach((point) => {
207
+ rotatePoint(point, angle, center);
208
+ });
209
+ return getLinearGradient(ctx, points, colors, r);
210
+ }
211
+
212
+ function getTextRadialGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
213
+ const { worldRect, textGradientColors } = pen.calculative;
214
+ if (!textGradientColors) {
215
+ return;
216
+ }
217
+
218
+ const { width, height, center } = worldRect;
219
+ const { x: centerX, y: centerY } = center;
220
+ let r = width;
221
+ if (r < height) {
222
+ r = height;
223
+ }
224
+ r *= 0.5;
225
+ const { colors } = formatGradient(textGradientColors);
226
+ const grd = ctx.createRadialGradient(
227
+ centerX,
228
+ centerY,
229
+ 0,
230
+ centerX,
231
+ centerY,
232
+ r
233
+ );
234
+ colors.forEach((stop) => {
235
+ grd.addColorStop(stop.i, stop.color);
236
+ });
237
+
238
+ return grd;
239
+ }
240
+
241
+ function getTextGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
242
+ const { x, y, ex, width, height, center } = pen.calculative.worldRect;
243
+ let points = [
244
+ { x: ex, y: y + height / 2 },
245
+ { x: x, y: y + height / 2 },
246
+ ];
247
+ const { angle, colors } = formatGradient(pen.calculative.textGradientColors);
248
+ let r = getGradientR(angle, width, height);
249
+ points.forEach((point) => {
250
+ rotatePoint(point, angle, center);
251
+ });
252
+ return getLinearGradient(ctx, points, colors, r);
253
+ }
254
+
255
+ function getGradientR(angle: number, width: number, height: number) {
256
+ const dividAngle = (Math.atan(height / width) / Math.PI) * 180;
257
+ let calculateAngle = (angle - 90) % 360;
258
+ let r = 0;
259
+ if (
260
+ (calculateAngle > dividAngle && calculateAngle < 180 - dividAngle) ||
261
+ (calculateAngle > 180 + dividAngle && calculateAngle < 360 - dividAngle) ||
262
+ calculateAngle < 0
263
+ ) {
264
+ //根据高计算
265
+ if (calculateAngle > 270) {
266
+ calculateAngle = 360 - calculateAngle;
267
+ } else if (calculateAngle > 180) {
268
+ calculateAngle = calculateAngle - 180;
269
+ } else if (calculateAngle > 90) {
270
+ calculateAngle = 180 - calculateAngle;
271
+ }
272
+ r = Math.abs(height / Math.sin((calculateAngle / 180) * Math.PI) / 2);
273
+ } else {
274
+ //根据宽计算
275
+ if (calculateAngle > 270) {
276
+ calculateAngle = 360 - calculateAngle;
277
+ } else if (calculateAngle > 180) {
278
+ calculateAngle = calculateAngle - 180;
279
+ } else if (calculateAngle > 90) {
280
+ calculateAngle = 180 - calculateAngle;
281
+ }
282
+ r = Math.abs(width / Math.cos((calculateAngle / 180) * Math.PI) / 2);
283
+ }
284
+ return r;
285
+ }
286
+
287
+ function formatGradient(color: string) {
288
+ if (typeof color == 'string' && color.startsWith('linear-gradient')) {
289
+ let arr = color.slice(16, -2).split('deg,');
290
+ if (arr.length > 1) {
291
+ let _arr = arr[1].split('%,');
292
+ const colors = [];
293
+ _arr.forEach((stap) => {
294
+ if (/rgba?/.test(stap)) {
295
+ let _arr = stap.split(') ');
296
+ colors.push({
297
+ color: rgbaToHex(_arr[0] + ')'),
298
+ i: parseFloat(_arr[1]) / 100,
299
+ });
300
+ } else {
301
+ let _arr = stap.split(' ');
302
+ if (_arr.length > 2) {
303
+ colors.push({
304
+ color: _arr[1],
305
+ i: parseFloat(_arr[2]) / 100,
306
+ });
307
+ } else {
308
+ colors.push({
309
+ color: _arr[0],
310
+ i: parseFloat(_arr[1]) / 100,
311
+ });
312
+ }
313
+ }
314
+ });
315
+ return {
316
+ angle: parseFloat(arr[0]),
317
+ colors,
318
+ };
319
+ } else {
320
+ return {
321
+ angle: parseFloat(arr[0]),
322
+ colors: [],
323
+ };
324
+ }
325
+ } else {
326
+ return {
327
+ angle: 0,
328
+ colors: [],
329
+ };
330
+ }
331
+ }
332
+
333
+ function rgbaToHex(value) {
334
+ if (/rgba?/.test(value)) {
335
+ let array = value.split(',');
336
+ //不符合rgb或rgb规则直接return
337
+ if (array.length < 3) return '';
338
+ value = '#';
339
+ for (let i = 0, color; (color = array[i++]); ) {
340
+ if (i < 4) {
341
+ //前三位转换成16进制
342
+ color = parseInt(color.replace(/[^\d]/gi, ''), 10).toString(16);
343
+ value += color.length == 1 ? '0' + color : color;
344
+ } else {
345
+ //rgba的透明度转换成16进制
346
+ color = color.replace(')', '');
347
+ let colorA = parseInt(color * 255 + '');
348
+ let colorAHex = colorA.toString(16);
349
+ colorAHex = colorAHex.length === 2 ? colorAHex : '0' + colorAHex;
350
+ value += colorAHex;
351
+ }
352
+ }
353
+ value = value.toUpperCase();
354
+ }
355
+ return value;
356
+ }
357
+
358
+ function getLineGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
359
+ const { x, y, ex, width, height, center } = pen.calculative.worldRect;
360
+ let points = [
361
+ { x: ex, y: y + height / 2 },
362
+ { x: x, y: y + height / 2 },
363
+ ];
364
+
365
+ const { angle, colors } = formatGradient(pen.calculative.lineGradientColors);
366
+ let r = getGradientR(angle, width, height);
367
+
368
+ points.forEach((point) => {
369
+ rotatePoint(point, angle, center);
370
+ });
371
+ return getLinearGradient(ctx, points, colors, r);
372
+ }
373
+
374
+ function getLinearGradient(
375
+ ctx: CanvasRenderingContext2D,
376
+ points: Point[],
377
+ colors: ColorStop[],
378
+ radius: number
379
+ ): CanvasGradient {
380
+ let arr = getLinearGradientPoints(
381
+ points[0].x,
382
+ points[0].y,
383
+ points[1].x,
384
+ points[1].y,
385
+ radius
386
+ );
387
+ let gradient = ctx.createLinearGradient(arr[0], arr[1], arr[2], arr[3]);
388
+ colors.forEach((stop) => {
389
+ gradient.addColorStop(stop.i, stop.color);
390
+ });
391
+ return gradient;
392
+ }
393
+
394
+ function drawLinearGradientLine(
395
+ ctx: CanvasRenderingContext2D,
396
+ pen: Pen,
397
+ points: Point[]
398
+ ) {
399
+ let colors = [];
400
+ if (pen.calculative.gradientColorStop) {
401
+ colors = pen.calculative.gradientColorStop;
402
+ } else {
403
+ colors = formatGradient(pen.calculative.lineGradientColors).colors;
404
+ pen.calculative.gradientColorStop = colors;
405
+ }
406
+ ctx.strokeStyle = getLinearGradient(
407
+ ctx,
408
+ points,
409
+ colors,
410
+ pen.calculative.lineWidth / 2
411
+ );
412
+ ctx.beginPath();
413
+ ctx.moveTo(points[0].x, points[0].y);
414
+ ctx.lineTo(points[1].x, points[1].y);
415
+ ctx.stroke();
416
+ }
417
+
418
+ function ctxDrawLinearGradientPath(ctx: CanvasRenderingContext2D, pen: Pen) {
419
+ const anchors = pen.calculative.worldAnchors;
420
+ let smoothLenth =
421
+ pen.calculative.lineWidth * (pen.calculative.gradientSmooth || pen.calculative.lineSmooth || 0);
422
+ for (let i = 0; i < anchors.length - 1; i++) {
423
+ if (
424
+ (pen.lineName === 'curve' || pen.lineName === 'mind') &&
425
+ anchors[i].curvePoints
426
+ ) {
427
+ if (i > 0) {
428
+ let lastCurvePoints = anchors[i - 1].curvePoints;
429
+ if (lastCurvePoints) {
430
+ //上一个存在锚点
431
+ smoothTransition(
432
+ ctx,
433
+ pen,
434
+ smoothLenth,
435
+ lastCurvePoints[lastCurvePoints.length - 1],
436
+ anchors[i],
437
+ anchors[i].curvePoints[0]
438
+ );
439
+ } else {
440
+ smoothTransition(
441
+ ctx,
442
+ pen,
443
+ smoothLenth,
444
+ anchors[i - 1],
445
+ anchors[i],
446
+ anchors[i].curvePoints[0]
447
+ );
448
+ }
449
+ //获取当前相对于0的位置
450
+ let next = getSmoothAdjacent(
451
+ smoothLenth,
452
+ anchors[i],
453
+ anchors[i].curvePoints[0]
454
+ );
455
+ drawLinearGradientLine(ctx, pen, [next, anchors[i].curvePoints[1]]);
456
+ } else {
457
+ drawLinearGradientLine(ctx, pen, [
458
+ anchors[i],
459
+ anchors[i].curvePoints[0],
460
+ ]);
461
+ drawLinearGradientLine(ctx, pen, [
462
+ anchors[i].curvePoints[0],
463
+ anchors[i].curvePoints[1],
464
+ ]);
465
+ }
466
+ let len = anchors[i].curvePoints.length - 1;
467
+ for (let j = 1; j < len; j++) {
468
+ drawLinearGradientLine(ctx, pen, [
469
+ anchors[i].curvePoints[j],
470
+ anchors[i].curvePoints[j + 1],
471
+ ]);
472
+ }
473
+ let last = getSmoothAdjacent(
474
+ smoothLenth,
475
+ anchors[i + 1],
476
+ anchors[i].curvePoints[len]
477
+ );
478
+ drawLinearGradientLine(ctx, pen, [anchors[i].curvePoints[len], last]);
479
+ } else {
480
+ let _next = anchors[i];
481
+ let _last = anchors[i + 1];
482
+ if (i > 0 && i < anchors.length - 1) {
483
+ //有突兀的地方
484
+ let lastCurvePoints = anchors[i - 1].curvePoints;
485
+ if (lastCurvePoints) {
486
+ smoothTransition(
487
+ ctx,
488
+ pen,
489
+ smoothLenth,
490
+ lastCurvePoints[lastCurvePoints.length - 1],
491
+ anchors[i],
492
+ anchors[i + 1]
493
+ );
494
+ } else {
495
+ smoothTransition(
496
+ ctx,
497
+ pen,
498
+ smoothLenth,
499
+ anchors[i - 1],
500
+ anchors[i],
501
+ anchors[i + 1]
502
+ );
503
+ }
504
+ }
505
+ if (i > 0 && i < anchors.length - 1) {
506
+ _next = getSmoothAdjacent(smoothLenth, anchors[i], anchors[i + 1]);
507
+ }
508
+ if (i < anchors.length - 2) {
509
+ _last = getSmoothAdjacent(smoothLenth, anchors[i + 1], anchors[i]);
510
+ }
511
+ let flag = false;
512
+ if(i === 0){
513
+ if( pen.fromLineCap && pen.fromLineCap !== 'butt'){
514
+ ctx.save();
515
+ flag = true;
516
+ ctx.lineCap = pen.fromLineCap;
517
+ }
518
+ }
519
+ if(i !== 0 && i === anchors.length - 2){
520
+ if( pen.toLineCap && pen.toLineCap !== 'butt'){
521
+ ctx.save();
522
+ flag = true;
523
+ ctx.lineCap = pen.toLineCap;
524
+ }
525
+ }
526
+
527
+ drawLinearGradientLine(ctx, pen, [_next, _last]);
528
+ if(flag){
529
+ ctx.restore();
530
+ }
531
+ if(anchors.length===2&&i===0){
532
+ ctx.save();
533
+ flag = true;
534
+ ctx.lineCap = pen.toLineCap;
535
+ let _y = 0.1;
536
+ let _x = 0.1;
537
+ if(_next.x-_last.x===0){
538
+ _x =0
539
+ }else{
540
+ _y = (_next.y-_last.y)/(_next.x-_last.x)*0.1;
541
+ }
542
+ drawLinearGradientLine(ctx, pen, [{x:_last.x-_x,y:_last.y-_y}, _last]);
543
+ ctx.restore();
544
+ }
545
+ }
546
+ }
547
+ }
548
+
549
+ function getSmoothAdjacent(smoothLenth: number, p1: Point, p2: Point) {
550
+ let nexLength = Math.sqrt(
551
+ (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
552
+ );
553
+ if (nexLength === 0) {
554
+ return {
555
+ x: p1.x,
556
+ y: p1.y,
557
+ };
558
+ }
559
+ if (smoothLenth < nexLength) {
560
+ return {
561
+ x: p1.x + ((p2.x - p1.x) * smoothLenth) / nexLength,
562
+ y: p1.y + ((p2.y - p1.y) * smoothLenth) / nexLength,
563
+ };
564
+ } else {
565
+ return {
566
+ x: p1.x + (p2.x - p1.x) / nexLength / 2,
567
+ y: p1.y + (p2.y - p1.y) / nexLength / 2,
568
+ };
569
+ }
570
+ }
571
+
572
+ function smoothTransition(
573
+ ctx: CanvasRenderingContext2D,
574
+ pen: Pen,
575
+ smoothLenth: number,
576
+ p1: Point,
577
+ p2: Point,
578
+ p3: Point
579
+ ) {
580
+ let last = getSmoothAdjacent(smoothLenth, p2, p1);
581
+ let next = getSmoothAdjacent(smoothLenth, p2, p3);
582
+ let contrlPoint = { x: p2.x, y: p2.y };
583
+
584
+ let points = getBezierPoints(pen.calculative.canvas.store.data.smoothNum || 20, last, contrlPoint, next);
585
+ for (let k = 0; k < points.length - 1; k++) {
586
+ drawLinearGradientLine(ctx, pen, [
587
+ {
588
+ x: points[k].x,
589
+ y: points[k].y,
590
+ },
591
+ {
592
+ x: points[k + 1].x,
593
+ y: points[k + 1].y,
594
+ },
595
+ ]);
596
+ }
597
+ }
598
+
599
+ function smoothAnimateTransition(
600
+ ctx: Path2D,
601
+ smoothLenth: number,
602
+ p2: Point,
603
+ p3: Point
604
+ ) {
605
+ let next = getSmoothAdjacent(smoothLenth, p2, p3);
606
+ let contrlPoint = { x: p2.x, y: p2.y };
607
+
608
+ ctx.quadraticCurveTo(contrlPoint.x, contrlPoint.y, next.x, next.y);
609
+ }
610
+
611
+ export function getGradientAnimatePath(pen: Pen) {
612
+ const anchors = pen.calculative.worldAnchors;
613
+ let smoothLenth =
614
+ pen.calculative.lineWidth * (pen.calculative.gradientSmooth || pen.calculative.lineSmooth || 0);
615
+ //只创建一次
616
+ const _path = new Path2D();
617
+ for (let i = 0; i < anchors.length - 1; i++) {
618
+ let _next = anchors[i];
619
+ let _last = anchors[i + 1];
620
+ if (i == 0) {
621
+ _path.moveTo(anchors[i].x, anchors[i].y);
622
+ }
623
+ if (i > 0 && i < anchors.length - 1) {
624
+ //有突兀的地方
625
+ let lastCurvePoints = anchors[i - 1].curvePoints;
626
+ // const path = new Path2D();
627
+ if (lastCurvePoints) {
628
+ smoothAnimateTransition(_path, smoothLenth, anchors[i], anchors[i + 1]);
629
+ } else {
630
+ smoothAnimateTransition(_path, smoothLenth, anchors[i], anchors[i + 1]);
631
+ }
632
+ }
633
+ if (i > 0 && i < anchors.length - 1) {
634
+ _next = getSmoothAdjacent(smoothLenth, anchors[i], anchors[i + 1]);
635
+ }
636
+ if (i < anchors.length - 2) {
637
+ _last = getSmoothAdjacent(smoothLenth, anchors[i + 1], anchors[i]);
638
+ }
639
+ _path.lineTo(_last.x, _last.y);
640
+ }
641
+
642
+ return _path;
643
+ }
644
+
645
+ function getAngle(p1: Point, p2: Point, p3: Point) {
646
+ let a = { x: 0, y: 0 },
647
+ b = { x: 0, y: 0 };
648
+ a.x = p1.x - p2.x;
649
+ a.y = p1.y - p2.y;
650
+ b.x = p3.x - p2.x;
651
+ b.y = p3.y - p2.y;
652
+
653
+ return (
654
+ (Math.acos(
655
+ (a.x * b.x + a.y * b.y) /
656
+ (Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y))
657
+ ) /
658
+ Math.PI) *
659
+ 180
660
+ );
661
+ }
662
+
663
+ function getBezierPoints(
664
+ num = 100,
665
+ p1?: Point,
666
+ p2?: Point,
667
+ p3?: Point,
668
+ p4?: Point
669
+ ) {
670
+ let func = null;
671
+ const points = [];
672
+ if (!p3 && !p4) {
673
+ func = oneBezier;
674
+ } else if (p3 && !p4) {
675
+ func = twoBezier;
676
+ } else if (p3 && p4) {
677
+ func = threeBezier;
678
+ }
679
+ for (let i = 0; i < num; i++) {
680
+ points.push(func(i / num, p1, p2, p3, p4));
681
+ }
682
+ if (p4) {
683
+ points.push(p4);
684
+ } else if (p3) {
685
+ points.push(p3);
686
+ }
687
+ return points;
688
+ }
689
+
690
+ /**
691
+ * @desc 一阶贝塞尔
692
+ * @param t 当前百分比
693
+ * @param p1 起点坐标
694
+ * @param p2 终点坐标
695
+ */
696
+ function oneBezier(t: number, p1: Point, p2: Point) {
697
+ const { x: x1, y: y1 } = p1;
698
+ const { x: x2, y: y2 } = p2;
699
+ let x = x1 + (x2 - x1) * t;
700
+ let y = y1 + (y2 - y1) * t;
701
+ return { x, y };
702
+ }
703
+
704
+ /**
705
+ * @desc 二阶贝塞尔
706
+ * @param t 当前百分比
707
+ * @param p1 起点坐标
708
+ * @param p2 终点坐标
709
+ * @param cp 控制点
710
+ */
711
+ function twoBezier(t: number, p1: Point, cp: Point, p2: Point) {
712
+ const { x: x1, y: y1 } = p1;
713
+ const { x: cx, y: cy } = cp;
714
+ const { x: x2, y: y2 } = p2;
715
+ let x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;
716
+ let y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;
717
+ return { x, y };
718
+ }
719
+
720
+ /**
721
+ * @desc 三阶贝塞尔
722
+ * @param t 当前百分比
723
+ * @param p1 起点坐标
724
+ * @param p2 终点坐标
725
+ * @param cp1 控制点1
726
+ * @param cp2 控制点2
727
+ */
728
+ function threeBezier(t: number, p1: Point, cp1: Point, cp2: Point, p2: Point) {
729
+ const { x: x1, y: y1 } = p1;
730
+ const { x: x2, y: y2 } = p2;
731
+ const { x: cx1, y: cy1 } = cp1;
732
+ const { x: cx2, y: cy2 } = cp2;
733
+ let x =
734
+ x1 * (1 - t) * (1 - t) * (1 - t) +
735
+ 3 * cx1 * t * (1 - t) * (1 - t) +
736
+ 3 * cx2 * t * t * (1 - t) +
737
+ x2 * t * t * t;
738
+ let y =
739
+ y1 * (1 - t) * (1 - t) * (1 - t) +
740
+ 3 * cy1 * t * (1 - t) * (1 - t) +
741
+ 3 * cy2 * t * t * (1 - t) +
742
+ y2 * t * t * t;
743
+ return { x, y };
744
+ }
745
+
746
+ function strokeLinearGradient(ctx: CanvasRenderingContext2D, pen: Pen) {
747
+ const {
748
+ worldRect,
749
+ lineGradientFromColor,
750
+ lineGradientToColor,
751
+ lineGradientAngle,
752
+ } = pen.calculative;
753
+ return linearGradient(
754
+ ctx,
755
+ worldRect,
756
+ lineGradientFromColor,
757
+ lineGradientToColor,
758
+ lineGradientAngle
759
+ );
760
+ }
761
+
762
+ /**
763
+ * 避免副作用,把创建好后的线性渐变对象返回出来
764
+ * @param ctx 画布绘制对象
765
+ * @param worldRect 世界坐标
766
+ * @returns 线性渐变
767
+ */
768
+ function linearGradient(
769
+ ctx: CanvasRenderingContext2D,
770
+ worldRect: Rect,
771
+ fromColor: string,
772
+ toColor: string,
773
+ angle: number
774
+ ) {
775
+ if (!fromColor || !toColor) {
776
+ return;
777
+ }
778
+
779
+ const { x, y, center, ex, ey } = worldRect;
780
+ const from: Point = {
781
+ x,
782
+ y: center.y,
783
+ };
784
+ const to: Point = {
785
+ x: ex,
786
+ y: center.y,
787
+ };
788
+ if (angle % 90 === 0 && angle % 180) {
789
+ from.x = center.x;
790
+ to.x = center.x;
791
+ if (angle % 270) {
792
+ from.y = y;
793
+ to.y = ey;
794
+ } else {
795
+ from.y = ey;
796
+ to.y = y;
797
+ }
798
+ } else if (angle) {
799
+ rotatePoint(from, angle, worldRect.center);
800
+ rotatePoint(to, angle, worldRect.center);
801
+ }
802
+
803
+ // contributor: https://github.com/sunnyguohua/meta2d
804
+ const grd = ctx.createLinearGradient(from.x, from.y, to.x, to.y);
805
+ grd.addColorStop(0, fromColor);
806
+ grd.addColorStop(1, toColor);
807
+ return grd;
808
+ }
809
+
810
+ /**
811
+ * 根据图片的宽高, imageRatio iconAlign 来获取图片的实际位置
812
+ * @param pen 画笔
813
+ */
814
+ function getImagePosition(pen: Pen) {
815
+ const {
816
+ worldIconRect: rect,
817
+ iconWidth,
818
+ iconHeight,
819
+ imgNaturalWidth,
820
+ imgNaturalHeight,
821
+ } = pen.calculative;
822
+ let { x, y, width: w, height: h } = rect;
823
+ if (iconWidth) {
824
+ w = iconWidth;
825
+ }
826
+ if (iconHeight) {
827
+ h = iconHeight;
828
+ }
829
+ if (imgNaturalWidth && imgNaturalHeight && pen.imageRatio) {
830
+ const scaleW = rect.width / imgNaturalWidth;
831
+ const scaleH = rect.height / imgNaturalHeight;
832
+ const scaleMin = Math.min(scaleW, scaleH);
833
+ const wDivideH = imgNaturalWidth / imgNaturalHeight;
834
+ if (iconWidth) {
835
+ h = iconWidth / wDivideH;
836
+ } else if (iconHeight) {
837
+ w = iconHeight * wDivideH;
838
+ } else {
839
+ w = scaleMin * imgNaturalWidth;
840
+ h = scaleMin * imgNaturalHeight;
841
+ }
842
+ }
843
+ x += (rect.width - w) / 2;
844
+ y += (rect.height - h) / 2;
845
+
846
+ switch (pen.iconAlign) {
847
+ case 'top':
848
+ y = rect.y;
849
+ break;
850
+ case 'bottom':
851
+ y = rect.ey - h;
852
+ break;
853
+ case 'left':
854
+ x = rect.x;
855
+ break;
856
+ case 'right':
857
+ x = rect.ex - w;
858
+ break;
859
+ case 'left-top':
860
+ x = rect.x;
861
+ y = rect.y;
862
+ break;
863
+ case 'right-top':
864
+ x = rect.ex - w;
865
+ y = rect.y;
866
+ break;
867
+ case 'left-bottom':
868
+ x = rect.x;
869
+ y = rect.ey - h;
870
+ break;
871
+ case 'right-bottom':
872
+ x = rect.ex - w;
873
+ y = rect.ey - h;
874
+ break;
875
+ }
876
+
877
+ return {
878
+ x,
879
+ y,
880
+ width: w,
881
+ height: h,
882
+ };
883
+ }
884
+
885
+ export function drawImage(
886
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
887
+ pen: Pen
888
+ ) {
889
+ const { x, y, width, height } = getImagePosition(pen);
890
+ const { worldIconRect, iconRotate, img } = pen.calculative;
891
+
892
+ if (iconRotate) {
893
+ const { x: centerX, y: centerY } = worldIconRect.center;
894
+ ctx.translate(centerX, centerY);
895
+ ctx.rotate((iconRotate * Math.PI) / 180);
896
+ ctx.translate(-centerX, -centerY);
897
+ }
898
+ if (pen.imageRadius) {
899
+ ctx.save();
900
+ let wr = pen.calculative.imageRadius || 0,
901
+ hr = wr;
902
+ const {
903
+ x: _x,
904
+ y: _y,
905
+ width: w,
906
+ height: h,
907
+ ex,
908
+ ey,
909
+ } = pen.calculative.worldRect;
910
+ if (wr < 1) {
911
+ wr = w * wr;
912
+ hr = h * hr;
913
+ }
914
+ let r = wr < hr ? wr : hr;
915
+ if (w < 2 * r) {
916
+ r = w / 2;
917
+ }
918
+ if (h < 2 * r) {
919
+ r = h / 2;
920
+ }
921
+ ctx.beginPath();
922
+
923
+ ctx.moveTo(_x + r, _y);
924
+ ctx.arcTo(ex, _y, ex, ey, r);
925
+ ctx.arcTo(ex, ey, _x, ey, r);
926
+ ctx.arcTo(_x, ey, _x, _y, r);
927
+ ctx.arcTo(_x, _y, ex, _y, r);
928
+ ctx.clip();
929
+ ctx.drawImage(img, x, y, width, height);
930
+ ctx.restore();
931
+ } else {
932
+ ctx.drawImage(img, x, y, width, height);
933
+ }
934
+ }
935
+
936
+ /**
937
+ * 获取文字颜色, textColor 优先其次 color
938
+ */
939
+ export function getTextColor(pen: Pen, store: Meta2dStore) {
940
+ const { textColor, color } = pen.calculative;
941
+ const { data, options } = store;
942
+ return (
943
+ textColor ||
944
+ color ||
945
+ data.textColor ||
946
+ data.color ||
947
+ options.textColor ||
948
+ options.color
949
+ );
950
+ }
951
+
952
+ function drawText(ctx: CanvasRenderingContext2D, pen: Pen) {
953
+ const {
954
+ fontStyle,
955
+ fontWeight,
956
+ fontSize,
957
+ fontFamily,
958
+ lineHeight,
959
+ text,
960
+ hiddenText,
961
+ canvas,
962
+ textHasShadow,
963
+ textBackground,
964
+ textType,
965
+ } = pen.calculative;
966
+ if (text == undefined || hiddenText) {
967
+ return;
968
+ }
969
+ const store = canvas.store;
970
+ ctx.save();
971
+ if (!textHasShadow) {
972
+ ctx.shadowBlur = 0;
973
+ ctx.shadowOffsetX = 0;
974
+ ctx.shadowOffsetY = 0;
975
+ }
976
+ let fill: string = undefined;
977
+ if(pen.calculative.disabled){
978
+ fill = pen.disabledTextColor || pen.disabledColor || pSBC(0.4, getTextColor(pen, store));
979
+ }else if (pen.calculative.hover) {
980
+ fill = pen.hoverTextColor || pen.hoverColor || store.options.hoverColor;
981
+ } else if (pen.calculative.active) {
982
+ fill = pen.activeTextColor || pen.activeColor || store.options.activeColor;
983
+ }
984
+ let gradient = undefined;
985
+ if (textType === Gradient.Linear) {
986
+ gradient = getTextGradient(ctx, pen);
987
+ } else if (textType === Gradient.Radial) {
988
+ gradient = getTextRadialGradient(ctx, pen);
989
+ }
990
+ ctx.fillStyle = fill || gradient || getTextColor(pen, store);
991
+ ctx.font = getFont({
992
+ fontStyle,
993
+ fontWeight,
994
+ fontFamily: fontFamily || store.options.fontFamily,
995
+ fontSize,
996
+ lineHeight,
997
+ });
998
+
999
+ !pen.calculative.textDrawRect && calcTextDrawRect(ctx, pen);
1000
+ const {
1001
+ x: drawRectX,
1002
+ y: drawRectY,
1003
+ width,
1004
+ height,
1005
+ } = pen.calculative.textDrawRect;
1006
+ if (textBackground) {
1007
+ ctx.save();
1008
+ ctx.fillStyle = textBackground;
1009
+ ctx.fillRect(drawRectX, drawRectY, width, height);
1010
+ ctx.restore();
1011
+ }
1012
+
1013
+ const y = 0.55;
1014
+ const textAlign = pen.textAlign || store.options.textAlign;
1015
+ const oneRowHeight = fontSize * lineHeight;
1016
+ pen.calculative.textLines.forEach((text, i) => {
1017
+ const textLineWidth = pen.calculative.textLineWidths[i];
1018
+ let x = 0;
1019
+ if (textAlign === 'center') {
1020
+ x = (width - textLineWidth) / 2;
1021
+ } else if (textAlign === 'right') {
1022
+ x = width - textLineWidth;
1023
+ }
1024
+ // 下划线
1025
+ ctx.fillText(text, drawRectX + x, drawRectY + (i + y) * oneRowHeight);
1026
+ const { textDecorationColor, textDecorationDash, textDecoration } = pen;
1027
+ if (textDecoration) {
1028
+ drawUnderLine(
1029
+ ctx,
1030
+ {
1031
+ x: drawRectX + x,
1032
+ y: drawRectY + (i + y) * oneRowHeight,
1033
+ width: textLineWidth,
1034
+ },
1035
+ { textDecorationColor, textDecorationDash, fontSize }
1036
+ );
1037
+ }
1038
+ // 删除线
1039
+ const { textStrickoutColor, textStrickoutDash, textStrickout } = pen;
1040
+ if (textStrickout) {
1041
+ drawStrickout(
1042
+ ctx,
1043
+ {
1044
+ x: drawRectX + x,
1045
+ y: drawRectY + (i + y) * oneRowHeight,
1046
+ width: textLineWidth,
1047
+ },
1048
+ { textStrickoutColor, textStrickoutDash, fontSize }
1049
+ );
1050
+ }
1051
+ });
1052
+ ctx.restore();
1053
+ }
1054
+ function drawUnderLine(
1055
+ ctx: CanvasRenderingContext2D,
1056
+ location: any,
1057
+ config: any
1058
+ ) {
1059
+ const { textDecorationColor, textDecorationDash, fontSize } = config;
1060
+ let { x, y, width } = location;
1061
+ switch (ctx.textBaseline) {
1062
+ case 'top':
1063
+ y += fontSize;
1064
+ break;
1065
+ case 'middle':
1066
+ y += fontSize / 2;
1067
+ break;
1068
+ }
1069
+ ctx.save();
1070
+ ctx.beginPath();
1071
+ ctx.strokeStyle = textDecorationColor ? textDecorationColor : ctx.fillStyle;
1072
+ ctx.lineWidth = 1;
1073
+ ctx.moveTo(x, y);
1074
+ ctx.setLineDash(textDecorationDash || []);
1075
+ ctx.lineTo(x + width, y);
1076
+ ctx.stroke();
1077
+ ctx.restore();
1078
+ }
1079
+ function drawStrickout(
1080
+ ctx: CanvasRenderingContext2D,
1081
+ location: any,
1082
+ config: any
1083
+ ) {
1084
+ const { textStrickoutColor, textStrickoutDash, fontSize } = config;
1085
+ let { x, y, width } = location;
1086
+ switch (ctx.textBaseline) {
1087
+ case 'top':
1088
+ y += fontSize / 2;
1089
+ break;
1090
+ case 'bottom':
1091
+ y -= fontSize / 2;
1092
+ break;
1093
+ }
1094
+ ctx.save();
1095
+ ctx.beginPath();
1096
+ ctx.strokeStyle = textStrickoutColor ? textStrickoutColor : ctx.fillStyle;
1097
+ ctx.lineWidth = 1;
1098
+ ctx.moveTo(x, y);
1099
+ ctx.setLineDash(textStrickoutDash || []);
1100
+ ctx.lineTo(x + width, y);
1101
+ ctx.stroke();
1102
+ ctx.restore();
1103
+ }
1104
+ function drawFillText(ctx: CanvasRenderingContext2D, pen: Pen, text: string) {
1105
+ if (text == undefined) {
1106
+ return;
1107
+ }
1108
+
1109
+ const { fontStyle, fontWeight, fontSize, fontFamily, lineHeight, canvas } =
1110
+ pen.calculative;
1111
+
1112
+ const store = canvas.store;
1113
+ ctx.save();
1114
+
1115
+ let fill: string = undefined;
1116
+ if (pen.calculative.hover) {
1117
+ fill = pen.hoverTextColor || pen.hoverColor || store.options.hoverColor;
1118
+ } else if (pen.calculative.active) {
1119
+ fill = pen.activeTextColor || pen.activeColor || store.options.activeColor;
1120
+ }
1121
+ ctx.fillStyle = fill || getTextColor(pen, store);
1122
+
1123
+ ctx.font = getFont({
1124
+ fontStyle,
1125
+ fontWeight,
1126
+ fontFamily: fontFamily || store.options.fontFamily,
1127
+ fontSize,
1128
+ lineHeight,
1129
+ });
1130
+
1131
+ const w = ctx.measureText(text).width;
1132
+ let t: string;
1133
+
1134
+ let prev: Point;
1135
+ for (const anchor of pen.calculative.worldAnchors) {
1136
+ if (!prev) {
1137
+ prev = anchor;
1138
+ continue;
1139
+ }
1140
+
1141
+ const dis = distance(prev, anchor);
1142
+
1143
+ const n = Math.floor(dis / w);
1144
+ t = '';
1145
+ for (let i = 0; i < n; i++) {
1146
+ t += text;
1147
+ }
1148
+
1149
+ const angle = calcRotate(prev, anchor) - 270;
1150
+ ctx.save();
1151
+ if (angle % 360 !== 0) {
1152
+ const { x, y } = prev;
1153
+ ctx.translate(x, y);
1154
+ let rotate = (angle * Math.PI) / 180;
1155
+ ctx.rotate(rotate);
1156
+ ctx.translate(-x, -y);
1157
+ }
1158
+ ctx.fillText(t, prev.x, prev.y + lineHeight / 2);
1159
+ ctx.restore();
1160
+ prev = anchor;
1161
+ }
1162
+
1163
+ ctx.restore();
1164
+ }
1165
+
1166
+ export function drawIcon(
1167
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1168
+ pen: Pen
1169
+ ) {
1170
+ const store = pen.calculative.canvas.store;
1171
+ ctx.save();
1172
+ ctx.shadowColor = '';
1173
+ ctx.shadowBlur = 0;
1174
+ ctx.shadowOffsetX = 0;
1175
+ ctx.shadowOffsetY = 0;
1176
+ ctx.textAlign = 'center';
1177
+ ctx.textBaseline = 'middle';
1178
+ const iconRect = pen.calculative.worldIconRect;
1179
+ let x = iconRect.x + iconRect.width / 2;
1180
+ let y = iconRect.y + iconRect.height / 2;
1181
+
1182
+ switch (pen.iconAlign) {
1183
+ case 'top':
1184
+ y = iconRect.y;
1185
+ ctx.textBaseline = 'top';
1186
+ break;
1187
+ case 'bottom':
1188
+ y = iconRect.ey;
1189
+ ctx.textBaseline = 'bottom';
1190
+ break;
1191
+ case 'left':
1192
+ x = iconRect.x;
1193
+ ctx.textAlign = 'left';
1194
+ break;
1195
+ case 'right':
1196
+ x = iconRect.ex;
1197
+ ctx.textAlign = 'right';
1198
+ break;
1199
+ case 'left-top':
1200
+ x = iconRect.x;
1201
+ y = iconRect.y;
1202
+ ctx.textAlign = 'left';
1203
+ ctx.textBaseline = 'top';
1204
+ break;
1205
+ case 'right-top':
1206
+ x = iconRect.ex;
1207
+ y = iconRect.y;
1208
+ ctx.textAlign = 'right';
1209
+ ctx.textBaseline = 'top';
1210
+ break;
1211
+ case 'left-bottom':
1212
+ x = iconRect.x;
1213
+ y = iconRect.ey;
1214
+ ctx.textAlign = 'left';
1215
+ ctx.textBaseline = 'bottom';
1216
+ break;
1217
+ case 'right-bottom':
1218
+ x = iconRect.ex;
1219
+ y = iconRect.ey;
1220
+ ctx.textAlign = 'right';
1221
+ ctx.textBaseline = 'bottom';
1222
+ break;
1223
+ }
1224
+
1225
+ const fontWeight = pen.calculative.iconWeight;
1226
+ let fontSize: number = undefined;
1227
+ const fontFamily = pen.calculative.iconFamily;
1228
+ if (pen.calculative.iconSize > 0) {
1229
+ fontSize = pen.calculative.iconSize;
1230
+ } else if (iconRect.width > iconRect.height) {
1231
+ fontSize = iconRect.height;
1232
+ } else {
1233
+ fontSize = iconRect.width;
1234
+ }
1235
+ ctx.font = getFont({
1236
+ fontSize,
1237
+ fontWeight,
1238
+ fontFamily,
1239
+ });
1240
+ ctx.fillStyle = pen.calculative.iconColor || getTextColor(pen, store);
1241
+
1242
+ if (pen.calculative.iconRotate) {
1243
+ ctx.translate(iconRect.center.x, iconRect.center.y);
1244
+ ctx.rotate((pen.calculative.iconRotate * Math.PI) / 180);
1245
+ ctx.translate(-iconRect.center.x, -iconRect.center.y);
1246
+ }
1247
+
1248
+ ctx.beginPath();
1249
+ ctx.fillText(pen.calculative.icon, x, y);
1250
+ ctx.restore();
1251
+ }
1252
+
1253
+ /**
1254
+ * canvas2svg 中对 font 的解析规则比 canvas 中简单,能识别的类型很少
1255
+ * @returns ctx.font
1256
+ */
1257
+ export function getFont({
1258
+ fontStyle = 'normal',
1259
+ textDecoration = 'normal',
1260
+ fontWeight = 'normal',
1261
+ fontSize = 12,
1262
+ fontFamily = 'Arial',
1263
+ lineHeight = 1, // TODO: lineHeight 默认值待测试
1264
+ }: {
1265
+ fontStyle?: string;
1266
+ textDecoration?: string;
1267
+ fontWeight?: string;
1268
+ fontSize?: number;
1269
+ fontFamily?: string;
1270
+ lineHeight?: number;
1271
+ } = {}) {
1272
+ return `${fontStyle} ${textDecoration} ${fontWeight} ${fontSize}px/${lineHeight} ${fontFamily}`;
1273
+ }
1274
+
1275
+ // TODO: 0.5 偏移量在 图片中可能存在问题
1276
+ export function ctxFlip(
1277
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1278
+ pen: Pen
1279
+ ) {
1280
+ // worldRect 可能为 undefined
1281
+ const { x, ex, y, ey } = pen.calculative.worldRect || {};
1282
+ if (pen.calculative.flipX) {
1283
+ ctx.translate(x + ex + 0.5, 0.5);
1284
+ ctx.scale(-1, 1);
1285
+ }
1286
+ if (pen.calculative.flipY) {
1287
+ ctx.translate(0.5, y + ey + 0.5);
1288
+ ctx.scale(1, -1);
1289
+ }
1290
+ }
1291
+
1292
+ export function ctxRotate(
1293
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1294
+ pen: Pen,
1295
+ noFlip: boolean = false
1296
+ ) {
1297
+ const { x, y } = pen.calculative.worldRect.pivot || pen.calculative.worldRect.center;
1298
+ ctx.translate(x, y);
1299
+ let rotate = (pen.calculative.rotate * Math.PI) / 180;
1300
+ // 目前只有水平和垂直翻转,都需要 * -1
1301
+ if (!noFlip) {
1302
+ if (pen.calculative.flipX) {
1303
+ rotate *= -1;
1304
+ }
1305
+ if (pen.calculative.flipY) {
1306
+ rotate *= -1;
1307
+ }
1308
+ }
1309
+ ctx.rotate(rotate);
1310
+ ctx.translate(-x, -y);
1311
+ }
1312
+
1313
+ export function renderPen(
1314
+ ctx: CanvasRenderingContext2D,
1315
+ pen: Pen,
1316
+ download?: boolean
1317
+ ) {
1318
+ ctx.save();
1319
+ ctx.translate(0.5, 0.5);
1320
+ ctx.beginPath();
1321
+ const store = pen.calculative.canvas.store;
1322
+ const textFlip = pen.textFlip || store.options.textFlip;
1323
+ const textRotate = pen.textRotate || store.options.textRotate;
1324
+ if (!textFlip || !textRotate) {
1325
+ ctx.save();
1326
+ }
1327
+ ctxFlip(ctx, pen);
1328
+
1329
+ if (pen.calculative.rotate && pen.name !== 'line') {
1330
+ ctxRotate(ctx, pen);
1331
+ }
1332
+ if (pen.calculative.lineWidth > 1 || download) {
1333
+ ctx.lineWidth = pen.calculative.lineWidth;
1334
+ }
1335
+
1336
+ inspectRect(ctx, store, pen); // 审查 rect
1337
+ let fill: any;
1338
+ // 该变量控制在 hover active 状态下的节点是否设置填充颜色
1339
+ // let setBack = true;
1340
+ let lineGradientFlag = false;
1341
+ let _stroke = undefined;
1342
+ if(pen.calculative.disabled){
1343
+ _stroke = pen.disabledColor || store.options.disabledColor || pSBC(0.4, pen.calculative.color || getGlobalColor(store));
1344
+ fill = pen.disabledBackground || store.options.disabledBackground || pSBC(0.4, pen.calculative.background || store.data.penBackground);
1345
+ } else if (pen.mouseDownValid && pen.calculative.mouseDown){
1346
+ _stroke = pen.mouseDownColor || pSBC(-0.4, pen.calculative.color || getGlobalColor(store));
1347
+ fill = pen.mouseDownBackground || pSBC(-0.4, pen.calculative.background || store.data.penBackground);
1348
+ } else if (pen.calculative.hover) {
1349
+ _stroke = pen.hoverColor || store.options.hoverColor;
1350
+ fill = pen.hoverBackground || store.options.hoverBackground;
1351
+ // ctx.fillStyle = fill;
1352
+ // fill && (setBack = false);
1353
+ } else if (pen.calculative.active) {
1354
+ _stroke = pen.activeColor || store.options.activeColor;
1355
+ fill = pen.activeBackground || store.options.activeBackground;
1356
+ // ctx.fillStyle = fill;
1357
+ // fill && (setBack = false);
1358
+ } else if (pen.calculative.isDock) {
1359
+ if (pen.type === PenType.Line) {
1360
+ _stroke = store.options.dockPenColor;
1361
+ } else {
1362
+ fill = rgba(store.options.dockPenColor, 0.2);
1363
+ // ctx.fillStyle = fill;
1364
+ // fill && (setBack = false);
1365
+ }
1366
+ }
1367
+ // else {
1368
+ const strokeImg = pen.calculative.strokeImg;
1369
+ if (pen.calculative.strokeImage && strokeImg) {
1370
+ ctx.strokeStyle = _stroke || ctx.createPattern(strokeImg, 'repeat');
1371
+ // fill = true;
1372
+ } else {
1373
+ let stroke: string | CanvasGradient | CanvasPattern;
1374
+ // TODO: 线只有线性渐变
1375
+ if (pen.calculative.strokeType) {
1376
+ if (pen.calculative.lineGradientColors) {
1377
+ if (pen.name === 'line') {
1378
+ lineGradientFlag = true;
1379
+ } else {
1380
+ if (pen.calculative.lineGradient) {
1381
+ stroke = pen.calculative.lineGradient;
1382
+ } else {
1383
+ stroke = getLineGradient(ctx, pen);
1384
+ pen.calculative.lineGradient = stroke;
1385
+ }
1386
+ }
1387
+ } else {
1388
+ stroke = strokeLinearGradient(ctx, pen);
1389
+ }
1390
+ } else {
1391
+ stroke = pen.calculative.color || getGlobalColor(store);
1392
+ }
1393
+ ctx.strokeStyle = _stroke || stroke;
1394
+ }
1395
+ // }
1396
+ //if (setBack) {
1397
+ const backgroundImg = pen.calculative.backgroundImg;
1398
+ if (pen.calculative.backgroundImage && backgroundImg) {
1399
+ ctx.fillStyle = fill || ctx.createPattern(backgroundImg, 'repeat');
1400
+ fill = true;
1401
+ } else {
1402
+ let back: string | CanvasGradient | CanvasPattern;
1403
+ if (pen.calculative.bkType === Gradient.Linear) {
1404
+ if (pen.calculative.gradientColors) {
1405
+ // if (!pen.type) {
1406
+ //连线不考虑渐进背景
1407
+ if (pen.calculative.gradient) {
1408
+ //位置变化/放大缩小操作不会触发重新计算
1409
+ back = pen.calculative.gradient;
1410
+ } else {
1411
+ back = getBkGradient(ctx, pen);
1412
+ pen.calculative.gradient = back;
1413
+ }
1414
+ // }
1415
+ } else {
1416
+ back = drawBkLinearGradient(ctx, pen);
1417
+ }
1418
+ } else if (pen.calculative.bkType === Gradient.Radial) {
1419
+ if (pen.calculative.gradientColors) {
1420
+ if (pen.calculative.radialGradient) {
1421
+ back = pen.calculative.radialGradient;
1422
+ } else {
1423
+ back = getBkRadialGradient(ctx, pen);
1424
+ pen.calculative.radialGradient = back;
1425
+ }
1426
+ } else {
1427
+ back = drawBkRadialGradient(ctx, pen);
1428
+ }
1429
+ } else {
1430
+ back = pen.calculative.background || store.data.penBackground;
1431
+ }
1432
+ ctx.fillStyle = fill || back;
1433
+ fill = !!back;
1434
+ }
1435
+ // }
1436
+
1437
+ setLineCap(ctx, pen);
1438
+ setLineJoin(ctx, pen);
1439
+
1440
+ setGlobalAlpha(ctx, pen);
1441
+
1442
+ if (pen.calculative.lineDash) {
1443
+ ctx.setLineDash(
1444
+ pen.calculative.lineDash.map(
1445
+ (item) => item * pen.calculative.canvas.store.data.scale
1446
+ )
1447
+ );
1448
+ }
1449
+ if (pen.calculative.lineDashOffset) {
1450
+ ctx.lineDashOffset = pen.calculative.lineDashOffset;
1451
+ }
1452
+
1453
+ if (pen.calculative.shadowColor) {
1454
+ ctx.shadowColor = pen.calculative.shadowColor;
1455
+ ctx.shadowOffsetX = pen.calculative.shadowOffsetX;
1456
+ ctx.shadowOffsetY = pen.calculative.shadowOffsetY;
1457
+ ctx.shadowBlur = pen.calculative.shadowBlur;
1458
+ }
1459
+ if (lineGradientFlag) {
1460
+ ctxDrawLinearGradientPath(ctx, pen);
1461
+ ctxDrawLinePath(true, ctx, pen, store);
1462
+ } else {
1463
+ ctxDrawPath(true, ctx, pen, store, fill);
1464
+
1465
+ ctxDrawCanvas(ctx, pen);
1466
+ }
1467
+ if (!(pen.image && pen.calculative.img) && pen.calculative.icon) {
1468
+ drawIcon(ctx, pen);
1469
+ }
1470
+
1471
+ if (!textFlip || !textRotate) {
1472
+ ctx.restore();
1473
+ }
1474
+ if (textFlip && !textRotate) {
1475
+ ctxFlip(ctx, pen);
1476
+ }
1477
+ if (!textFlip && textRotate) {
1478
+ if (pen.calculative.rotate && pen.name !== 'line') {
1479
+ ctxRotate(ctx, pen, true);
1480
+ }
1481
+ }
1482
+
1483
+ drawText(ctx, pen);
1484
+ if (pen.type === PenType.Line && pen.fillTexts) {
1485
+ for (const text of pen.fillTexts) {
1486
+ drawFillText(ctx, pen, text);
1487
+ }
1488
+ }
1489
+
1490
+ ctx.restore();
1491
+ }
1492
+
1493
+ /**
1494
+ * 更改 ctx 的 lineCap 属性
1495
+ */
1496
+ export function setLineCap(ctx: CanvasRenderingContext2D, pen: Pen) {
1497
+ const lineCap = pen.lineCap || (pen.type ? 'round' : 'square');
1498
+ if (lineCap) {
1499
+ ctx.lineCap = lineCap;
1500
+ } else if (pen.type) {
1501
+ ctx.lineCap = 'round';
1502
+ }
1503
+ }
1504
+
1505
+ /**
1506
+ * 更改 ctx 的 lineJoin 属性
1507
+ */
1508
+ export function setLineJoin(ctx: CanvasRenderingContext2D, pen: Pen) {
1509
+ const lineJoin = pen.lineJoin;
1510
+ if (lineJoin) {
1511
+ ctx.lineJoin = lineJoin;
1512
+ } else if (pen.type) {
1513
+ ctx.lineJoin = 'round';
1514
+ }
1515
+ }
1516
+
1517
+ /**
1518
+ * 通常用在下载 svg
1519
+ * canvas2svg 与 canvas ctx 设置 strokeStyle 表现不同
1520
+ * 若设置值为 undefined ,canvas2svg 为空, canvas ctx 为上一个值
1521
+ */
1522
+ export function renderPenRaw(
1523
+ ctx: CanvasRenderingContext2D,
1524
+ pen: Pen,
1525
+ rect?: Rect,
1526
+ download?: boolean
1527
+ ) {
1528
+ ctx.save();
1529
+ if (rect) {
1530
+ ctx.translate(-rect.x, -rect.y);
1531
+ }
1532
+
1533
+ // for canvas2svg
1534
+ (ctx as any).setAttrs?.(pen);
1535
+ // end
1536
+ let lineGradientFlag = false;
1537
+ const store = pen.calculative.canvas.store;
1538
+ const textFlip = pen.textFlip || store.options.textFlip;
1539
+ const textRotate = pen.textRotate || store.options.textRotate;
1540
+ ctx.beginPath();
1541
+ if (!textFlip || !textRotate) {
1542
+ ctx.save();
1543
+ }
1544
+ if (pen.calculative.flipX) {
1545
+ if (rect) {
1546
+ ctx.translate(
1547
+ pen.calculative.worldRect.x + pen.calculative.worldRect.ex,
1548
+ 0
1549
+ );
1550
+ } else {
1551
+ ctx.translate(
1552
+ pen.calculative.worldRect.x + pen.calculative.worldRect.ex,
1553
+ 0
1554
+ );
1555
+ }
1556
+ ctx.scale(-1, 1);
1557
+ }
1558
+ if (pen.calculative.flipY) {
1559
+ if (rect) {
1560
+ ctx.translate(
1561
+ 0,
1562
+ pen.calculative.worldRect.y + pen.calculative.worldRect.ey
1563
+ );
1564
+ } else {
1565
+ ctx.translate(
1566
+ 0,
1567
+ pen.calculative.worldRect.y + pen.calculative.worldRect.ey
1568
+ );
1569
+ }
1570
+ ctx.scale(1, -1);
1571
+ }
1572
+
1573
+ if (pen.calculative.rotate && pen.name !== 'line') {
1574
+ ctxRotate(ctx, pen);
1575
+ }
1576
+ if (pen.calculative.lineWidth > 1 || download) {
1577
+ ctx.lineWidth = pen.calculative.lineWidth;
1578
+ }
1579
+ let fill: any;
1580
+ if (pen.calculative.hover) {
1581
+ ctx.strokeStyle = pen.hoverColor || store.options.hoverColor;
1582
+ ctx.fillStyle = pen.hoverBackground || store.options.hoverBackground;
1583
+ fill = pen.hoverBackground || store.options.hoverBackground;
1584
+ } else if (pen.calculative.active) {
1585
+ ctx.strokeStyle = pen.activeColor || store.options.activeColor;
1586
+ ctx.fillStyle = pen.activeBackground || store.options.activeBackground;
1587
+ fill = pen.activeBackground || store.options.activeBackground;
1588
+ } else {
1589
+ if (pen.strokeImage) {
1590
+ if (pen.calculative.strokeImg) {
1591
+ ctx.strokeStyle = ctx.createPattern(
1592
+ pen.calculative.strokeImg,
1593
+ 'repeat'
1594
+ );
1595
+ fill = true;
1596
+ }
1597
+ } else {
1598
+ let stroke: string | CanvasGradient | CanvasPattern;
1599
+ if (pen.calculative.strokeType&&pen.calculative.lineGradientColors&&pen.name === 'line') {
1600
+ lineGradientFlag = true;
1601
+ } else {
1602
+ stroke = pen.calculative.color || getGlobalColor(store);
1603
+ }
1604
+ ctx.strokeStyle = stroke;
1605
+ }
1606
+
1607
+ if (pen.backgroundImage) {
1608
+ if (pen.calculative.backgroundImg) {
1609
+ ctx.fillStyle = ctx.createPattern(
1610
+ pen.calculative.backgroundImg,
1611
+ 'repeat'
1612
+ );
1613
+ fill = true;
1614
+ }
1615
+ } else {
1616
+ ctx.fillStyle = pen.background;
1617
+ fill = !!pen.background;
1618
+ }
1619
+ }
1620
+
1621
+ setLineCap(ctx, pen);
1622
+ setLineJoin(ctx, pen);
1623
+
1624
+ setGlobalAlpha(ctx, pen);
1625
+
1626
+ if (pen.calculative.lineDash) {
1627
+ ctx.setLineDash(pen.calculative.lineDash);
1628
+ }
1629
+ if (pen.calculative.lineDashOffset) {
1630
+ ctx.lineDashOffset = pen.calculative.lineDashOffset;
1631
+ }
1632
+
1633
+ if (pen.calculative.shadowColor) {
1634
+ ctx.shadowColor = pen.calculative.shadowColor;
1635
+ ctx.shadowOffsetX = pen.calculative.shadowOffsetX;
1636
+ ctx.shadowOffsetY = pen.calculative.shadowOffsetY;
1637
+ ctx.shadowBlur = pen.calculative.shadowBlur;
1638
+ }
1639
+
1640
+ if (lineGradientFlag) {
1641
+ ctxDrawLinearGradientPath(ctx, pen);
1642
+ ctxDrawLinePath(true, ctx, pen, store);
1643
+ } else {
1644
+ ctxDrawPath(false, ctx, pen, store, fill);
1645
+
1646
+ ctxDrawCanvas(ctx, pen);
1647
+ }
1648
+
1649
+ // renderPenRaw 用在 downloadPng svg , echarts 等图形需要
1650
+ if (pen.calculative.img) {
1651
+ ctx.save();
1652
+ ctx.shadowColor = '';
1653
+ ctx.shadowBlur = 0;
1654
+ ctx.shadowOffsetX = 0;
1655
+ ctx.shadowOffsetY = 0;
1656
+ drawImage(ctx, pen);
1657
+ ctx.restore();
1658
+ } else if (pen.calculative.icon) {
1659
+ drawIcon(ctx, pen);
1660
+ }
1661
+
1662
+ if (!textFlip || !textRotate) {
1663
+ ctx.restore();
1664
+ }
1665
+
1666
+ if (textFlip && !textRotate) {
1667
+ if (pen.calculative.flipX) {
1668
+ if (rect) {
1669
+ ctx.translate(
1670
+ pen.calculative.worldRect.x + pen.calculative.worldRect.ex,
1671
+ 0
1672
+ );
1673
+ } else {
1674
+ ctx.translate(
1675
+ pen.calculative.worldRect.x + pen.calculative.worldRect.ex,
1676
+ 0
1677
+ );
1678
+ }
1679
+ ctx.scale(-1, 1);
1680
+ }
1681
+ if (pen.calculative.flipY) {
1682
+ if (rect) {
1683
+ ctx.translate(
1684
+ 0,
1685
+ pen.calculative.worldRect.y + pen.calculative.worldRect.ey
1686
+ );
1687
+ } else {
1688
+ ctx.translate(
1689
+ 0,
1690
+ pen.calculative.worldRect.y + pen.calculative.worldRect.ey
1691
+ );
1692
+ }
1693
+ ctx.scale(1, -1);
1694
+ }
1695
+ }
1696
+ if (!textFlip && textRotate) {
1697
+ if (pen.calculative.rotate && pen.name !== 'line') {
1698
+ ctxRotate(ctx, pen, true);
1699
+ }
1700
+ }
1701
+
1702
+ drawText(ctx, pen);
1703
+
1704
+ if (pen.type === PenType.Line && pen.fillTexts) {
1705
+ for (const text of pen.fillTexts) {
1706
+ drawFillText(ctx, pen, text);
1707
+ }
1708
+ }
1709
+
1710
+ ctx.restore();
1711
+ }
1712
+
1713
+ /**
1714
+ * 根据 path2D 绘制 path
1715
+ * @param canUsePath 是否可使用 Path2D, downloadSvg 不可使用 path2D
1716
+ */
1717
+ export function ctxDrawPath(
1718
+ canUsePath = true,
1719
+ ctx: CanvasRenderingContext2D,
1720
+ pen: Pen,
1721
+ store: Meta2dStore,
1722
+ fill: boolean
1723
+ ) {
1724
+ const path = canUsePath
1725
+ ? store.path2dMap.get(pen)
1726
+ : globalStore.path2dDraws[pen.name];
1727
+
1728
+ let path_from = null;
1729
+ let path_to = null;
1730
+ if(pen.type === PenType.Line){
1731
+ //线段的起始和结束线帽 分别配置
1732
+ if( pen.fromLineCap && pen.fromLineCap !== 'butt'){
1733
+ ctx.lineCap = 'butt';
1734
+ path_from = new Path2D();
1735
+ path_from.moveTo(pen.calculative.worldAnchors[0].x, pen.calculative.worldAnchors[0].y);
1736
+ path_from.lineTo(pen.calculative.worldAnchors[0].x, pen.calculative.worldAnchors[0].y);
1737
+ }
1738
+ if( pen.toLineCap && pen.toLineCap !== 'butt'){
1739
+ ctx.lineCap = 'butt';
1740
+ path_to = new Path2D();
1741
+ path_to.moveTo(pen.calculative.worldAnchors[pen.calculative.worldAnchors.length-1].x, pen.calculative.worldAnchors[pen.calculative.worldAnchors.length-1].y);
1742
+ path_to.lineTo(pen.calculative.worldAnchors[pen.calculative.worldAnchors.length-1].x, pen.calculative.worldAnchors[pen.calculative.worldAnchors.length-1].y);
1743
+ }
1744
+ }
1745
+ if (path) {
1746
+ if (pen.type === PenType.Line && pen.borderWidth) {
1747
+ ctx.save();
1748
+ ctx.beginPath();
1749
+ const lineWidth = pen.calculative.lineWidth + pen.calculative.borderWidth;
1750
+ ctx.lineWidth = lineWidth;
1751
+ ctx.strokeStyle = pen.borderColor;
1752
+ if( path_from ){
1753
+ ctx.save();
1754
+ ctx.lineCap = pen.fromLineCap;
1755
+ ctx.stroke(path_from);
1756
+ ctx.restore();
1757
+ }
1758
+ if (path instanceof Path2D) {
1759
+ fill && ctx.fill(path);
1760
+ lineWidth && ctx.stroke(path);
1761
+ } else {
1762
+ path(pen, ctx);
1763
+ fill && ctx.fill();
1764
+ lineWidth && ctx.stroke();
1765
+ }
1766
+ if( path_to){
1767
+ ctx.save();
1768
+ ctx.lineCap = pen.toLineCap;
1769
+ ctx.stroke(path_to);
1770
+ ctx.restore();
1771
+ }
1772
+ ctx.restore();
1773
+ }
1774
+ if (path instanceof Path2D) {
1775
+ if(pen.type){
1776
+ if(pen.close){
1777
+ fill && ctx.fill(path);
1778
+ }
1779
+ }else{
1780
+ //svgPath
1781
+ fill && ctx.fill(path);
1782
+ }
1783
+ } else {
1784
+ ctx.save();
1785
+ path(pen, ctx);
1786
+ fill && ctx.fill();
1787
+ ctx.restore();
1788
+ }
1789
+
1790
+ const progress = pen.calculative.progress;
1791
+ if (progress != null) {
1792
+ // 从左往右 x, y, x + width * progress, y
1793
+ // 从右往左 ex, y, x + width * (1-progress), y
1794
+ // 从下往上 x, y, x, y + height * progress
1795
+ // 从上往下 x, ey, x, y + height * (1 - progress)
1796
+ ctx.save();
1797
+ const { ex, x, y, width, height, ey } = pen.calculative.worldRect;
1798
+ let grd = null;
1799
+ if (!pen.verticalProgress) {
1800
+ grd = !pen.reverseProgress
1801
+ ? ctx.createLinearGradient(x, y, x + width * progress, y)
1802
+ : ctx.createLinearGradient(ex, y, x + width * (1 - progress), y);
1803
+ } else {
1804
+ grd = !pen.reverseProgress
1805
+ ? ctx.createLinearGradient(x, ey, x, y + height * (1 - progress))
1806
+ : ctx.createLinearGradient(x, y, x, y + height * progress);
1807
+ }
1808
+
1809
+ if (pen.calculative.progressGradientColors) {
1810
+ const { colors } = formatGradient(
1811
+ pen.calculative.progressGradientColors
1812
+ );
1813
+ colors.forEach((stop) => {
1814
+ grd.addColorStop(stop.i, stop.color);
1815
+ });
1816
+ } else {
1817
+ const color =
1818
+ pen.calculative.progressColor ||
1819
+ pen.calculative.color ||
1820
+ store.options.activeColor;
1821
+ grd.addColorStop(0, color);
1822
+ grd.addColorStop(1, color);
1823
+ }
1824
+ grd.addColorStop(1, 'transparent');
1825
+
1826
+ ctx.fillStyle = grd;
1827
+ if (path instanceof Path2D) {
1828
+ ctx.fill(path);
1829
+ } else {
1830
+ path(pen, ctx);
1831
+ ctx.fill();
1832
+ }
1833
+ ctx.restore();
1834
+ }
1835
+
1836
+ if (pen.calculative.lineWidth) {
1837
+ if (path instanceof Path2D) {
1838
+ if(store.options.svgPathStroke || pen.name!=='svgPath'){
1839
+ if(path_from){
1840
+ ctx.save();
1841
+ ctx.lineCap = pen.fromLineCap;
1842
+ ctx.stroke(path_from);
1843
+ ctx.restore();
1844
+ }
1845
+ ctx.stroke(path);
1846
+ if( path_to ){
1847
+ ctx.save();
1848
+ ctx.lineCap = pen.toLineCap;
1849
+ ctx.stroke(path_to);
1850
+ ctx.restore();
1851
+ }
1852
+ }
1853
+ } else {
1854
+ path(pen, ctx);
1855
+ ctx.stroke();
1856
+ }
1857
+ }
1858
+
1859
+ if (pen.type) {
1860
+ if (pen.calculative.animatePos) {
1861
+ ctx.save();
1862
+ setCtxLineAnimate(ctx, pen, store);
1863
+ if(pen.lineAnimateType === LineAnimateType.Arrow || pen.lineAnimateType === LineAnimateType.WaterDrop){
1864
+ //箭头动画
1865
+ let _path = drawArrow(pen,ctx);
1866
+ if(_path instanceof Path2D){
1867
+ ctx.stroke(_path);
1868
+ ctx.fill(_path);
1869
+ }else{
1870
+ ctx.stroke();
1871
+ ctx.fill();
1872
+ }
1873
+ }else{
1874
+ if (path instanceof Path2D) {
1875
+ if(path_from&&!pen.lineAnimateType){
1876
+ ctx.save();
1877
+ ctx.lineCap = pen.fromLineCap;
1878
+ ctx.stroke(path_from);
1879
+ ctx.restore();
1880
+ }
1881
+ ctx.lineCap = pen.lineCap;
1882
+ ctx.stroke(path);
1883
+ } else {
1884
+ path(pen, ctx);
1885
+ ctx.stroke();
1886
+ }
1887
+ }
1888
+ ctx.restore();
1889
+ }
1890
+
1891
+ pen.fromArrow && renderFromArrow(ctx, pen, store);
1892
+ pen.toArrow && renderToArrow(ctx, pen, store);
1893
+
1894
+ if (pen.calculative.active && !pen.calculative.pencil && !store.options.disableAnchor && !store.data.locked) {
1895
+ renderLineAnchors(ctx, pen);
1896
+ }
1897
+ }
1898
+ }
1899
+ }
1900
+
1901
+ /**
1902
+ * 连线配置线条渐进后,动画效果、起始点、终点的绘制
1903
+ */
1904
+ export function ctxDrawLinePath(
1905
+ canUsePath = true,
1906
+ ctx: CanvasRenderingContext2D,
1907
+ pen: Pen,
1908
+ store: Meta2dStore
1909
+ ) {
1910
+ const path = canUsePath
1911
+ ? store.path2dMap.get(pen)
1912
+ : globalStore.path2dDraws[pen.name];
1913
+ if (path) {
1914
+ if (pen.type) {
1915
+ if (pen.calculative.animatePos) {
1916
+ ctx.save();
1917
+ setCtxLineAnimate(ctx, pen, store);
1918
+ ctx.beginPath();
1919
+ if (path instanceof Path2D) {
1920
+ if(pen.lineName === 'polyline' || pen.lineName === 'line'){
1921
+ if(pen.lineAnimateType === LineAnimateType.Arrow || pen.lineAnimateType === LineAnimateType.WaterDrop){
1922
+ //箭头动画
1923
+ const _path = drawArrow(pen);
1924
+ ctx.stroke(_path);
1925
+ ctx.fill(_path);
1926
+ }else{
1927
+ if(pen.calculative.gradientSmooth || pen.calculative.lineSmooth){
1928
+ if (!pen.calculative.gradientAnimatePath) {
1929
+ pen.calculative.gradientAnimatePath = getGradientAnimatePath(pen);
1930
+ }
1931
+ if (pen.calculative.gradientAnimatePath instanceof Path2D) {
1932
+ ctx.stroke(pen.calculative.gradientAnimatePath);
1933
+ }
1934
+ }else{
1935
+ ctx.stroke(path);
1936
+ }
1937
+ }
1938
+ }else {
1939
+ ctx.stroke(path);
1940
+ }
1941
+ } else {
1942
+ path(pen, ctx);
1943
+ ctx.stroke();
1944
+ }
1945
+ ctx.restore();
1946
+ }
1947
+
1948
+ pen.fromArrow && renderFromArrow(ctx, pen, store);
1949
+ pen.toArrow && renderToArrow(ctx, pen, store);
1950
+ //TODO 锚点处渐进色的过渡
1951
+ if (pen.calculative.active && !pen.calculative.pencil && !store.options.disableAnchor && !store.data.locked) {
1952
+ renderLineAnchors(ctx, pen);
1953
+ }
1954
+ }
1955
+ }
1956
+ }
1957
+
1958
+ /**
1959
+ * 设置线条动画,ctx 的 strokeStyle lineDash 等属性更改
1960
+ */
1961
+ export function setCtxLineAnimate(
1962
+ ctx: CanvasRenderingContext2D,
1963
+ pen: Pen,
1964
+ store: Meta2dStore
1965
+ ) {
1966
+ ctx.strokeStyle = pen.animateColor || store.options.animateColor;
1967
+ if(pen.animateShadow){
1968
+ ctx.shadowBlur = pen.animateShadowBlur || pen.animateLineWidth || 6;
1969
+ ctx.shadowColor = pen.animateShadowColor || pen.animateColor || store.options.animateColor;
1970
+ }
1971
+ pen.calculative.animateLineWidth &&
1972
+ (ctx.lineWidth = pen.calculative.animateLineWidth * store.data.scale);
1973
+ let len = 0;
1974
+ switch (pen.lineAnimateType) {
1975
+ case LineAnimateType.Beads:
1976
+ if (pen.animateReverse) {
1977
+ ctx.lineDashOffset = pen.calculative.animatePos;
1978
+ } else {
1979
+ ctx.lineDashOffset = pen.length - pen.calculative.animatePos;
1980
+ }
1981
+ len = pen.calculative.lineWidth || 5;
1982
+ if (len < 5) {
1983
+ len = 5;
1984
+ }
1985
+ const dash =
1986
+ pen.animateLineDash &&
1987
+ pen.animateLineDash.map((item) => (item * len) / 5);
1988
+ ctx.setLineDash(dash || [len, len * 2]);
1989
+ break;
1990
+ case LineAnimateType.Dot:
1991
+ if (pen.animateReverse) {
1992
+ ctx.lineDashOffset = pen.calculative.animatePos;
1993
+ } else {
1994
+ ctx.lineDashOffset = pen.length - pen.calculative.animatePos;
1995
+ }
1996
+ len =
1997
+ pen.calculative.animateDotSize || pen.calculative.lineWidth * 2 || 6;
1998
+ if (len < 6) {
1999
+ len = 6;
2000
+ }
2001
+ if(len > 40){
2002
+ len = 40;
2003
+ }
2004
+ ctx.lineWidth =
2005
+ (pen.calculative.animateLineWidth || len) * store.data.scale;
2006
+ ctx.setLineDash([0.1, pen.length]);
2007
+ break;
2008
+ case LineAnimateType.Arrow:
2009
+ ctx.fillStyle = pen.animateColor || store.options.animateColor;
2010
+ ctx.lineWidth = 1;
2011
+ break;
2012
+ case LineAnimateType.WaterDrop:
2013
+ ctx.fillStyle = pen.animateColor || store.options.animateColor;
2014
+ ctx.lineWidth = 1;
2015
+ break;
2016
+ default:
2017
+ if (pen.animateReverse) {
2018
+ ctx.lineDashOffset = Number.EPSILON;//防止在执行动画时会绘制多余的远点
2019
+ ctx.setLineDash([
2020
+ 0,
2021
+ pen.length - pen.calculative.animatePos + 1,
2022
+ pen.calculative.animatePos,
2023
+ ]);
2024
+ } else {
2025
+ ctx.setLineDash([
2026
+ pen.calculative.animatePos,
2027
+ pen.length + 0.01 - pen.calculative.animatePos,//避免在缩放时,精度问题绘制多余圆点
2028
+ ]);
2029
+ }
2030
+ break;
2031
+ }
2032
+ }
2033
+
2034
+ /**
2035
+ * 全局 color
2036
+ */
2037
+ export function getGlobalColor(store: Meta2dStore) {
2038
+ const { data, options } = store;
2039
+ return data.color || options.color;
2040
+ }
2041
+
2042
+ export function renderLineAnchors(ctx: CanvasRenderingContext2D, pen: Pen) {
2043
+ const store = pen.calculative.canvas.store;
2044
+
2045
+ ctx.save();
2046
+ ctx.lineWidth = 1;
2047
+ ctx.fillStyle = pen.activeColor || store.options.activeColor;
2048
+ pen.calculative.worldAnchors.forEach((pt) => {
2049
+ !pt.hidden && !pt.isTemp && renderAnchor(ctx, pt, pen);
2050
+ });
2051
+ ctx.restore();
2052
+ }
2053
+
2054
+ export function renderAnchor(
2055
+ ctx: CanvasRenderingContext2D,
2056
+ pt: Point,
2057
+ pen: Pen
2058
+ ) {
2059
+ if (!pt) {
2060
+ return;
2061
+ }
2062
+
2063
+ const active =
2064
+ pen.calculative.canvas.store.activeAnchor ===
2065
+ pen.calculative.activeAnchor && pen.calculative.activeAnchor === pt;
2066
+ let r = 3;
2067
+ if (pen.calculative.lineWidth > 3) {
2068
+ r = pen.calculative.lineWidth;
2069
+ }
2070
+ if (pen.anchorRadius) {
2071
+ r = pen.anchorRadius;
2072
+ }
2073
+ if (pt.radius) {
2074
+ r = pt.radius;
2075
+ }
2076
+ if (active) {
2077
+ if (pt.prev) {
2078
+ ctx.save();
2079
+ ctx.strokeStyle = '#4dffff';
2080
+ ctx.beginPath();
2081
+ ctx.moveTo(pt.prev.x, pt.prev.y);
2082
+ ctx.lineTo(pt.x, pt.y);
2083
+ ctx.stroke();
2084
+ ctx.restore();
2085
+
2086
+ ctx.save();
2087
+ ctx.fillStyle = '#ffffff';
2088
+ ctx.beginPath();
2089
+ ctx.arc(pt.prev.x, pt.prev.y, r, 0, Math.PI * 2);
2090
+ ctx.fill();
2091
+ ctx.stroke();
2092
+ ctx.restore();
2093
+ }
2094
+ if (pt.next) {
2095
+ ctx.save();
2096
+ ctx.strokeStyle = '#4dffff';
2097
+ ctx.beginPath();
2098
+ ctx.moveTo(pt.x, pt.y);
2099
+ ctx.lineTo(pt.next.x, pt.next.y);
2100
+ ctx.stroke();
2101
+ ctx.restore();
2102
+
2103
+ ctx.save();
2104
+ ctx.fillStyle = '#ffffff';
2105
+ ctx.beginPath();
2106
+ ctx.arc(pt.next.x, pt.next.y, r, 0, Math.PI * 2);
2107
+ ctx.fill();
2108
+ ctx.stroke();
2109
+ ctx.restore();
2110
+
2111
+ ctx.beginPath();
2112
+ ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);
2113
+ ctx.fill();
2114
+ ctx.stroke();
2115
+ }
2116
+
2117
+ ctx.beginPath();
2118
+ ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);
2119
+ ctx.fill();
2120
+ ctx.stroke();
2121
+ } else {
2122
+ ctx.save();
2123
+ ctx.fillStyle = '#ffffff';
2124
+ ctx.beginPath();
2125
+ ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);
2126
+ ctx.fill();
2127
+ ctx.stroke();
2128
+ ctx.restore();
2129
+ }
2130
+ }
2131
+
2132
+ export function calcWorldRects(pen: Pen) {
2133
+ const store: Meta2dStore = pen.calculative.canvas.store;
2134
+
2135
+ let rect: Rect = {
2136
+ x: pen.x,
2137
+ y: pen.y,
2138
+ };
2139
+
2140
+ if (!pen.parentId || (pen.parentId && !store.pens[pen.parentId])) {
2141
+ pen.parentId = undefined;
2142
+ rect.width = pen.width;
2143
+ rect.height = pen.height;
2144
+ rect.rotate = pen.rotate;
2145
+ calcRightBottom(rect);
2146
+ calcCenter(rect);
2147
+ if(pen.pivot){
2148
+ calcPivot(rect, pen.pivot);
2149
+ }
2150
+ } else {
2151
+ const parent = store.pens[pen.parentId];
2152
+ let parentRect = parent.calculative.worldRect;
2153
+ if (!parentRect) {
2154
+ parentRect = calcWorldRects(parent);
2155
+ }
2156
+
2157
+ rect.x = parentRect.x + parentRect.width * pen.x;
2158
+ rect.y = parentRect.y + parentRect.height * pen.y;
2159
+ rect.width = parentRect.width * pen.width;
2160
+ rect.height = parentRect.height * pen.height;
2161
+ if (parent.flipX) {
2162
+ rect.x =
2163
+ parentRect.width - (rect.x - parentRect.x + rect.width) + parentRect.x;
2164
+ }
2165
+ if (parent.flipY) {
2166
+ rect.y =
2167
+ parentRect.height -
2168
+ (rect.y - parentRect.y + rect.height) +
2169
+ parentRect.y;
2170
+ }
2171
+
2172
+ calcRightBottom(rect);
2173
+
2174
+ rect.rotate = parentRect.rotate + pen.rotate;
2175
+ calcCenter(rect);
2176
+ if(pen.pivot){
2177
+ calcPivot(rect, pen.pivot);
2178
+ }
2179
+ }
2180
+
2181
+ pen.calculative.worldRect = rect;
2182
+ // 这里的 rect 均是绝对值
2183
+ calcPadding(pen, rect);
2184
+
2185
+ return rect;
2186
+ }
2187
+
2188
+ export function calcPadding(pen: Pen, rect: Rect) {
2189
+ !pen.paddingTop && (pen.calculative.paddingTop = 0);
2190
+ !pen.paddingBottom && (pen.calculative.paddingBottom = 0);
2191
+ !pen.paddingLeft && (pen.calculative.paddingLeft = 0);
2192
+ !pen.paddingRight && (pen.calculative.paddingRight = 0);
2193
+
2194
+ Math.abs(pen.calculative.paddingTop) < 1 &&
2195
+ (pen.calculative.paddingTop *= rect.height);
2196
+ Math.abs(pen.calculative.paddingBottom) < 1 &&
2197
+ (pen.calculative.paddingBottom *= rect.height);
2198
+ Math.abs(pen.calculative.paddingLeft) < 1 &&
2199
+ (pen.calculative.paddingLeft *= rect.width);
2200
+ Math.abs(pen.calculative.paddingRight) < 1 &&
2201
+ (pen.calculative.paddingRight *= rect.width);
2202
+ }
2203
+
2204
+ export function calcPenRect(pen: Pen) {
2205
+ const worldRect = deepClone(pen.calculative.worldRect);
2206
+ delete worldRect.pivot;
2207
+ if (!pen.parentId) {
2208
+ Object.assign(pen, worldRect);
2209
+ return;
2210
+ }
2211
+ const store = pen.calculative.canvas.store;
2212
+ const parentRect = store.pens[pen.parentId].calculative.worldRect;
2213
+ Object.assign(pen, calcRelativeRect(worldRect, parentRect));
2214
+ }
2215
+
2216
+ export function calcWorldAnchors(pen: Pen) {
2217
+ const store: Meta2dStore = pen.calculative.canvas.store;
2218
+ let anchors: Point[] = [];
2219
+ if (pen.anchors) {
2220
+ let _anchors = deepClone(pen.anchors);
2221
+ if (pen.flipX) {
2222
+ _anchors.forEach((anchor) => {
2223
+ anchor.x = 0.5 - (anchor.x - 0.5);
2224
+ });
2225
+ }
2226
+ if (pen.flipY) {
2227
+ _anchors.forEach((anchor) => {
2228
+ anchor.y = 0.5 - (anchor.y - 0.5);
2229
+ });
2230
+ }
2231
+ _anchors.forEach((anchor) => {
2232
+ anchors.push(calcWorldPointOfPen(pen, anchor));
2233
+ });
2234
+ }
2235
+
2236
+ // Default anchors of node
2237
+ if (
2238
+ !anchors.length &&
2239
+ !pen.type &&
2240
+ !pen.calculative.canvas.parent.isCombine(pen)
2241
+ ) {
2242
+ const { x, y, width, height } = pen.calculative.worldRect;
2243
+ anchors = store.options.defaultAnchors.map((anchor, index) => {
2244
+ return {
2245
+ id: `${index}`,
2246
+ penId: pen.id,
2247
+ x: x + width * anchor.x,
2248
+ y: y + height * anchor.y,
2249
+ };
2250
+ });
2251
+ }
2252
+
2253
+ if (pen.calculative.rotate) {
2254
+ anchors.forEach((anchor) => {
2255
+ rotatePoint(
2256
+ anchor,
2257
+ pen.calculative.rotate,
2258
+ pen.calculative.worldRect.pivot || pen.calculative.worldRect.center
2259
+ );
2260
+ });
2261
+ }
2262
+
2263
+ if (!pen.type || pen.anchors) {
2264
+ pen.calculative.worldAnchors = anchors;
2265
+ }
2266
+
2267
+ if (pen.calculative.activeAnchor && anchors.length) {
2268
+ pen.calculative.activeAnchor = anchors.find((a) => {
2269
+ a.id === pen.calculative.activeAnchor.id;
2270
+ });
2271
+ }
2272
+
2273
+ pen.calculative.gradientAnimatePath = undefined;
2274
+ }
2275
+
2276
+ export function calcChildrenInitRect(pen:Pen){
2277
+ // 重新计算子节点初始化坐标
2278
+ if (pen.children?.length) {
2279
+ let parentRect = pen.calculative.worldRect;
2280
+ pen.children.forEach((id) => {
2281
+ const child = pen.calculative.canvas.store.pens[id];
2282
+ if (child.calculative.initRect && child.calculative.initRelativeRect) {
2283
+ child.calculative.initRect.x =
2284
+ parentRect.x +
2285
+ parentRect.width * child.calculative.initRelativeRect.x;
2286
+ child.calculative.initRect.y =
2287
+ parentRect.y +
2288
+ parentRect.height * child.calculative.initRelativeRect.y;
2289
+ child.calculative.initRect.ex =
2290
+ child.calculative.initRect.x +
2291
+ parentRect.width * child.calculative.initRelativeRect.width;
2292
+ child.calculative.initRect.ey =
2293
+ child.calculative.initRect.y +
2294
+ parentRect.height +
2295
+ child.calculative.initRelativeRect.height;
2296
+ calcCenter(child.calculative.initRect);
2297
+ }
2298
+ calcChildrenInitRect(child);
2299
+ });
2300
+ }
2301
+ }
2302
+
2303
+ export function calcWorldPointOfPen(pen: Pen, pt: Point) {
2304
+ const p: Point = { ...pt };
2305
+ const { x, y, width, height } = pen.calculative.worldRect;
2306
+ p.x = x + width * pt.x;
2307
+ p.y = y + height * pt.y;
2308
+ if (pt.prev) {
2309
+ p.prev = {
2310
+ penId: pen.id,
2311
+ connectTo: pt.prev.connectTo,
2312
+ x: x + width * pt.prev.x,
2313
+ y: y + height * pt.prev.y,
2314
+ };
2315
+ }
2316
+ if (pt.next) {
2317
+ p.next = {
2318
+ penId: pen.id,
2319
+ connectTo: pt.next.connectTo,
2320
+ x: x + width * pt.next.x,
2321
+ y: y + height * pt.next.y,
2322
+ };
2323
+ }
2324
+
2325
+ return p;
2326
+ }
2327
+
2328
+ export function calcIconRect(pens: { [key: string]: Pen }, pen: Pen) {
2329
+ const { paddingTop, paddingBottom, paddingLeft, paddingRight } =
2330
+ pen.calculative;
2331
+ let x = paddingLeft;
2332
+ let y = paddingTop;
2333
+ let width = pen.calculative.worldRect.width - paddingLeft - paddingRight;
2334
+ let height = pen.calculative.worldRect.height - paddingTop - paddingBottom;
2335
+ let iconLeft = pen.calculative.iconLeft;
2336
+ let iconTop = pen.calculative.iconTop;
2337
+ if (iconLeft && Math.abs(iconLeft) < 1) {
2338
+ iconLeft = pen.calculative.worldRect.width * iconLeft;
2339
+ }
2340
+
2341
+ if (iconTop && Math.abs(iconTop) < 1) {
2342
+ iconTop = pen.calculative.worldRect.height * iconTop;
2343
+ }
2344
+ x += iconLeft || 0;
2345
+ y += iconTop || 0;
2346
+ width -= iconLeft || 0;
2347
+ height -= iconTop || 0;
2348
+
2349
+ let rotate = pen.calculative.iconRotate || 0;
2350
+ if (pen.parentId) {
2351
+ const parentPen = pens[pen.parentId].calculative;
2352
+ if (parentPen) {
2353
+ rotate += parentPen.rotate;
2354
+ rotate %= 360;
2355
+ }
2356
+ }
2357
+
2358
+ x = pen.calculative.worldRect.x + x;
2359
+ y = pen.calculative.worldRect.y + y;
2360
+ pen.calculative.worldIconRect = {
2361
+ x,
2362
+ y,
2363
+ width,
2364
+ height,
2365
+ rotate,
2366
+ };
2367
+ calcRightBottom(pen.calculative.worldIconRect);
2368
+ calcCenter(pen.calculative.worldIconRect);
2369
+ }
2370
+
2371
+ export function scalePen(pen: Pen, scale: number, center: Point) {
2372
+ scaleRect(pen.calculative.worldRect, scale, center, pen.pivot);
2373
+
2374
+ if (pen.calculative.initRect) {
2375
+ scaleRect(pen.calculative.initRect, scale, center, pen.pivot);
2376
+ }
2377
+ scaleChildrenInitRect(pen, scale, center);
2378
+ if (pen.calculative.x) {
2379
+ scalePoint(pen.calculative as any as Point, scale, center);
2380
+ }
2381
+
2382
+ if (pen.type) {
2383
+ calcWorldAnchors(pen);
2384
+ }
2385
+ }
2386
+
2387
+ export function scaleChildrenInitRect(pen: Pen, scale: number, center: Point) {
2388
+ if(!pen){
2389
+ return;
2390
+ }
2391
+ if (pen.children?.length) {
2392
+ pen.children.forEach((id) => {
2393
+ const child = pen.calculative.canvas.store.pens[id];
2394
+ if(child){
2395
+ if ( child.calculative.initRect ) {
2396
+ scaleRect(
2397
+ child.calculative.initRect,
2398
+ scale,
2399
+ center
2400
+ );
2401
+ }
2402
+ scaleChildrenInitRect(child, scale, center);
2403
+ }
2404
+ });
2405
+ }
2406
+ }
2407
+
2408
+ export function pushPenAnchor(pen: Pen, pt: Point) {
2409
+ if (!pen.anchors) {
2410
+ pen.anchors = [];
2411
+ }
2412
+ if (!pen.calculative.worldAnchors) {
2413
+ pen.calculative.worldAnchors = [];
2414
+ }
2415
+
2416
+ const worldAnchor = {
2417
+ id: pt.id,
2418
+ penId: pen.id,
2419
+ x: pt.x,
2420
+ y: pt.y,
2421
+ };
2422
+ pen.calculative.worldAnchors.push(worldAnchor);
2423
+
2424
+ if (pen.calculative.worldRect) {
2425
+ if (pen.rotate % 360) {
2426
+ rotatePoint(pt, -pen.rotate, pen.calculative.worldRect.center);
2427
+ }
2428
+
2429
+ const anchor = {
2430
+ id: pt.id,
2431
+ penId: pen.id,
2432
+ x: (pt.x - pen.calculative.worldRect.x) / pen.calculative.worldRect.width,
2433
+ y:
2434
+ (pt.y - pen.calculative.worldRect.y) / pen.calculative.worldRect.height,
2435
+ };
2436
+ pen.anchors.push(anchor);
2437
+ }
2438
+
2439
+ return worldAnchor;
2440
+ }
2441
+
2442
+ export function addLineAnchor(pen: Pen, pt: Point, index: number) {
2443
+ if (!pen.anchors) {
2444
+ pen.anchors = [];
2445
+ }
2446
+ if (!pen.calculative.worldAnchors) {
2447
+ pen.calculative.worldAnchors = [];
2448
+ }
2449
+
2450
+ const worldAnchor = getSplitAnchor(pen, pt, index);
2451
+ pen.calculative.worldAnchors.splice(index + 1, 0, worldAnchor);
2452
+ pen.anchors.splice(
2453
+ index + 1,
2454
+ 0,
2455
+ calcRelativePoint(worldAnchor, pen.calculative.worldRect)
2456
+ );
2457
+ pen.calculative.activeAnchor = worldAnchor;
2458
+ return worldAnchor;
2459
+ }
2460
+
2461
+ export function removePenAnchor(pen: Pen, anchor: Point) {
2462
+ if (!pen || !pen.calculative.worldAnchors) {
2463
+ return;
2464
+ }
2465
+ let i = pen.calculative.worldAnchors.findIndex((a) => a.id === anchor.id);
2466
+ if (i > -1) {
2467
+ pen.calculative.worldAnchors.splice(i, 1);
2468
+ }
2469
+
2470
+ i = pen.anchors.findIndex((a) => a.id === anchor.id);
2471
+ if (i > -1) {
2472
+ pen.anchors.splice(i, 1);
2473
+ }
2474
+ }
2475
+
2476
+ export function facePen(pt: Point, pen?: Pen) {
2477
+ if (!pen || !pen.calculative || !pen.calculative.worldRect.center) {
2478
+ return Direction.None;
2479
+ }
2480
+ if (pt.anchorId) {
2481
+ let anchor = pen.anchors.filter((_anchor) => _anchor.id === pt.anchorId);
2482
+ if (anchor.length) {
2483
+ if (anchor[0].direction > -1) {
2484
+ return anchor[0].direction;
2485
+ }
2486
+ }
2487
+ }
2488
+ return facePoint(pt, pen.calculative.worldRect.center);
2489
+ }
2490
+
2491
+ export function nearestAnchor(pen: Pen, pt: Point) {
2492
+ let dis = Infinity;
2493
+ let anchor: Point;
2494
+ pen.calculative.worldAnchors.forEach((a: Point) => {
2495
+ const d = distance(pt, a);
2496
+ if (dis > d) {
2497
+ dis = d;
2498
+ anchor = a;
2499
+ }
2500
+ });
2501
+
2502
+ return anchor;
2503
+ }
2504
+
2505
+ export function translateLine(pen: Pen, x: number, y: number) {
2506
+ pen.x += x;
2507
+ pen.y += y;
2508
+
2509
+ if (pen.anchors) {
2510
+ pen.anchors.forEach((a) => {
2511
+ translatePoint(a, x, y);
2512
+ });
2513
+ }
2514
+
2515
+ if (pen.calculative.worldAnchors) {
2516
+ pen.calculative.worldAnchors.forEach((a) => {
2517
+ translatePoint(a, x, y);
2518
+ });
2519
+ }
2520
+ }
2521
+
2522
+ export function deleteTempAnchor(pen: Pen) {
2523
+ if (pen && pen.calculative && pen.calculative.worldAnchors.length) {
2524
+ let to: Point = getToAnchor(pen);
2525
+
2526
+ // 第一次画线
2527
+ if (!pen.anchors || !pen.anchors.length) {
2528
+ while (
2529
+ pen.calculative.worldAnchors.length &&
2530
+ to !== pen.calculative.activeAnchor
2531
+ ) {
2532
+ pen.calculative.worldAnchors.pop();
2533
+ to = getToAnchor(pen);
2534
+ }
2535
+ }
2536
+ // 拖拽终点
2537
+ else if (to === pen.calculative.activeAnchor) {
2538
+ pen.calculative.worldAnchors = [pen.calculative.worldAnchors[0]];
2539
+ }
2540
+ // 拖拽起点
2541
+ else if (pen.calculative.worldAnchors[0] === pen.calculative.activeAnchor) {
2542
+ pen.calculative.worldAnchors = [
2543
+ pen.calculative.worldAnchors[pen.calculative.worldAnchors.length - 1],
2544
+ ];
2545
+ }
2546
+ }
2547
+ }
2548
+
2549
+ /**
2550
+ * 添加line到pen的connectedLines中,并关联相关属性
2551
+ * 不添加连线到画布中,请确保画布中已经有该连线。
2552
+ * */
2553
+ export function connectLine(
2554
+ pen: Pen,
2555
+ anchor: Point,
2556
+ line: Pen,
2557
+ lineAnchor: Point
2558
+ ) {
2559
+ if (
2560
+ !pen ||
2561
+ !anchor ||
2562
+ !line ||
2563
+ !lineAnchor ||
2564
+ anchor.twoWay === TwoWay.DisableConnected ||
2565
+ anchor.twoWay === TwoWay.Disable ||
2566
+ lineAnchor.twoWay === TwoWay.DisableConnectTo ||
2567
+ lineAnchor.twoWay === TwoWay.Disable
2568
+ ) {
2569
+ return;
2570
+ }
2571
+
2572
+ if (anchor.twoWay === TwoWay.In) {
2573
+ if (line.calculative.worldAnchors.length === 1) {
2574
+ return;
2575
+ }
2576
+ const to = getToAnchor(line);
2577
+ if (lineAnchor.id !== to.id) {
2578
+ return;
2579
+ }
2580
+ }
2581
+
2582
+ if (anchor.twoWay === TwoWay.Out) {
2583
+ const from = getFromAnchor(line);
2584
+ if (lineAnchor.id !== from.id) {
2585
+ return;
2586
+ }
2587
+ }
2588
+
2589
+ if (lineAnchor.connectTo === pen.id && lineAnchor.anchorId === anchor.id) {
2590
+ return;
2591
+ }
2592
+
2593
+ if (lineAnchor.connectTo) {
2594
+ const p = pen.calculative.canvas.store.pens[lineAnchor.connectTo];
2595
+ disconnectLine(p, getAnchor(p, lineAnchor.anchorId), line, lineAnchor);
2596
+ }
2597
+
2598
+ if (!pen.connectedLines) {
2599
+ pen.connectedLines = [];
2600
+ }
2601
+
2602
+ const i = pen.connectedLines.findIndex(
2603
+ (item) =>
2604
+ item.lineId === line.id &&
2605
+ item.lineAnchor === lineAnchor.id &&
2606
+ item.anchor === anchor.id
2607
+ );
2608
+
2609
+ if (i < 0) {
2610
+ pen.connectedLines.push({
2611
+ lineId: line.id,
2612
+ lineAnchor: lineAnchor.id,
2613
+ anchor: anchor.id,
2614
+ });
2615
+ }
2616
+
2617
+ lineAnchor.connectTo = pen.id;
2618
+ lineAnchor.anchorId = anchor.id;
2619
+
2620
+ // 如果两条连线,则相互关联
2621
+ if (pen.type) {
2622
+ connectLine(line, lineAnchor, pen, anchor);
2623
+ }
2624
+
2625
+ pen.calculative.canvas.store.emitter.emit('connectLine', {
2626
+ line,
2627
+ lineAnchor,
2628
+ pen,
2629
+ anchor,
2630
+ });
2631
+ // 新增连线生命周期
2632
+ let fromPen = (line.calculative.worldAnchors?.length >= 2)? line.calculative.worldAnchors?.[0].connectTo : undefined;
2633
+ let fromAnchor = (line.calculative.worldAnchors?.length >= 2)?line.calculative.canvas.store.pens[line.calculative.worldAnchors?.[0].connectTo]?.anchors.find(i=>i.id === line.calculative.worldAnchors?.[0].anchorId): undefined; // num
2634
+ pen.onConnectLine?.(pen,{line,lineAnchor,pen,anchor,fromPen,fromAnchor});
2635
+ return true;
2636
+ }
2637
+
2638
+ /**
2639
+ * 从 pen.connectedLines 中删除 lineId 和 lineAnchor
2640
+ */
2641
+ export function disconnectLine(
2642
+ pen: Pen,
2643
+ anchor: Point,
2644
+ line: Pen,
2645
+ lineAnchor: Point
2646
+ ) {
2647
+ if (!pen || !anchor || !line || !lineAnchor) {
2648
+ return;
2649
+ }
2650
+
2651
+ if (!pen.connectedLines || !pen.connectedLines.length) {
2652
+ return;
2653
+ }
2654
+
2655
+ if (!line.lastConnected) {
2656
+ line.lastConnected = {};
2657
+ }
2658
+ if (!line.lastConnected[pen.id]) {
2659
+ line.lastConnected[pen.id] = deepClone(pen.connectedLines);
2660
+ }
2661
+ pen.connectedLines.forEach((item, index, arr) => {
2662
+ if (
2663
+ (item.lineId === line.id || item.lineId === line.id) &&
2664
+ item.lineAnchor === lineAnchor.id &&
2665
+ item.anchor === anchor.id
2666
+ ) {
2667
+ arr.splice(index, 1);
2668
+ }
2669
+ });
2670
+
2671
+ lineAnchor.connectTo = undefined;
2672
+ lineAnchor.anchorId = undefined;
2673
+ // 如果两条连线相互关联,则都取消关联
2674
+ if (
2675
+ pen.type &&
2676
+ anchor.connectTo === line.id &&
2677
+ anchor.anchorId === lineAnchor.id
2678
+ ) {
2679
+ disconnectLine(line, lineAnchor, pen, anchor);
2680
+ }
2681
+
2682
+ pen.calculative.canvas.store.emitter.emit('disconnectLine', {
2683
+ line,
2684
+ lineAnchor,
2685
+ pen,
2686
+ anchor,
2687
+ });
2688
+
2689
+ return true;
2690
+ }
2691
+
2692
+ export function getAnchor(pen: Pen, anchorId: string) {
2693
+ if (!pen || !anchorId) {
2694
+ return;
2695
+ }
2696
+
2697
+ return pen.calculative.worldAnchors?.find((item) => item.id === anchorId);
2698
+ }
2699
+
2700
+ export function getFromAnchor(pen: Pen) {
2701
+ if (!pen || !pen.calculative.worldAnchors) {
2702
+ return;
2703
+ }
2704
+
2705
+ return pen.calculative.worldAnchors[0];
2706
+ }
2707
+
2708
+ export function getToAnchor(pen: Pen) {
2709
+ if (!pen || !pen.calculative.worldAnchors) {
2710
+ return;
2711
+ }
2712
+
2713
+ return pen.calculative.worldAnchors[pen.calculative.worldAnchors.length - 1];
2714
+ }
2715
+
2716
+ export function setNodeAnimate(pen: Pen, now: number) {
2717
+ if (pen.calculative.start === 0 || !pen.frames || !pen.frames.length) {
2718
+ pen.calculative.start = undefined;
2719
+ return 0;
2720
+ }
2721
+ if (!pen.calculative.duration) {
2722
+ pen.calculative.duration = 0;
2723
+ for (const f of pen.frames) {
2724
+ pen.calculative.duration += f.duration;
2725
+ for (const k in f) {
2726
+ if (k !== 'duration' && !pen[k]) {
2727
+ if (k === 'scale') {
2728
+ pen[k] = 1;
2729
+ }
2730
+ }
2731
+ }
2732
+ }
2733
+ }
2734
+ if (!pen.animateCycle) {
2735
+ pen.animateCycle = Infinity;
2736
+ }
2737
+
2738
+ if (!pen.calculative.start) {
2739
+ pen.calculative.start = now;
2740
+ pen.calculative.frameIndex = 0;
2741
+ pen.calculative.frameStart = pen.calculative.start;
2742
+ pen.calculative.frameDuration = pen.frames[0].duration;
2743
+ pen.calculative.frameEnd =
2744
+ pen.calculative.frameStart + pen.calculative.frameDuration;
2745
+ pen.calculative.cycleIndex = 1;
2746
+ pen.calculative.x = pen.calculative.worldRect.x;
2747
+ pen.calculative.y = pen.calculative.worldRect.y;
2748
+ pen.calculative.initRect = deepClone(pen.calculative.worldRect);
2749
+ if (pen.parentId) {
2750
+ pen.calculative.initRelativeRect = {
2751
+ x: pen.x,
2752
+ y: pen.y,
2753
+ width: pen.width,
2754
+ height: pen.height,
2755
+ };
2756
+ }
2757
+ if (pen.children?.length) {
2758
+ const store = pen.calculative.canvas.store;
2759
+ pen.calculative.childrenVisible = {};
2760
+ pen.children.forEach((id) => {
2761
+ pen.calculative.childrenVisible[id] = store.pens[id].visible;
2762
+ });
2763
+ }
2764
+ pen.calculative.initRect.rotate = pen.calculative.rotate || 0;
2765
+
2766
+ initPrevFrame(pen);
2767
+ } else {
2768
+ let frameIndex = 0;
2769
+ const cycleIndex = Math.ceil(
2770
+ (now - pen.calculative.start) / pen.calculative.duration
2771
+ );
2772
+ // 播放结束
2773
+ if (cycleIndex > pen.animateCycle) {
2774
+ pen.currentAnimation = undefined;
2775
+ pen.calculative.start = undefined;
2776
+ setNodeAnimateProcess(pen, 1);
2777
+ return 0;
2778
+ }
2779
+
2780
+ const pos = (now - pen.calculative.start) % pen.calculative.duration;
2781
+ let d = 0;
2782
+ for (const frame of pen.frames) {
2783
+ d += frame.duration;
2784
+ if (pos > d) {
2785
+ ++frameIndex;
2786
+ } else {
2787
+ break;
2788
+ }
2789
+ }
2790
+ // 帧超出
2791
+ if (!pen.frames[frameIndex]) {
2792
+ return true;
2793
+ }
2794
+
2795
+ pen.calculative.frameDuration = pen.frames[frameIndex].duration;
2796
+ pen.calculative.frameStart =
2797
+ pen.calculative.start + pen.calculative.duration * (cycleIndex - 1);
2798
+ pen.calculative.frameEnd =
2799
+ pen.calculative.frameStart + pen.calculative.frameDuration;
2800
+
2801
+ // 换帧
2802
+ const frameChanged = frameIndex !== pen.calculative.frameIndex;
2803
+ // 新循环播放
2804
+ const cycleChanged = cycleIndex > pen.calculative.cycleIndex;
2805
+
2806
+ frameChanged && (pen.calculative.frameIndex = frameIndex);
2807
+ cycleChanged && (pen.calculative.cycleIndex = cycleIndex);
2808
+
2809
+ if (frameChanged || cycleChanged) {
2810
+ // 以初始位置为参考点。因为网页在后台时,不执行动画帧,网页恢复显示时,位置不确定
2811
+ pen.calculative.x = pen.calculative.initRect.x;
2812
+ pen.calculative.y = pen.calculative.initRect.y;
2813
+ if (pen.children?.length && !pen.parentId) {
2814
+ pen.calculative.canvas.rotatePen(
2815
+ pen,
2816
+ (pen.calculative.initRect.rotate || 0) - (pen.calculative.rotate||0),
2817
+ pen.calculative.initRect
2818
+ );
2819
+ } else {
2820
+ pen.calculative.rotate = pen.calculative.initRect.rotate || 0;
2821
+ }
2822
+ if (frameIndex > 0) {
2823
+ pen.prevFrame = {};
2824
+ const prevFrame = pen.frames[frameIndex - 1];
2825
+ for (const k in prevFrame) {
2826
+ pen.prevFrame[k] = prevFrame[k];
2827
+ }
2828
+ Object.assign(pen.prevFrame, {
2829
+ rotate: prevFrame.rotate || 0,
2830
+ x: prevFrame.x || 0,
2831
+ y: prevFrame.y || 0,
2832
+ scale: prevFrame.scale || 1,
2833
+ });
2834
+ } else {
2835
+ initPrevFrame(pen);
2836
+ }
2837
+ }
2838
+ }
2839
+
2840
+ const process =
2841
+ ((now - pen.calculative.frameStart) / pen.calculative.frameDuration) % 1;
2842
+ setNodeAnimateProcess(pen, process);
2843
+
2844
+ return true;
2845
+ }
2846
+
2847
+ // 把前一个动画帧初始化为播放前状态
2848
+ export function initPrevFrame(pen: Pen) {
2849
+ pen.prevFrame = {};
2850
+ for (const k in pen) {
2851
+ if (typeof pen[k] !== 'object' || k === 'lineDash') {
2852
+ pen.prevFrame[k] = pen[k];
2853
+ }
2854
+ }
2855
+ pen.prevFrame.rotate = 0;
2856
+ pen.prevFrame.x = 0;
2857
+ pen.prevFrame.y = 0;
2858
+ pen.prevFrame.scale = 1;
2859
+ }
2860
+
2861
+ // 根据process进度值(纯小数),计算节点动画属性
2862
+ export function setNodeAnimateProcess(pen: Pen, process: number) {
2863
+ if (process < 0) {
2864
+ return;
2865
+ }
2866
+
2867
+ if (process > 1) {
2868
+ process = 1;
2869
+ }
2870
+
2871
+ const frame = pen.frames[pen.calculative.frameIndex];
2872
+ for (const k in frame) {
2873
+ if (k === 'duration') {
2874
+ continue;
2875
+ } else if (k === 'scale') {
2876
+ pen.calculative.worldRect = deepClone(pen.calculative.initRect);
2877
+ scaleRect(
2878
+ pen.calculative.worldRect,
2879
+ pen.prevFrame.scale,
2880
+ pen.calculative.worldRect.center
2881
+ );
2882
+ const newScale =
2883
+ pen.prevFrame.scale + (frame[k] - pen.prevFrame.scale) * process;
2884
+ scaleRect(
2885
+ pen.calculative.worldRect,
2886
+ newScale / pen.prevFrame.scale,
2887
+ pen.calculative.worldRect.center
2888
+ );
2889
+ pen.calculative.patchFlags = true;
2890
+ } else if (k === 'x') {
2891
+ const lastVal = getFrameValue(pen, k, pen.calculative.frameIndex);
2892
+ pen.calculative.worldRect.x = pen.calculative.initRect.x + lastVal;
2893
+ pen.calculative.worldRect.ex = pen.calculative.initRect.ex + lastVal;
2894
+ translateRect(
2895
+ pen.calculative.worldRect,
2896
+ frame[k] * process * pen.calculative.canvas.store.data.scale,
2897
+ 0
2898
+ );
2899
+ pen.calculative.patchFlags = true;
2900
+ } else if (k === 'y') {
2901
+ const lastVal = getFrameValue(pen, k, pen.calculative.frameIndex);
2902
+ pen.calculative.worldRect.y = pen.calculative.initRect.y + lastVal;
2903
+ pen.calculative.worldRect.ey = pen.calculative.initRect.ey + lastVal;
2904
+ translateRect(
2905
+ pen.calculative.worldRect,
2906
+ 0,
2907
+ frame[k] * process * pen.calculative.canvas.store.data.scale
2908
+ );
2909
+ pen.calculative.patchFlags = true;
2910
+ } else if (k === 'rotate') {
2911
+ if (pen.prevFrame[k] >= 360) {
2912
+ pen.prevFrame[k] %= 360;
2913
+ }
2914
+ const lastVal = getFrameValue(pen, k, pen.calculative.frameIndex);
2915
+ const offsetRotate =
2916
+ ((pen.calculative.initRect.rotate + lastVal + frame[k] * process) %
2917
+ 360) -
2918
+ (pen.calculative.rotate || 0);
2919
+ if (pen.children?.length) {
2920
+ pen.calculative.canvas.rotatePen(
2921
+ pen,
2922
+ offsetRotate,
2923
+ pen.calculative.initRect
2924
+ );
2925
+ } else {
2926
+ pen.calculative.rotate =
2927
+ (pen.calculative.initRect.rotate + lastVal + frame[k] * process) %
2928
+ 360;
2929
+ }
2930
+ pen.calculative.patchFlags = true;
2931
+ } else if (k === 'image') {
2932
+ pen.image = frame['image'];
2933
+ pen.calculative.image = undefined;
2934
+ pen.calculative.canvas.loadImage(pen);
2935
+ // if (pen.isBottom) {
2936
+ // pen.calculative.canvas.canvasImageBottom.init();
2937
+ // } else {
2938
+ // pen.calculative.canvas.canvasImage.init();
2939
+ // }
2940
+ if (pen.canvasLayer === CanvasLayer.CanvasImageBottom) {
2941
+ pen.calculative.canvas.canvasImageBottom.init();
2942
+ } else if (pen.canvasLayer === CanvasLayer.CanvasImage) {
2943
+ pen.calculative.canvas.canvasImage.init();
2944
+ }
2945
+ } else if (isLinear(frame[k], k, pen)) {
2946
+ if (pen.prevFrame[k] == null) {
2947
+ if (k === 'globalAlpha') {
2948
+ pen.prevFrame[k] = 1;
2949
+ } else {
2950
+ pen.prevFrame[k] = 0;
2951
+ }
2952
+ }
2953
+
2954
+ const current =
2955
+ pen.prevFrame[k] + (frame[k] - pen.prevFrame[k]) * process;
2956
+ pen.calculative[k] = Math.round(current * 100) / 100;
2957
+ } else {
2958
+ if (k === 'visible') {
2959
+ if (pen.calculative.image) {
2960
+ // if (pen.isBottom) {
2961
+ // pen.calculative.canvas.canvasImageBottom.init();
2962
+ // } else {
2963
+ // pen.calculative.canvas.canvasImage.init();
2964
+ // }
2965
+ if (pen.canvasLayer === CanvasLayer.CanvasImageBottom) {
2966
+ pen.calculative.canvas.canvasImageBottom.init();
2967
+ } else if (pen.canvasLayer === CanvasLayer.CanvasImage) {
2968
+ pen.calculative.canvas.canvasImage.init();
2969
+ }
2970
+ } else if(pen.children?.length) {
2971
+ const childs = getAllChildren(pen, pen.calculative.canvas.store);
2972
+ pen.calculative.canvas.initImageCanvas(childs);
2973
+ }
2974
+ }
2975
+ pen.calculative[k] = frame[k];
2976
+ const v: any = {};
2977
+ v[k] = frame[k];
2978
+ setChildValue(pen, v);
2979
+ }
2980
+
2981
+ if (k === 'text') {
2982
+ calcTextLines(pen);
2983
+ }
2984
+ }
2985
+ }
2986
+
2987
+ /**
2988
+ * 值类型为 number , pen.linear 为 false 时,且 key 不属于 noLinear 时,返回 true
2989
+ * @param value 值
2990
+ * @param key 键值
2991
+ * @param pen 画笔
2992
+ * @returns
2993
+ */
2994
+ function isLinear(value: unknown, key: string, pen: Pen): boolean {
2995
+ // 不线性变化的属性
2996
+ const noLinear = ['strokeType', 'bkType', 'showChild'] as const;
2997
+ type NoLinear = (typeof noLinear)[number];
2998
+ return (
2999
+ typeof value === 'number' &&
3000
+ pen.linear !== false &&
3001
+ !noLinear.includes(key as NoLinear)
3002
+ );
3003
+ }
3004
+
3005
+ export function setLineAnimate(pen: Pen, now: number) {
3006
+ if (pen.calculative.start === 0) {
3007
+ pen.calculative.start = undefined;
3008
+ return 0;
3009
+ }
3010
+
3011
+ if (!pen.animateCycle) {
3012
+ pen.animateCycle = Infinity;
3013
+ }
3014
+
3015
+ if (!pen.animateSpan) {
3016
+ pen.animateSpan = 1;
3017
+ }
3018
+
3019
+ pen.calculative.animatePos +=
3020
+ pen.animateSpan * (pen.calculative.canvas.store.data.scale || 1);
3021
+ if (!pen.calculative.start) {
3022
+ pen.calculative.start = Date.now();
3023
+ pen.calculative.animatePos =
3024
+ pen.animateSpan * (pen.calculative.canvas.store.data.scale || 1);
3025
+ pen.calculative.cycleIndex = 1;
3026
+ } else if (pen.calculative.animatePos > pen.length) {
3027
+ // 播放到尾了
3028
+ ++pen.calculative.cycleIndex;
3029
+
3030
+ // 播放结束
3031
+ if (pen.calculative.cycleIndex > pen.animateCycle) {
3032
+ pen.currentAnimation = undefined;
3033
+ pen.calculative.start = undefined;
3034
+ return 0;
3035
+ }
3036
+ pen.calculative.animatePos = pen.animateSpan;
3037
+ }
3038
+
3039
+ return true;
3040
+ }
3041
+
3042
+ export function setChildrenActive(pen: Pen, active = true) {
3043
+ if (!pen.children || pen.childActive === false) {
3044
+ return;
3045
+ }
3046
+ const store = pen.calculative.canvas.store;
3047
+ pen.children.forEach((id) => {
3048
+ const child: Pen = store.pens[id];
3049
+ if (child) {
3050
+ child.calculative.active = active;
3051
+
3052
+ setChildrenActive(child, active);
3053
+ }
3054
+ });
3055
+ }
3056
+
3057
+ export function setHover(pen: Pen, hover = true) {
3058
+ if (!pen) {
3059
+ return;
3060
+ }
3061
+ const store = pen.calculative.canvas.store;
3062
+ pen.calculative.hover = hover;
3063
+ if(pen.childHover === false){
3064
+ return;
3065
+ }
3066
+ if (pen.children) {
3067
+ pen.children.forEach((id) => {
3068
+ // 子节点没有自己的独立hover,继承父节点hover
3069
+ if (
3070
+ store.pens[id]?.hoverColor == undefined &&
3071
+ store.pens[id]?.hoverBackground == undefined
3072
+ ) {
3073
+ setHover(store.pens[id], hover);
3074
+ }
3075
+ });
3076
+ }
3077
+ }
3078
+
3079
+ export function setElemPosition(pen: Pen, elem: HTMLElement) {
3080
+ if (!elem) {
3081
+ return;
3082
+ }
3083
+ const store = pen.calculative.canvas.store;
3084
+ const worldRect = pen.calculative.worldRect;
3085
+ elem.style.opacity = pen.globalAlpha + '';
3086
+ elem.style.position = 'absolute';
3087
+ elem.style.outline = 'none';
3088
+ elem.style.left = worldRect.x + store.data.x + 'px';
3089
+ elem.style.top = worldRect.y + store.data.y + 'px';
3090
+ elem.style.width = worldRect.width + 'px';
3091
+ elem.style.height = worldRect.height + 'px';
3092
+ elem.style.display =
3093
+ pen.calculative.inView != false
3094
+ ? pen.calculative.cssDisplay || 'inline'
3095
+ : 'none'; // 是否隐藏元素
3096
+ !pen.calculative.rotate && (pen.calculative.rotate = 0);
3097
+ elem.style.transform = `rotate(${pen.calculative.rotate}deg)`;
3098
+ if (!pen.calculative.rotate) {
3099
+ if (pen.calculative.flipX) {
3100
+ elem.style.transform = `rotateY(180deg)`;
3101
+ }
3102
+ if (pen.calculative.flipY) {
3103
+ elem.style.transform = `rotateX(180deg)`;
3104
+ }
3105
+ if (pen.calculative.flipX && pen.calculative.flipY) {
3106
+ elem.style.transform = `rotateZ(180deg)`;
3107
+ }
3108
+ }
3109
+ elem.style.zIndex =
3110
+ pen.calculative.zIndex !== undefined ? pen.calculative.zIndex + '' : '5';
3111
+ if (pen.calculative.zIndex > pen.calculative.canvas.maxZindex) {
3112
+ pen.calculative.canvas.maxZindex = pen.calculative.zIndex;
3113
+ }
3114
+ if (
3115
+ pen.locked === LockState.DisableEdit ||
3116
+ pen.locked === LockState.DisableMove ||
3117
+ store.data.locked
3118
+ ) {
3119
+ // gif 组合后,作为子节点可通过 lockedOnCombine 来决定自身的 locked 状态
3120
+ elem.style.userSelect = 'initial';
3121
+ elem.style.pointerEvents = 'initial';
3122
+ if (pen.name === 'gif') {
3123
+ elem.style.userSelect = 'none';
3124
+ elem.style.pointerEvents = 'none';
3125
+ }
3126
+ } else {
3127
+ // pen.locked LockState.Disable 不响应鼠标
3128
+ elem.style.userSelect = 'none';
3129
+ elem.style.pointerEvents = 'none';
3130
+ }
3131
+ }
3132
+
3133
+ export function setElemImg(pen: Pen, elem: HTMLElement) {
3134
+ if (!elem) {
3135
+ return;
3136
+ }
3137
+ //https://github.com/niklasvh/html2canvas
3138
+ globalThis.html2canvas &&
3139
+ globalThis.html2canvas(elem).then(function (canvas) {
3140
+ // document.body.appendChild(canvas);
3141
+ const img = new Image();
3142
+ img.src = canvas.toDataURL('image/png', 0.1);
3143
+ if (img.src.length > 10) {
3144
+ pen.calculative.img = img;
3145
+ }
3146
+ });
3147
+ }
3148
+
3149
+ /**
3150
+ * 每个画笔 locked
3151
+ * @param pens 画笔
3152
+ * @returns
3153
+ */
3154
+ export function getPensLock(pens: Pen[]): boolean {
3155
+ return pens.every((pen) => pen.locked);
3156
+ }
3157
+
3158
+ /**
3159
+ * 画笔们的 disabledRotate = true
3160
+ * 即 全部禁止旋转 返回 true
3161
+ * @param pens 画笔
3162
+ * @returns
3163
+ */
3164
+ export function getPensDisableRotate(pens: Pen[]): boolean {
3165
+ return pens.every((pen) => pen.disableRotate);
3166
+ }
3167
+
3168
+ export function rotatePen(pen: Pen, angle: number, rect: Rect) {
3169
+ if (pen.type) {
3170
+ pen.calculative.worldAnchors.forEach((anchor) => {
3171
+ rotatePoint(anchor, angle, rect.center);
3172
+ });
3173
+ initLineRect(pen);
3174
+ calcPenRect(pen);
3175
+ } else {
3176
+ if (pen.calculative.rotate) {
3177
+ pen.calculative.rotate += angle;
3178
+ } else {
3179
+ pen.calculative.rotate = angle;
3180
+ }
3181
+ rotatePoint(pen.calculative.worldRect.center, angle, rect.center);
3182
+ if (pen.parentId) {
3183
+ pen.calculative.worldRect.x =
3184
+ pen.calculative.worldRect.center.x -
3185
+ pen.calculative.worldRect.width / 2;
3186
+ pen.calculative.worldRect.y =
3187
+ pen.calculative.worldRect.center.y -
3188
+ pen.calculative.worldRect.height / 2;
3189
+ pen.x = (pen.calculative.worldRect.x - rect.x) / rect.width;
3190
+ pen.y = (pen.calculative.worldRect.y - rect.y) / rect.height;
3191
+ }
3192
+ }
3193
+
3194
+ pen.children?.forEach((id) => {
3195
+ const child = pen.calculative.canvas.store.pens[id];
3196
+ rotatePen(child, angle, rect);
3197
+ });
3198
+ }
3199
+
3200
+ function initLineRect(pen: Pen) {
3201
+ if (!pen.calculative.worldAnchors?.length) {
3202
+ return;
3203
+ }
3204
+ if (!isFinite(pen.x) || !isFinite(pen.x)) {
3205
+ return;
3206
+ }
3207
+ if (pen.x == null || pen.y == null) {
3208
+ return;
3209
+ }
3210
+ const rect = getLineRect(pen);
3211
+ if (!pen.parentId) {
3212
+ Object.assign(pen, rect);
3213
+ }
3214
+ const { fontSize, lineHeight } = pen.calculative.canvas.store.options;
3215
+ if (!pen.fontSize) {
3216
+ pen.fontSize = fontSize >= 0 ? fontSize : 12;
3217
+ pen.calculative.fontSize =
3218
+ pen.fontSize * pen.calculative.canvas.store.data.scale;
3219
+ } else if(pen.fontSize < 0) {
3220
+ pen.fontSize = 0;
3221
+ pen.calculative.fontSize = 0;
3222
+ }
3223
+ if (!pen.lineHeight) {
3224
+ pen.lineHeight = lineHeight;
3225
+ pen.calculative.lineHeight = pen.lineHeight;
3226
+ }
3227
+ calcCenter(rect);
3228
+ pen.calculative.worldRect = rect;
3229
+ calcPadding(pen, rect);
3230
+ calcTextRect(pen);
3231
+ if (pen.calculative.worldAnchors) {
3232
+ pen.anchors = pen.calculative.worldAnchors.map((pt) => {
3233
+ return calcRelativePoint(pt, pen.calculative.worldRect);
3234
+ });
3235
+ }
3236
+ }
3237
+
3238
+ /**
3239
+ * 画笔们的 disableSize = true
3240
+ * 即 全部不允许改变大小 返回 true
3241
+ * @param pens 画笔
3242
+ * @returns
3243
+ */
3244
+ export function getPensDisableResize(pens: Pen[]): boolean {
3245
+ return pens.every((pen) => pen.disableSize || pen.pivot); //旋转中心点图元不允许改变大小
3246
+ }
3247
+
3248
+ export function getFrameValue(pen: Pen, prop: string, frameIndex: number) {
3249
+ if (!pen.frames || !prop) {
3250
+ return 0;
3251
+ }
3252
+
3253
+ let v = 0;
3254
+ for (let i = 0; i < frameIndex; i++) {
3255
+ if (pen.frames[i]) {
3256
+ v += pen.frames[i][prop] || 0;
3257
+ }
3258
+ }
3259
+
3260
+ return v;
3261
+ }
3262
+
3263
+ /**
3264
+ * 判断该画笔 是否是组合为状态中 展示的画笔
3265
+ */
3266
+ export function isShowChild(pen: Pen, store: Meta2dStore) {
3267
+ let selfPen = pen;
3268
+ while (selfPen && selfPen.parentId) {
3269
+ const oldPen = selfPen;
3270
+ selfPen = store.pens[selfPen.parentId];
3271
+ const showChildIndex = selfPen?.calculative?.showChild;
3272
+ if (showChildIndex != undefined) {
3273
+ const showChildId = selfPen.children[showChildIndex];
3274
+ if (showChildId !== oldPen.id) {
3275
+ // toPng 不展示它
3276
+ return false;
3277
+ }
3278
+ }
3279
+ }
3280
+ return true;
3281
+ }
3282
+
3283
+ /**
3284
+ * 计算画笔的 inView
3285
+ * @param pen 画笔
3286
+ * @param calcChild 是否计算子画笔
3287
+ */
3288
+ export function calcInView(pen: Pen, calcChild = false) {
3289
+ const { store, canvasRect } = pen.calculative.canvas as Canvas;
3290
+ if (calcChild) {
3291
+ pen.children?.forEach((id) => {
3292
+ const child = store.pens[id];
3293
+ child && calcInView(child, true);
3294
+ });
3295
+ }
3296
+
3297
+ pen.calculative.inView = true;
3298
+ if (
3299
+ !isShowChild(pen, store) ||
3300
+ pen.visible == false ||
3301
+ pen.calculative.visible == false
3302
+ ) {
3303
+ pen.calculative.inView = false;
3304
+ } else {
3305
+ const { x, y, width, height, rotate } = pen.calculative.worldRect;
3306
+ const penRect: Rect = {
3307
+ x: x + store.data.x,
3308
+ y: y + store.data.y,
3309
+ width,
3310
+ height,
3311
+ rotate,
3312
+ };
3313
+ calcRightBottom(penRect);
3314
+ if (!rectInRect(penRect, canvasRect)) {
3315
+ pen.calculative.inView = false;
3316
+ }
3317
+ }
3318
+ // TODO: 语义化上,用 onValue 更合适,但 onValue 会触发 echarts 图形的重绘,没有必要
3319
+ // 更改 view 后,修改 dom 节点的显示隐藏
3320
+ // pen.onMove?.(pen);
3321
+ pen.calculative.singleton?.div && setElemPosition(pen, pen.calculative.singleton.div);
3322
+ }
3323
+
3324
+ /**
3325
+ * 绘制 rect ,上线后可查看 rect 位置
3326
+ */
3327
+ function inspectRect(
3328
+ ctx: CanvasRenderingContext2D,
3329
+ store: Meta2dStore,
3330
+ pen: Pen
3331
+ ) {
3332
+ if (store.fillWorldTextRect) {
3333
+ ctx.save();
3334
+ ctx.fillStyle = '#c3deb7';
3335
+ const { x, y, width, height } = pen.calculative.worldTextRect;
3336
+ ctx.fillRect(x, y, width, height);
3337
+ ctx.restore();
3338
+ }
3339
+ }
3340
+
3341
+ export function setGlobalAlpha(
3342
+ ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
3343
+ pen: Pen
3344
+ ) {
3345
+ const globalAlpha = pen.calculative.globalAlpha;
3346
+ if (
3347
+ typeof globalAlpha === 'number' &&
3348
+ globalAlpha < 1 &&
3349
+ !isNaN(globalAlpha)
3350
+ ) {
3351
+ ctx.globalAlpha = globalAlpha;
3352
+ }
3353
+ }
3354
+
3355
+ /**
3356
+ * ctx 绘制图纸,并非 Path2D
3357
+ * @param ctx 画布上下文
3358
+ * @param pen 画笔
3359
+ */
3360
+ function ctxDrawCanvas(ctx: CanvasRenderingContext2D, pen: Pen) {
3361
+ const canvasDraw = drawFuncGenerator(ctx,pen) || globalStore.canvasDraws[pen.name];
3362
+ if (canvasDraw) {
3363
+ // TODO: 后续考虑优化 save / restore
3364
+ ctx.save();
3365
+ // TODO: 原有 return 终止后续操作,必要性不大
3366
+ canvasDraw(ctx, pen);
3367
+ ctx.restore();
3368
+ }
3369
+ }
3370
+ function drawFuncGenerator(ctx: CanvasRenderingContext2D, pen: any) {
3371
+ // 进行数据的预处理
3372
+ const drawCommand:Array<any> = pen.drawCommand;
3373
+ if(!drawCommand || pen.name === 'line')return ;
3374
+ // 单位转换 将其他单位转换为px
3375
+
3376
+ // 执行自定义绘画函数
3377
+ return (ctx: CanvasRenderingContext2D,pen: Pen)=> {
3378
+ // TODO 绘制命令的转换 (能否兼容多种指令??)
3379
+ drawCommand.forEach((command)=> {
3380
+ try {
3381
+ command.steps = command.steps.flat(Infinity);
3382
+ command.steps.reduce((calculate,step)=>{
3383
+ const cs = commandTransfer(step,pen,calculate.x,calculate.y);
3384
+ // 应当保证顺序的正确
3385
+ try {
3386
+ if(cs.c){
3387
+ if(cs.c.startsWith('_')){
3388
+ const prop = cs.c.split('_')[1];
3389
+ // debugger;
3390
+ (cs.p || ctx)[prop] = cs.v.value;
3391
+ return {x:calculate.x,y:calculate.y};
3392
+ }
3393
+ let l = [];
3394
+ for (const csKey in cs.v) {
3395
+ l.push(cs.v[csKey]);
3396
+ }
3397
+ // ctx.beginPath();
3398
+ (cs.p || ctx)[cs.c](...l);
3399
+ ctx.moveTo(cs.startX || cs.v.x,cs.startY || cs.v.y);
3400
+ // command.prop.NoFill === '0'?ctx.fill():'';
3401
+ return {x:cs.startX || cs.v.x, y:cs.startY||cs.v.y};
3402
+ }
3403
+ return {x:calculate.x,y:calculate.y};
3404
+ }catch (e) {
3405
+ // pass
3406
+ console.log(e,'error',cs);
3407
+ }
3408
+ },{});
3409
+ }
3410
+ catch (e) {
3411
+ }
3412
+ });
3413
+ ctx.stroke();
3414
+ };
3415
+ }
3416
+
3417
+ function commandTransfer(command,pen,startX,startY){
3418
+
3419
+ // TODO 是否支持扩展更多的命令?用于兼容未来的其他解析格式?
3420
+ //1. 进行简单的命令解析
3421
+ // VISIO
3422
+ const map = {
3423
+ 'visio':dealWithVisio,
3424
+ 'dxf':dealWithDXF
3425
+ };
3426
+ // CAD
3427
+ return map[pen.parseType](command,pen,startX,startY);
3428
+ }
3429
+
3430
+ function dealWithDXF(command,pen,startX,startY) {
3431
+ const { x, y, width, height } = pen.calculative.worldRect;
3432
+ const {originWidth,originHeight} = pen.dxfOrigin;
3433
+ switch (command.c) {
3434
+ case "beginPath":
3435
+ return {
3436
+ c:'beginPath',
3437
+ v:{}
3438
+ };
3439
+ case "closePath":
3440
+ return {
3441
+ c:'closePath',
3442
+ v:{}
3443
+ };
3444
+ case "moveTo":
3445
+ return {
3446
+ c:'moveTo',
3447
+ v:{
3448
+ x: command.v.x * (width / originWidth) + x,
3449
+ y: command.v.y * (height / originHeight) + y
3450
+ }
3451
+ };
3452
+ case "lineTo":
3453
+ return {
3454
+ c:'lineTo',
3455
+ v:{
3456
+ x: command.v.x * (width / originWidth) + x,
3457
+ y: command.v.y * (height / originHeight) + y
3458
+ }
3459
+ };
3460
+ case "arc":
3461
+ return {
3462
+ c:'ellipse',
3463
+ v:{
3464
+ x:command.v.x * (width / originWidth) + x,
3465
+ y:(command.v.y * (height / originHeight)) + y,
3466
+ rx:command.v.xr * (width / originWidth),
3467
+ ry:command.v.yr * (height / originHeight),
3468
+ rotation:command.v.rotation || 0,
3469
+ startAngle:command.v.startAngle,
3470
+ endAngle: command.v.endAngle,
3471
+ a:command.v.aclockwise ?? true
3472
+ }
3473
+ };
3474
+ case "ellipse":
3475
+ return {
3476
+ c:'ellipse',
3477
+ v:{
3478
+ x:command.v.x * (width / originWidth) + x,
3479
+ y:(command.v.y * (height / originHeight)) + y,
3480
+ rx:command.v.xr * (width / originWidth),
3481
+ ry:command.v.yr * (height / originHeight),
3482
+ rotation:command.v.rotation || 0,
3483
+ startAngle:command.v.startAngle,
3484
+ endAngle: command.v.endAngle,
3485
+ a:command.v.aclockwise ?? true
3486
+ }
3487
+ };
3488
+ case "_font":
3489
+ return {
3490
+ c:'_font',
3491
+ v:{
3492
+ value:command.v.fontSize * meta2d.store.data.scale + 'px ' + (command.v.fontFamily || meta2d.store.options.fontFamily)
3493
+ }
3494
+ };
3495
+ case "_fillStyle":
3496
+ return {
3497
+ c:'_fillStyle',
3498
+ v:{
3499
+ value:pen.color || command.v.value
3500
+ }
3501
+ };
3502
+ default:
3503
+ const c = {
3504
+ c:command.c,
3505
+ v:{
3506
+ ...command.v,
3507
+ x:command.v.x * (width / originWidth) + x,
3508
+ y:(command.v.y * (height / originHeight)) + y
3509
+ }
3510
+ };
3511
+ !command.v.x && delete c.v.x;
3512
+ !command.v.y && delete c.v.y;
3513
+ return c;
3514
+ }
3515
+ }
3516
+ function dealWithVisio(command,pen,startX,startY) {
3517
+ const { x, y, width, height } = pen.calculative.worldRect;
3518
+ const { width:originWidth, height:originHeight} = pen.origin;
3519
+ switch (command.c) {
3520
+ case "MoveTo":
3521
+ return {
3522
+ c:"moveTo",
3523
+ v:{
3524
+ x:+ command.v.X * (100) * (width / originWidth) + x,
3525
+ y:+ command.v.Y * (100) * (height / originHeight) + y
3526
+ }
3527
+ };
3528
+ case "RelMoveTo":
3529
+ return {
3530
+ c:"moveTo",
3531
+ v:{
3532
+ x:+ command.v.X * originWidth * (width / originWidth) + x,
3533
+ y:+ command.v.Y * originHeight * (height / originHeight) + y
3534
+ }
3535
+ };
3536
+ case "LineTo":
3537
+ return {
3538
+ c: "lineTo",
3539
+ v:{
3540
+ x:+ command.v.X * (100) * (width / originWidth) + x,
3541
+ y:+ command.v.Y * (100) * (height / originHeight) + y
3542
+ }
3543
+ };
3544
+ case "RelLineTo":
3545
+ return {
3546
+ c: "lineTo",
3547
+ v:{
3548
+ x:+ command.v.X * originWidth * (width / originWidth) + x,
3549
+ y:+ command.v.Y * originHeight * (height / originHeight) + y
3550
+ }
3551
+ };
3552
+ case "Ellipse":
3553
+ let centerX1 = command.v.X;
3554
+ let centerY1 = command.v.Y;
3555
+ let longAxis = Math.abs(command.v.A - command.v.C);
3556
+ let shortAxis = Math.abs(command.v.B - command.v.D);
3557
+
3558
+ return {
3559
+ c: "ellipse",
3560
+ v: {
3561
+ x: centerX1 * (100) * (width / originWidth) + x,
3562
+ y: centerY1 * (100) * (height / originHeight) + y,
3563
+ radiuX: longAxis * (100) * (width / originWidth),
3564
+ radiuY: shortAxis * (100) * (height / originHeight),
3565
+ rotation: 0,
3566
+ startAngle: 0,
3567
+ endAngle: Math.PI *2,
3568
+ anticlockwise: true
3569
+ }
3570
+ };
3571
+ case "EllipticalArcTo":
3572
+ const endX = command.v.X * 100 * (width / originWidth) + x; // 弧上结束顶点的 x 坐标
3573
+ const endY = command.v.Y * 100 * (height / originHeight) + y; // 弧上结束顶点的 y 坐标
3574
+ const ctrlX = command.v.A * 100 * (width / originWidth) + x; // 控制点的 x 坐标
3575
+ const ctrlY = command.v.B * 100 * (height / originHeight) + y; // 控制点的 y 坐标
3576
+ const angleDeg = command.v.C; // 主轴相对于 x 轴的角度 (度)
3577
+ const axisRatio = command.v.D * (width / height) * (originHeight / originWidth); // 长轴和短轴的比率
3578
+ //
3579
+ const params = calculateEllipseParameters(startX,startY,endX,endY,ctrlX,ctrlY,axisRatio);
3580
+ // 开始绘制路径
3581
+ !command.orign && (command.orign = {});
3582
+ !command.orign.startA && ( command.orign.startA = calculateAngleInRadians(params.x0,params.y0,startX,startY));
3583
+ !command.orign.endA && ( command.orign.endA = calculateAngleInRadians(params.x0,params.y0,endX,endY));
3584
+ return {
3585
+ c:"ellipse",
3586
+ v:{
3587
+ centerX: params.x0,
3588
+ centerY: params.y0 ,
3589
+ radiuX: params.a ,
3590
+ radiuY: params.b,
3591
+ // rotation:radiansToDegrees(angleDeg),
3592
+ rotation:0,
3593
+ startAngle:command.orign.startA,
3594
+ endAngle: command.orign.endA,
3595
+ // startAngle: 0,
3596
+ // endAngle: Math.PI * 2,
3597
+ // anticlockwise: startA > 0 && startA>endA
3598
+ anticlockwise: + angleDeg < 0
3599
+ // anticlockwise: Math.abs(endA - startA) < Math.PI
3600
+ },
3601
+ startX:endX,
3602
+ startY:endY,
3603
+ };
3604
+ case "ArcTo":
3605
+ let endX2 = command.v.X * 100 * width / originWidth + x;
3606
+ let endY2 = command.v.Y * 100 * height / originHeight + y;
3607
+ let h = command.v.A * 100 * (width / height) * (originHeight / originWidth);
3608
+ // 计算弦的中点
3609
+ let xm = (startX + endX2) / 2;
3610
+ let ym = (startY + endY2) / 2;
3611
+
3612
+ // 计算弦的长度
3613
+ let d = Math.sqrt((endX2 - startX) ** 2 + (endY2 - startY) ** 2);
3614
+
3615
+ // 计算圆弧的半径
3616
+ let R = (d ** 2) / (8 * h) + h / 2;
3617
+
3618
+ // 计算单位垂直向量
3619
+ let ux = -(endY2 - startY) / d;
3620
+ let uy = (endX2 - startX) / d;
3621
+
3622
+ // 计算两个可能的圆心
3623
+ let xc1 = xm + ux * R;
3624
+ let yc1 = ym + uy * R;
3625
+ let xc2 = xm - ux * R;
3626
+ let yc2 = ym - uy * R;
3627
+
3628
+ // 选择一个圆心
3629
+ let xc = xc1;
3630
+ let yc = yc1;
3631
+
3632
+ // 计算起点和终点到圆心的角度
3633
+ let startAngle = Math.atan2(startY - yc, startX - xc);
3634
+ let endAngle = Math.atan2(endY2 - yc, endX2 - xc);
3635
+ return {
3636
+ c:'arc',
3637
+ v:{
3638
+ x:xc,
3639
+ y:yc,
3640
+ radius:R,
3641
+ startAngle:startAngle,
3642
+ endAngle: endAngle,
3643
+ aclockwise: true,
3644
+ }
3645
+ };
3646
+ // case "RelCubBezTo":
3647
+ // return {
3648
+ // c:"bezierCurveTo",
3649
+ // v:{
3650
+ //
3651
+ // }
3652
+ // };
3653
+ default:
3654
+ // console.log(command.c);
3655
+ return {};
3656
+ }
3657
+ }
3658
+ export function setChildValue(pen: Pen, data: IValue) {
3659
+ for (const k in data) {
3660
+ if (inheritanceProps.includes(k)) {
3661
+ if(k == 'fontSize' && data[k] < 0){
3662
+ data[k] = 0;
3663
+ }
3664
+ pen[k] = data[k];
3665
+ if (['fontSize','lineWidth'].includes(k)) {
3666
+ pen.calculative[k] = data[k] * pen.calculative.canvas.store.data.scale;
3667
+ calcTextRect(pen);
3668
+ } else {
3669
+ pen.calculative[k] = data[k];
3670
+ }
3671
+ }
3672
+ }
3673
+ if (
3674
+ pen.calculative.canvas.parent.isCombine(pen)
3675
+ //&& pen.showChild === undefined
3676
+ ) {
3677
+ const children = pen.children;
3678
+ children?.forEach((childId) => {
3679
+ let _data = deepClone(data);
3680
+ if(pen.calculative.childrenVisible){
3681
+ if(pen.calculative.childrenVisible[childId]===false){
3682
+ delete _data.visible;
3683
+ }
3684
+ }
3685
+ const child = pen.calculative.canvas.store.pens[childId];
3686
+ child && setChildValue(child, _data);
3687
+ });
3688
+ }
3689
+ }
3690
+
3691
+
3692
+ function calculateEllipseParameters(x1, y1, x2, y2, x3, y3, D) {
3693
+ // Calculate x₀ using equation ⑥
3694
+ let numeratorX0 = (x1 - x2) * (x1 + x2) * (y2 - y3) - (x2 - x3) * (x2 + x3) * (y1 - y2) + D*D * (y1 - y2) * (y2 - y3) * (y1 - y3);
3695
+ let denominatorX0 = 2 * ((x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2));
3696
+ let x0 = numeratorX0 / denominatorX0;
3697
+
3698
+ // Calculate y₀ using equation ⑦
3699
+ let numeratorY0 = (x1 - x2) * (x2 - x3) * (x1 - x3) + D*D * ((x2 - x3) * (y1 - y2) * (y1 + y2) - (x1 - x2) * (y2 - y3) * (y2 + y3)) ;
3700
+ let denominatorY0 = 2 * D*D * ((x2 - x3) * (y1 - y2) - (x1 - x2) * (y2 - y3));
3701
+ let y0 = numeratorY0 / denominatorY0;
3702
+
3703
+ // Calculate 'a' using equation ⑧, substituting x₀ and y₀
3704
+ let a = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(D * (y1 - y0), 2));
3705
+
3706
+ // Calculate 'b' using equation ⑨
3707
+ let b = a / D;
3708
+
3709
+ return {x0, y0, a, b};
3710
+ }
3711
+
3712
+ function calculateAngleInRadians(x1, y1, x2, y2) {
3713
+ // 计算两个点的差值
3714
+ let dx = x2 - x1;
3715
+ let dy = y2 - y1;
3716
+
3717
+ // 使用 atan2 计算角度,结果为弧度
3718
+ let angleRadians = Math.atan2(dy, dx);
3719
+
3720
+ // 如果角度为负值,加上2π
3721
+ if (angleRadians < 0) {
3722
+ angleRadians += 2 * Math.PI;
3723
+ }
3724
+ return angleRadians;
3725
+ }