@leafer/canvas-web 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023-present, Chao (Leafer) Wan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # @leafer/canvas-web
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@leafer/canvas-web",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "@leafer/canvas-web",
5
+ "author": "Chao (Leafer) Wan",
6
+ "license": "MIT",
7
+ "main": "src/index.ts",
8
+ "files": ["src"],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/leaferjs/leafer.git"
12
+ },
13
+ "homepage": "https://github.com/leaferjs/leafer/tree/main/packages/canvas/canvas-web",
14
+ "bugs": "https://github.com/leaferjs/leafer/issues",
15
+ "keywords": [
16
+ "leafer",
17
+ "leaferjs"
18
+ ],
19
+ "dependencies": {
20
+ "@leafer/math": "1.0.0-alpha.1",
21
+ "@leafer/event": "1.0.0-alpha.1",
22
+ "@leafer/debug": "1.0.0-alpha.1"
23
+ },
24
+ "devDependencies": {
25
+ "@leafer/interface": "1.0.0-alpha.1"
26
+ }
27
+ }
@@ -0,0 +1,272 @@
1
+ import { ICanvasAttr, CanvasFillRule, Path2D, TextMetrics, CanvasRenderingContext2D, IObject, InnerId, IMatrixData, IFunction } from '@leafer/interface'
2
+
3
+
4
+ function contextAttr(realName?: string) {
5
+ return (target: CanvasBase, key: string) => {
6
+ if (!realName) realName = key
7
+ const property: IObject & ThisType<CanvasBase> = {
8
+ get() { return (this.context as IObject)[realName] },
9
+ set(value: unknown) { (this.context as IObject)[realName] = value }
10
+ }
11
+ Object.defineProperty(target, key, property)
12
+ }
13
+ }
14
+
15
+ const contextMethodNameList: string[] = []
16
+ function contextMethod() {
17
+ return (target: CanvasBase, key: string) => {
18
+ contextMethodNameList.push(key)
19
+ }
20
+ }
21
+
22
+
23
+ export class CanvasBase {
24
+
25
+ public readonly innerId: InnerId
26
+
27
+ public width: number
28
+ public height: number
29
+
30
+ public __: ICanvasAttr
31
+
32
+ public context: CanvasRenderingContext2D
33
+
34
+ // canvas attr
35
+
36
+ @contextAttr('imageSmoothingEnabled')
37
+ public smooth: boolean
38
+
39
+ @contextAttr('imageSmoothingQuality')
40
+ public smoothLevel: ImageSmoothingQuality
41
+
42
+ @contextAttr('globalAlpha')
43
+ public opacity: number
44
+
45
+ @contextAttr('globalCompositeOperation')
46
+ public blendMode: string
47
+
48
+
49
+ @contextAttr()
50
+ public fillStyle: string | object
51
+
52
+ @contextAttr()
53
+ public strokeStyle: string | object
54
+
55
+
56
+ @contextAttr('lineWidth')
57
+ public strokeWidth: number
58
+
59
+ @contextAttr('lineCap')
60
+ public strokeCap: string
61
+
62
+ @contextAttr('lineJoin')
63
+ public strokeJoin: string
64
+
65
+ @contextAttr('lineDash')
66
+ public dashPattern: Array<number>
67
+
68
+ @contextAttr('lineDashOffset')
69
+ public dashOffset: number
70
+
71
+ @contextAttr()
72
+ public miterLimit: number
73
+
74
+
75
+ @contextAttr()
76
+ public shadowBlur: number
77
+
78
+ @contextAttr()
79
+ public shadowColor: string
80
+
81
+ @contextAttr()
82
+ public shadowOffsetX: number
83
+
84
+ @contextAttr()
85
+ public shadowOffsetY: number
86
+
87
+ @contextAttr()
88
+ public filter: string
89
+
90
+
91
+ @contextAttr()
92
+ public font: string
93
+
94
+ @contextAttr()
95
+ public fontKerning: string
96
+
97
+ @contextAttr()
98
+ public fontStretch: string
99
+
100
+ @contextAttr()
101
+ public fontVariantCaps: string
102
+
103
+
104
+ @contextAttr()
105
+ public textAlign: string
106
+
107
+ @contextAttr()
108
+ public textBaseline: string
109
+
110
+ @contextAttr()
111
+ public textRendering: string
112
+
113
+ @contextAttr()
114
+ public wordSpacing: string
115
+
116
+ @contextAttr()
117
+ public letterSpacing: string
118
+
119
+
120
+ @contextAttr()
121
+ public direction: string
122
+
123
+ // end
124
+
125
+ public bindContextMethod(): void {
126
+ let method: IFunction
127
+ contextMethodNameList.forEach(name => {
128
+ method = (this.context as IObject)[name]
129
+ if (method) (this as IObject)[name] = method.bind(this.context)
130
+ })
131
+ }
132
+
133
+ // canvas method
134
+
135
+ @contextMethod()
136
+ public setTransform(a: number | IMatrixData, b?: number, c?: number, d?: number, e?: number, f?: number): void { }
137
+
138
+ @contextMethod()
139
+ public resetTransform(): void { }
140
+
141
+ @contextMethod()
142
+ public getTransform(): IMatrixData { return void 0 }
143
+
144
+ @contextMethod()
145
+ public save(): void { }
146
+
147
+ @contextMethod()
148
+ public restore(): void { }
149
+
150
+ @contextMethod()
151
+ public translate(x: number, y: number): void { }
152
+
153
+ @contextMethod()
154
+ public scale(x: number, y: number): void { }
155
+
156
+ @contextMethod()
157
+ public rotate(angle: number): void { }
158
+
159
+ @contextMethod()
160
+ public fill(path2d?: Path2D | CanvasFillRule, rule?: CanvasFillRule): void { }
161
+
162
+ @contextMethod()
163
+ public stroke(path2d?: Path2D): void { }
164
+
165
+ @contextMethod()
166
+ public clip(path2d?: Path2D | CanvasFillRule, rule?: CanvasFillRule): void { }
167
+
168
+ @contextMethod()
169
+ public fillRect(x: number, y: number, width: number, height: number): void { }
170
+
171
+ @contextMethod()
172
+ public strokeRect(x: number, y: number, width: number, height: number): void { }
173
+
174
+ @contextMethod()
175
+ public clearRect(x: number, y: number, width: number, height: number): void { }
176
+
177
+ public drawImage(image: CanvasImageSource, sx: number, sy: number, sw?: number, sh?: number, dx?: number, dy?: number, dw?: number, dh?: number): void {
178
+ switch (arguments.length) {
179
+ case 9:
180
+
181
+ // safari: drawimage裁剪画布外的坐标会有问题, 必须是不小于0的坐标点
182
+ if (sx < 0) {
183
+ const d = (-sx / sw) * dw
184
+ sw += sx
185
+ sx = 0
186
+ dx += d
187
+ dw -= d
188
+ }
189
+
190
+ if (sy < 0) {
191
+ const d = (-sy / sh) * dh
192
+ sh += sy
193
+ sy = 0
194
+ dy += d
195
+ dh -= d
196
+ }
197
+
198
+ this.context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
199
+ break
200
+ case 5:
201
+ this.context.drawImage(image, sx, sy, sw, sh) // = dx, dy, dw, dh
202
+ break
203
+ case 3:
204
+ this.context.drawImage(image, sx, sy)
205
+ }
206
+ }
207
+
208
+
209
+ // canvas draw
210
+ @contextMethod()
211
+ public beginPath(): void { }
212
+
213
+ @contextMethod()
214
+ public moveTo(x: number, y: number): void { }
215
+
216
+ @contextMethod()
217
+ public lineTo(x: number, y: number): void { }
218
+
219
+ @contextMethod()
220
+ public bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void { }
221
+
222
+ @contextMethod()
223
+ public quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void { }
224
+
225
+ @contextMethod()
226
+ public closePath(): void { }
227
+
228
+ @contextMethod()
229
+ public arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void { }
230
+
231
+ @contextMethod()
232
+ public arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void { }
233
+
234
+ @contextMethod()
235
+ public ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void { }
236
+
237
+ @contextMethod()
238
+ public rect(x: number, y: number, width: number, height: number): void { }
239
+
240
+ @contextMethod()
241
+ public roundRect(x: number, y: number, width: number, height: number, radius?: number | number[]): void { }
242
+
243
+ // end canvas draw
244
+
245
+ // paint
246
+
247
+ @contextMethod()
248
+ public createConicGradient(startAngle: number, x: number, y: number): CanvasGradient { return void 0 }
249
+
250
+ @contextMethod()
251
+ public createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient { return void 0 }
252
+
253
+ @contextMethod()
254
+ public createPattern(image: CanvasImageSource, repetition: string | null): CanvasPattern | null { return void 0 }
255
+
256
+ @contextMethod()
257
+ public createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient { return void 0 }
258
+
259
+ // text
260
+ @contextMethod()
261
+ public fillText(text: string, x: number, y: number, maxWidth?: number): void { }
262
+
263
+ @contextMethod()
264
+ public measureText(text: string): TextMetrics { return void 0 }
265
+
266
+ @contextMethod()
267
+ public strokeText(text: string, x: number, y: number, maxWidth?: number): void { }
268
+
269
+ public destroy(): void {
270
+ this.context = undefined
271
+ }
272
+ }
@@ -0,0 +1,338 @@
1
+ import { IBounds, ILeaferCanvas, ICanvasStrokeOptions, CanvasFillRule, ILeaferCanvasConfig, IMatrixData, IBoundsData, IAutoBounds, ISizeData, IScreenSizeData, IResizeEventListener, IMatrixWithBoundsData, IPointData, InnerId, ILeafer, ICanvasManager } from '@leafer/interface'
2
+ import { Bounds, BoundsHelper, IncrementId } from '@leafer/math'
3
+ import { ResizeEvent } from '@leafer/event'
4
+ import { Debug } from '@leafer/debug'
5
+
6
+ import { CanvasBase } from './CanvasBase'
7
+
8
+
9
+ const debug = Debug.get('LeaferCanvas')
10
+ const minSize: IScreenSizeData = {
11
+ width: 1,
12
+ height: 1,
13
+ pixelRatio: 1
14
+ }
15
+
16
+ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
17
+
18
+ public readonly innerId: InnerId
19
+
20
+ public manager: ICanvasManager
21
+ public view: HTMLCanvasElement
22
+
23
+ public pixelRatio: number
24
+ public get pixelWidth(): number { return this.width * this.pixelRatio }
25
+ public get pixelHeight(): number { return this.height * this.pixelRatio }
26
+
27
+ public bounds: IBounds
28
+
29
+ public recycled?: boolean
30
+
31
+ protected resizeObserver: ResizeObserver
32
+
33
+ constructor(config?: ILeaferCanvasConfig, manager?: ICanvasManager) {
34
+ super()
35
+
36
+ if (!config) config = minSize
37
+
38
+ this.manager = manager
39
+ this.innerId = IncrementId.create(IncrementId.CNAVAS)
40
+
41
+ if (config.view) {
42
+
43
+ const { view } = config
44
+
45
+ let realView: unknown = (typeof view === 'string') ? document.getElementById(view) : view as HTMLElement
46
+ if (realView) {
47
+ if (realView instanceof HTMLCanvasElement) {
48
+
49
+ this.view = realView
50
+
51
+ } else {
52
+ if (realView === window || realView === document) {
53
+ const div = document.createElement('div')
54
+ const { style } = div
55
+ style.position = 'absolute'
56
+ style.top = style.bottom = style.left = style.right = '0px'
57
+ style.overflow = 'hidden'
58
+ document.body.appendChild(div)
59
+ realView = div
60
+ }
61
+
62
+ this.view = document.createElement('canvas');
63
+ (realView as HTMLElement).appendChild(this.view)
64
+ }
65
+ } else {
66
+ debug.error(`can't find view by id: ${view}`)
67
+ }
68
+
69
+ }
70
+
71
+ if (!this.view) this.view = document.createElement('canvas')
72
+ this.pixelRatio = config.pixelRatio
73
+
74
+ if (!config.webgl) {
75
+ this.context = this.view.getContext('2d')
76
+ this.smooth = true
77
+ if (config.fill) this.view.style.backgroundColor = config.fill
78
+ if (config.width && config.height) this.resize(config as IScreenSizeData)
79
+
80
+ this.bindContextMethod()
81
+ }
82
+ }
83
+
84
+ public debug(): void { }
85
+
86
+ public pixel(num: number): number { return num * this.pixelRatio }
87
+
88
+ public autoLayout(autoBounds: IAutoBounds, listener: IResizeEventListener): void {
89
+ const check = (parentSize: ISizeData) => {
90
+ const { x, y, width, height } = autoBounds.getBoundsFrom(parentSize)
91
+ const { style } = this.view
92
+ style.marginLeft = x + 'px'
93
+ style.marginTop = y + 'px'
94
+
95
+ if (width !== this.width || height !== this.height) {
96
+ const { pixelRatio } = this
97
+ const size = { width, height, pixelRatio }
98
+ const oldSize = { width: this.width, height: this.height, pixelRatio: this.pixelRatio }
99
+ this.resize(size)
100
+ if (this.width !== undefined) listener(new ResizeEvent(size, oldSize))
101
+ }
102
+ }
103
+
104
+ this.resizeObserver = new ResizeObserver((entries) => {
105
+ for (const entry of entries) {
106
+ check(entry.contentRect)
107
+ }
108
+ })
109
+
110
+ const parent = this.view.parentElement
111
+ if (parent) {
112
+ this.resizeObserver.observe(parent)
113
+ check(parent.getBoundingClientRect())
114
+ }
115
+ }
116
+
117
+ public stopAutoLayout(): void {
118
+ if (this.resizeObserver) {
119
+ this.resizeObserver.disconnect()
120
+ this.resizeObserver = undefined
121
+ }
122
+ }
123
+
124
+ public resize(size: IScreenSizeData): void {
125
+ const { style } = this.view
126
+ const { width, height, pixelRatio } = size
127
+ if (this.isSameSize(size)) return
128
+
129
+ let takeCanvas: ILeaferCanvas
130
+ if (this.context && this.width) {
131
+ takeCanvas = this.getSameCanvas()
132
+ takeCanvas.copy(this)
133
+ }
134
+
135
+ Object.assign(this, { width, height, pixelRatio })
136
+ this.bounds = new Bounds(0, 0, width, height)
137
+
138
+ style.width = width + 'px'
139
+ style.height = height + 'px'
140
+ this.view.width = width * pixelRatio
141
+ this.view.height = height * pixelRatio
142
+
143
+ if (this.context && takeCanvas) {
144
+ this.copy(takeCanvas)
145
+ takeCanvas.recycle()
146
+ }
147
+ }
148
+
149
+ public setWorld(matrix: IMatrixData, parentMatrix?: IMatrixData, onlyTranslate?: boolean): void {
150
+ const { pixelRatio } = this
151
+ if (parentMatrix) {
152
+
153
+ if (onlyTranslate) {
154
+ this.setTransform(
155
+ matrix.a * pixelRatio,
156
+ matrix.b * pixelRatio,
157
+ matrix.c * pixelRatio,
158
+ matrix.d * pixelRatio,
159
+ (matrix.e + parentMatrix.e) * pixelRatio,
160
+ (matrix.f + parentMatrix.f) * pixelRatio
161
+ )
162
+ } else {
163
+ const { a, b, c, d, e, f } = parentMatrix
164
+ this.setTransform(
165
+ ((matrix.a * a) + (matrix.b * c)) * pixelRatio,
166
+ ((matrix.a * b) + (matrix.b * d)) * pixelRatio,
167
+ ((matrix.c * a) + (matrix.d * c)) * pixelRatio,
168
+ ((matrix.c * b) + (matrix.d * d)) * pixelRatio,
169
+ (((matrix.e * a) + (matrix.f * c) + e)) * pixelRatio,
170
+ (((matrix.e * b) + (matrix.f * d) + f)) * pixelRatio
171
+ )
172
+ }
173
+
174
+ } else {
175
+
176
+ this.setTransform(
177
+ matrix.a * pixelRatio,
178
+ matrix.b * pixelRatio,
179
+ matrix.c * pixelRatio,
180
+ matrix.d * pixelRatio,
181
+ matrix.e * pixelRatio,
182
+ matrix.f * pixelRatio
183
+ )
184
+ }
185
+ }
186
+
187
+ public useSameTransform(canvas: ILeaferCanvas): void {
188
+ this.setTransform(canvas.getTransform())
189
+ }
190
+
191
+
192
+ public setStroke(strokeStyle: string | object, strokeWidth: number, options?: ICanvasStrokeOptions): void {
193
+
194
+ if (strokeWidth) this.strokeWidth = strokeWidth
195
+ if (strokeStyle) this.strokeStyle = strokeStyle
196
+ if (options) {
197
+ this.strokeCap = options.strokeCap
198
+ this.strokeJoin = options.strokeJoin
199
+ this.dashPattern = options.dashPattern
200
+ this.miterLimit = options.miterLimit
201
+ }
202
+ }
203
+
204
+ public setShadow(x: number, y: number, blur: number, color?: string): void {
205
+ const { pixelRatio } = this
206
+ this.shadowOffsetX = x * pixelRatio
207
+ this.shadowOffsetY = y * pixelRatio
208
+ this.shadowBlur = blur * pixelRatio
209
+ this.shadowColor = color || 'black'
210
+ }
211
+
212
+ public setBlur(blur: number): void {
213
+ const { pixelRatio } = this
214
+ this.filter = `blur(${blur * pixelRatio}px)`
215
+ }
216
+
217
+
218
+ public hitPath(point: IPointData, fillRule?: CanvasFillRule): boolean {
219
+ return this.context.isPointInPath(point.x, point.y, fillRule)
220
+ }
221
+
222
+ public hitStroke(point: IPointData): boolean {
223
+ return this.context.isPointInStroke(point.x, point.y)
224
+ }
225
+
226
+ public replaceBy(canvas: ILeaferCanvas, from?: IBoundsData, to?: IBoundsData): void {
227
+ canvas.save()
228
+ this.blendMode = 'copy'
229
+ this.copy(canvas, from, to)
230
+ canvas.restore()
231
+ }
232
+
233
+ public copy(canvas: ILeaferCanvas, from?: IBoundsData, to?: IBoundsData, blendMode?: string): void {
234
+ if (from) {
235
+ if (!to) to = from
236
+ const { pixelRatio } = this
237
+ if (blendMode) this.blendMode = blendMode
238
+ this.drawImage(canvas.view as HTMLCanvasElement, from.x * pixelRatio, from.y * pixelRatio, from.width * pixelRatio, from.height * pixelRatio, to.x * pixelRatio, to.y * pixelRatio, to.width * pixelRatio, to.height * pixelRatio)
239
+ if (blendMode) this.blendMode = 'normal'
240
+ } else {
241
+ this.drawImage(canvas.view as HTMLCanvasElement, 0, 0)
242
+ }
243
+
244
+ }
245
+
246
+ public copyWorldToLocal(canvas: ILeaferCanvas, fromWorld: IMatrixWithBoundsData, toLocalBounds: IBoundsData, blendMode?: string): void {
247
+ const { pixelRatio } = this
248
+ if (blendMode) this.blendMode = blendMode
249
+ if (fromWorld.b || fromWorld.c) {
250
+ this.save()
251
+ this.resetTransform()
252
+ this.copy(canvas, fromWorld, BoundsHelper.tempTimesMatrix(toLocalBounds, fromWorld))
253
+ this.restore()
254
+ } else {
255
+ this.drawImage(canvas.view as HTMLCanvasElement, fromWorld.x * pixelRatio, fromWorld.y * pixelRatio, fromWorld.width * pixelRatio, fromWorld.height * pixelRatio, toLocalBounds.x, toLocalBounds.y, toLocalBounds.width, toLocalBounds.height)
256
+ }
257
+ if (blendMode) this.blendMode = 'normal'
258
+ }
259
+
260
+ public fillBounds(bounds: IBoundsData, color: string | object, blendMode?: string): void {
261
+ const { pixelRatio } = this
262
+ if (blendMode) this.blendMode = blendMode
263
+ this.fillStyle = color
264
+ this.fillRect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
265
+ if (blendMode) this.blendMode = 'normal'
266
+ }
267
+
268
+ public strokeBounds(bounds: IBoundsData, color: string | object, blendMode?: string): void {
269
+ const { pixelRatio } = this
270
+ if (blendMode) this.blendMode = blendMode
271
+ this.strokeStyle = color
272
+ this.strokeRect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
273
+ if (blendMode) this.blendMode = 'normal'
274
+ }
275
+
276
+ public clearBounds(bounds: IBoundsData): void {
277
+ const { pixelRatio } = this
278
+ this.clearRect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
279
+ }
280
+
281
+ public clipBounds(bounds: IBoundsData): void {
282
+ const { pixelRatio } = this
283
+ this.beginPath()
284
+ this.rect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
285
+ this.clip()
286
+
287
+ }
288
+
289
+ public clear(): void {
290
+ const { pixelRatio } = this
291
+ this.clearRect(0, 0, this.width * pixelRatio, this.height * pixelRatio)
292
+ }
293
+
294
+
295
+ // other
296
+
297
+ public isSameSize(size: IScreenSizeData): boolean {
298
+ return this.width === size.width && this.height === size.height && this.pixelRatio === size.pixelRatio
299
+ }
300
+
301
+ // 需要有 manager变量
302
+ public getSameCanvas(useSameTransform?: boolean): ILeaferCanvas {
303
+ const { width, height, pixelRatio } = this
304
+ const canvas = this.manager.get({ width, height, pixelRatio })
305
+ canvas.manager || (canvas.manager = this.manager)
306
+ canvas.save()
307
+ if (useSameTransform) canvas.useSameTransform(this)
308
+ return canvas
309
+ }
310
+
311
+ public getBiggerCanvas(addWidth: number, addHeight: number): ILeaferCanvas {
312
+ let { width, height, pixelRatio } = this
313
+ if (addWidth) width += addWidth
314
+ if (addHeight) height += addHeight
315
+ const canvas = this.manager.get({ width, height, pixelRatio })
316
+ canvas.manager || (canvas.manager = this.manager)
317
+ canvas.save()
318
+ return canvas
319
+ }
320
+
321
+ public recycle(): void {
322
+ this.restore()
323
+ this.manager.recycle(this)
324
+ }
325
+
326
+
327
+ public destroy(): void {
328
+ if (this.view) {
329
+ super.destroy()
330
+ this.stopAutoLayout()
331
+ this.manager = undefined
332
+ if (this.view && this.view.parentElement) this.view.remove()
333
+ this.view = undefined
334
+ this.bounds = undefined
335
+ }
336
+ }
337
+
338
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { LeaferCanvas } from './LeaferCanvas'
2
+
3
+ import { patch } from './patch'
4
+ patch()
@@ -0,0 +1,5 @@
1
+ import { roundRect } from './roundRect'
2
+
3
+ export function patch(): void {
4
+ roundRect()
5
+ }
@@ -0,0 +1,63 @@
1
+ import { ICanvasDrawPath } from '@leafer/interface'
2
+
3
+
4
+ export function roundRect(): void {
5
+
6
+ if (!(CanvasRenderingContext2D.prototype as ICanvasDrawPath).roundRect) {
7
+
8
+ (CanvasRenderingContext2D.prototype as ICanvasDrawPath).roundRect = (Path2D.prototype as ICanvasDrawPath).roundRect = function (x: number, y: number, w: number, h: number, r: number | number[]): void {
9
+
10
+ let topLeft: number, topRight: number, bottomRight: number, bottomLeft: number
11
+
12
+ if (r instanceof Array) {
13
+ switch (r.length) {
14
+ case 4:
15
+ topLeft = r[0]
16
+ topRight = r[1]
17
+ bottomRight = r[2]
18
+ bottomLeft = r[3]
19
+ break
20
+ case 2:
21
+ topLeft = bottomRight = r[0]
22
+ topRight = bottomLeft = r[1]
23
+ break
24
+ case 3:
25
+ topLeft = r[0]
26
+ topRight = bottomLeft = r[1]
27
+ bottomRight = r[2]
28
+ break
29
+ case 1:
30
+ r = r[0]
31
+ break
32
+ default:
33
+ r = 0
34
+ }
35
+ }
36
+
37
+ if (topLeft === undefined) {
38
+ if (r) {
39
+ topLeft = topRight = bottomRight = bottomLeft = r as number
40
+ } else {
41
+ this.rect(x, y, w, h)
42
+ return
43
+ }
44
+ }
45
+
46
+ const max = Math.min(w / 2, h / 2)
47
+ if (topLeft > max) topLeft = max
48
+ if (topRight > max) topRight = max
49
+ if (bottomRight > max) bottomRight = max
50
+ if (bottomLeft > max) bottomLeft = max
51
+
52
+ const R = this
53
+ topLeft ? R.moveTo(x + topLeft, y) : R.moveTo(x, y)
54
+ topRight ? R.arcTo(x + w, y, x + w, y + h, topRight) : R.lineTo(x + w, y)
55
+ bottomRight ? R.arcTo(x + w, y + h, x, y + h, bottomRight) : R.lineTo(x + w, y + h)
56
+ bottomLeft ? R.arcTo(x, y + h, x, y, bottomLeft) : R.lineTo(x, y + h)
57
+ topLeft ? R.arcTo(x, y, x + w, y, topLeft) : R.lineTo(x, y)
58
+ R.closePath()
59
+ }
60
+ }
61
+
62
+ }
63
+