@leafer/canvas 1.0.0-alpha.23 → 1.0.0-alpha.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leafer/canvas",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.31",
4
4
  "description": "@leafer/canvas",
5
5
  "author": "Chao (Leafer) Wan",
6
6
  "license": "MIT",
@@ -19,9 +19,9 @@
19
19
  "leaferjs"
20
20
  ],
21
21
  "dependencies": {
22
- "@leafer/list": "1.0.0-alpha.23"
22
+ "@leafer/list": "1.0.0-alpha.31"
23
23
  },
24
24
  "devDependencies": {
25
- "@leafer/interface": "1.0.0-alpha.23"
25
+ "@leafer/interface": "1.0.0-alpha.31"
26
26
  }
27
27
  }
package/src/Canvas.ts ADDED
@@ -0,0 +1,278 @@
1
+ import { ICanvasAttr, ITextMetrics, ICanvasContext2D, IPath2D, IObject, InnerId, IMatrixData, IFunction, IWindingRule } from '@leafer/interface'
2
+
3
+ function contextAttr(realName?: string) {
4
+ return (target: Canvas, key: string) => {
5
+ if (!realName) realName = key
6
+ const property: IObject & ThisType<Canvas> = {
7
+ get() { return (this.context as IObject)[realName] },
8
+ set(value: unknown) { (this.context as IObject)[realName] = value }
9
+ }
10
+ Object.defineProperty(target, key, property)
11
+ }
12
+ }
13
+
14
+ const contextMethodNameList: string[] = []
15
+ function contextMethod() {
16
+ return (_target: Canvas, key: string) => {
17
+ contextMethodNameList.push(key)
18
+ }
19
+ }
20
+
21
+ const emptyArray: number[] = []
22
+
23
+
24
+ export class Canvas implements ICanvasAttr {
25
+
26
+ public readonly innerId: InnerId
27
+
28
+ public width: number
29
+ public height: number
30
+
31
+ public context: ICanvasContext2D
32
+
33
+ // canvas attr
34
+
35
+ @contextAttr('imageSmoothingEnabled')
36
+ public smooth: boolean
37
+
38
+ @contextAttr('imageSmoothingQuality')
39
+ public smoothLevel: ImageSmoothingQuality
40
+
41
+ @contextAttr('globalAlpha')
42
+ public opacity: number
43
+
44
+ @contextAttr('globalCompositeOperation')
45
+ public blendMode: string
46
+
47
+
48
+ @contextAttr()
49
+ public fillStyle: string | object
50
+
51
+ @contextAttr()
52
+ public strokeStyle: string | object
53
+
54
+
55
+ @contextAttr('lineWidth')
56
+ public strokeWidth: number
57
+
58
+ @contextAttr('lineCap')
59
+ public strokeCap: string
60
+
61
+ @contextAttr('lineJoin')
62
+ public strokeJoin: string
63
+
64
+ public set dashPattern(value: number[]) {
65
+ this.context.setLineDash(value || emptyArray)
66
+ }
67
+ public get dashPattern(): number[] {
68
+ return this.context.getLineDash()
69
+ }
70
+
71
+ @contextAttr('lineDashOffset')
72
+ public dashOffset: number
73
+
74
+ @contextAttr()
75
+ public miterLimit: number
76
+
77
+
78
+ @contextAttr()
79
+ public shadowBlur: number
80
+
81
+ @contextAttr()
82
+ public shadowColor: string
83
+
84
+ @contextAttr()
85
+ public shadowOffsetX: number
86
+
87
+ @contextAttr()
88
+ public shadowOffsetY: number
89
+
90
+ @contextAttr()
91
+ public filter: string
92
+
93
+
94
+ @contextAttr()
95
+ public font: string
96
+
97
+ @contextAttr()
98
+ public fontKerning: string
99
+
100
+ @contextAttr()
101
+ public fontStretch: string
102
+
103
+ @contextAttr()
104
+ public fontVariantCaps: string
105
+
106
+
107
+ @contextAttr()
108
+ public textAlign: string
109
+
110
+ @contextAttr()
111
+ public textBaseline: string
112
+
113
+ @contextAttr()
114
+ public textRendering: string
115
+
116
+ @contextAttr()
117
+ public wordSpacing: string
118
+
119
+ @contextAttr()
120
+ public letterSpacing: string
121
+
122
+
123
+ @contextAttr()
124
+ public direction: string
125
+
126
+ // end
127
+
128
+ public __bindContext(): void {
129
+ let method: IFunction
130
+ contextMethodNameList.forEach(name => {
131
+ method = (this.context as IObject)[name]
132
+ if (method) (this as IObject)[name] = method.bind(this.context)
133
+ })
134
+ }
135
+
136
+ // canvas method
137
+
138
+ @contextMethod()
139
+ public setTransform(_a: number | IMatrixData, _b?: number, _c?: number, _d?: number, _e?: number, _f?: number): void { }
140
+
141
+ @contextMethod()
142
+ public resetTransform(): void { }
143
+
144
+ @contextMethod()
145
+ public getTransform(): IMatrixData { return void 0 }
146
+
147
+ @contextMethod()
148
+ public save(): void { }
149
+
150
+ @contextMethod()
151
+ public restore(): void { }
152
+
153
+ @contextMethod()
154
+ public transform(_a: number, _b: number, _c: number, _d: number, _e: number, _f: number): void { }
155
+
156
+ @contextMethod()
157
+ public translate(_x: number, _y: number): void { }
158
+
159
+ @contextMethod()
160
+ public scale(_x: number, _y: number): void { }
161
+
162
+ @contextMethod()
163
+ public rotate(_angle: number): void { }
164
+
165
+ @contextMethod()
166
+ public fill(_path2d?: IPath2D | IWindingRule, _rule?: IWindingRule): void { }
167
+
168
+ @contextMethod()
169
+ public stroke(_path2d?: IPath2D): void { }
170
+
171
+ @contextMethod()
172
+ public clip(_path2d?: IPath2D | IWindingRule, _rule?: IWindingRule): void { }
173
+
174
+ @contextMethod()
175
+ public fillRect(_x: number, _y: number, _width: number, _height: number): void { }
176
+
177
+ @contextMethod()
178
+ public strokeRect(_x: number, _y: number, _width: number, _height: number): void { }
179
+
180
+ @contextMethod()
181
+ public clearRect(_x: number, _y: number, _width: number, _height: number): void { }
182
+
183
+ public drawImage(image: CanvasImageSource, sx: number, sy: number, sw?: number, sh?: number, dx?: number, dy?: number, dw?: number, dh?: number): void {
184
+ switch (arguments.length) {
185
+ case 9:
186
+
187
+ // safari: drawimage裁剪画布外的坐标会有问题, 必须是不小于0的坐标点
188
+ if (sx < 0) {
189
+ const d = (-sx / sw) * dw
190
+ sw += sx
191
+ sx = 0
192
+ dx += d
193
+ dw -= d
194
+ }
195
+
196
+ if (sy < 0) {
197
+ const d = (-sy / sh) * dh
198
+ sh += sy
199
+ sy = 0
200
+ dy += d
201
+ dh -= d
202
+ }
203
+
204
+ this.context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
205
+ break
206
+ case 5:
207
+ this.context.drawImage(image, sx, sy, sw, sh) // = dx, dy, dw, dh
208
+ break
209
+ case 3:
210
+ this.context.drawImage(image, sx, sy)
211
+ }
212
+ }
213
+
214
+
215
+ // canvas draw
216
+ @contextMethod()
217
+ public beginPath(): void { }
218
+
219
+ @contextMethod()
220
+ public moveTo(_x: number, _y: number): void { }
221
+
222
+ @contextMethod()
223
+ public lineTo(_x: number, _y: number): void { }
224
+
225
+ @contextMethod()
226
+ public bezierCurveTo(_cp1x: number, _cp1y: number, _cp2x: number, _cp2y: number, _x: number, _y: number): void { }
227
+
228
+ @contextMethod()
229
+ public quadraticCurveTo(_cpx: number, _cpy: number, _x: number, _y: number): void { }
230
+
231
+ @contextMethod()
232
+ public closePath(): void { }
233
+
234
+ @contextMethod()
235
+ public arc(_x: number, _y: number, _radius: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean): void { }
236
+
237
+ @contextMethod()
238
+ public arcTo(_x1: number, _y1: number, _x2: number, _y2: number, _radius: number): void { }
239
+
240
+ @contextMethod()
241
+ public ellipse(_x: number, _y: number, _radiusX: number, _radiusY: number, _rotation: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean): void { }
242
+
243
+ @contextMethod()
244
+ public rect(_x: number, _y: number, _width: number, _height: number): void { }
245
+
246
+ @contextMethod()
247
+ public roundRect(_x: number, _y: number, _width: number, _height: number, _radius?: number | number[]): void { }
248
+
249
+ // end canvas draw
250
+
251
+ // paint
252
+
253
+ @contextMethod()
254
+ public createConicGradient(_startAngle: number, _x: number, _y: number): CanvasGradient { return void 0 }
255
+
256
+ @contextMethod()
257
+ public createLinearGradient(_x0: number, _y0: number, _x1: number, _y1: number): CanvasGradient { return void 0 }
258
+
259
+ @contextMethod()
260
+ public createPattern(_image: CanvasImageSource, _repetition: string | null): CanvasPattern | null { return void 0 }
261
+
262
+ @contextMethod()
263
+ public createRadialGradient(_x0: number, _y0: number, _r0: number, _x1: number, _y1: number, _r1: number): CanvasGradient { return void 0 }
264
+
265
+ // text
266
+ @contextMethod()
267
+ public fillText(_text: string, _x: number, _y: number, _maxWidth?: number): void { }
268
+
269
+ @contextMethod()
270
+ public measureText(_text: string): ITextMetrics { return void 0 }
271
+
272
+ @contextMethod()
273
+ public strokeText(_text: string, _x: number, _y: number, _maxWidth?: number): void { }
274
+
275
+ public destroy(): void {
276
+ this.context = null
277
+ }
278
+ }
@@ -17,6 +17,7 @@ export class CanvasManager implements ICanvasManager {
17
17
  old = list[i]
18
18
  if (old.recycled && old.isSameSize(size)) {
19
19
  old.recycled = false
20
+ old.manager || (old.manager = this)
20
21
  return old
21
22
  }
22
23
  }
@@ -0,0 +1,301 @@
1
+ import { IBounds, ILeaferCanvas, ICanvasStrokeOptions, ILeaferCanvasConfig, IMatrixData, IBoundsData, IAutoBounds, IScreenSizeData, IResizeEventListener, IMatrixWithBoundsData, IPointData, InnerId, ICanvasManager, IWindingRule } from '@leafer/interface'
2
+ import { Bounds, BoundsHelper, IncrementId } from '@leafer/math'
3
+ import { Creator, Platform } from '@leafer/platform'
4
+ import { DataHelper } from '@leafer/data'
5
+
6
+ import { Canvas } from './Canvas'
7
+
8
+
9
+ const temp = new Bounds()
10
+ const minSize: IScreenSizeData = { width: 1, height: 1, pixelRatio: 1 }
11
+
12
+ export const canvasSizeAttrs = ['width', 'height', 'pixelRatio']
13
+
14
+ export class LeaferCanvasBase extends Canvas implements ILeaferCanvas {
15
+
16
+ public readonly innerId: InnerId
17
+
18
+ public name: string
19
+
20
+ public manager: ICanvasManager
21
+
22
+ public pixelRatio: number
23
+ public get pixelWidth(): number { return this.width * this.pixelRatio }
24
+ public get pixelHeight(): number { return this.height * this.pixelRatio }
25
+ public get allowBackgroundColor(): boolean { return this.view && this.parentView && !this.offscreen }
26
+
27
+ public bounds: IBounds
28
+
29
+ public config: ILeaferCanvasConfig
30
+
31
+ public autoLayout: boolean
32
+
33
+ public view: unknown
34
+ public parentView: unknown
35
+
36
+ public unreal?: boolean
37
+
38
+ public offscreen: boolean
39
+
40
+ public recycled?: boolean
41
+
42
+ public worldTransform: IMatrixData = {} as IMatrixData
43
+
44
+ protected savedBlendMode: string
45
+
46
+ constructor(config?: ILeaferCanvasConfig, manager?: ICanvasManager) {
47
+ super()
48
+ if (!config) config = minSize
49
+ if (!config.pixelRatio) config.pixelRatio = devicePixelRatio
50
+
51
+ this.manager = manager
52
+ this.innerId = IncrementId.create(IncrementId.CNAVAS)
53
+
54
+ const { width, height, pixelRatio } = config
55
+ this.autoLayout = !width || !height
56
+
57
+ this.pixelRatio = pixelRatio
58
+ this.offscreen = Platform.isWorker || config.offscreen
59
+ this.config = config
60
+
61
+ this.init()
62
+
63
+ this.textBaseline = "alphabetic"
64
+ }
65
+
66
+ public init(): void { }
67
+ public setBackgroundColor(_color: string): void { }
68
+ public setHittable(_hittable: boolean): void { }
69
+
70
+ public resize(size: IScreenSizeData): void {
71
+ const { width, height } = size
72
+ if (this.isSameSize(size)) return
73
+
74
+ let takeCanvas: ILeaferCanvas
75
+ if (this.context && !this.unreal && this.width) {
76
+ takeCanvas = this.getSameCanvas()
77
+ takeCanvas.copyWorld(this)
78
+ }
79
+
80
+ DataHelper.copyAttrs(this, size, canvasSizeAttrs)
81
+ this.bounds = new Bounds(0, 0, width, height)
82
+
83
+ if (!this.unreal) {
84
+ this.setViewSize(size)
85
+ this.smooth = this.config.smooth
86
+ }
87
+
88
+ if (this.context && !this.unreal && takeCanvas) {
89
+ this.clearWorld(takeCanvas.bounds)
90
+ this.copyWorld(takeCanvas)
91
+ takeCanvas.recycle()
92
+ }
93
+ }
94
+
95
+ public setViewSize(_size: IScreenSizeData): void { }
96
+ public startAutoLayout(_autoBounds: IAutoBounds, _listener: IResizeEventListener): void { }
97
+ public stopAutoLayout(): void { }
98
+
99
+ public setWorld(matrix: IMatrixData, parentMatrix?: IMatrixData, onlyTranslate?: boolean): void {
100
+ const { pixelRatio } = this
101
+ const w = this.worldTransform
102
+ if (parentMatrix) {
103
+
104
+ if (onlyTranslate) {
105
+ this.setTransform(
106
+ w.a = matrix.a * pixelRatio,
107
+ w.b = matrix.b * pixelRatio,
108
+ w.c = matrix.c * pixelRatio,
109
+ w.d = matrix.d * pixelRatio,
110
+ w.e = (matrix.e + parentMatrix.e) * pixelRatio,
111
+ w.f = (matrix.f + parentMatrix.f) * pixelRatio
112
+ )
113
+ } else {
114
+ const { a, b, c, d, e, f } = parentMatrix
115
+ this.setTransform(
116
+ w.a = ((matrix.a * a) + (matrix.b * c)) * pixelRatio,
117
+ w.b = ((matrix.a * b) + (matrix.b * d)) * pixelRatio,
118
+ w.c = ((matrix.c * a) + (matrix.d * c)) * pixelRatio,
119
+ w.d = ((matrix.c * b) + (matrix.d * d)) * pixelRatio,
120
+ w.e = (((matrix.e * a) + (matrix.f * c) + e)) * pixelRatio,
121
+ w.f = (((matrix.e * b) + (matrix.f * d) + f)) * pixelRatio
122
+ )
123
+ }
124
+
125
+ } else {
126
+
127
+ this.setTransform(
128
+ w.a = matrix.a * pixelRatio,
129
+ w.b = matrix.b * pixelRatio,
130
+ w.c = matrix.c * pixelRatio,
131
+ w.d = matrix.d * pixelRatio,
132
+ w.e = matrix.e * pixelRatio,
133
+ w.f = matrix.f * pixelRatio
134
+ )
135
+ }
136
+ }
137
+
138
+ public setStroke(color: string | object, strokeWidth: number, options?: ICanvasStrokeOptions): void {
139
+ if (strokeWidth) this.strokeWidth = strokeWidth
140
+ if (color) this.strokeStyle = color
141
+ if (options) this.setStrokeOptions(options)
142
+ }
143
+
144
+ public setStrokeOptions(options: ICanvasStrokeOptions): void {
145
+ this.strokeCap = options.strokeCap
146
+ this.strokeJoin = options.strokeJoin
147
+ this.dashPattern = options.dashPattern
148
+ this.dashOffset = options.dashOffset
149
+ this.miterLimit = options.miterLimit
150
+ }
151
+
152
+ public saveBlendMode(blendMode: string): void {
153
+ this.savedBlendMode = this.blendMode
154
+ this.blendMode = blendMode
155
+ }
156
+
157
+ public restoreBlendMode(): void {
158
+ this.blendMode = this.savedBlendMode
159
+ }
160
+
161
+ public hitFill(point: IPointData, fillRule?: IWindingRule): boolean {
162
+ return this.context.isPointInPath(point.x, point.y, fillRule)
163
+ }
164
+
165
+ public hitStroke(point: IPointData, strokeWidth?: number): boolean {
166
+ this.strokeWidth = strokeWidth
167
+ return this.context.isPointInStroke(point.x, point.y)
168
+ }
169
+
170
+
171
+ public setWorldShadow(x: number, y: number, blur: number, color?: string): void {
172
+ const { pixelRatio } = this
173
+ this.shadowOffsetX = x * pixelRatio
174
+ this.shadowOffsetY = y * pixelRatio
175
+ this.shadowBlur = blur * pixelRatio
176
+ this.shadowColor = color || 'black'
177
+ }
178
+
179
+ public setWorldBlur(blur: number): void {
180
+ const { pixelRatio } = this
181
+ this.filter = `blur(${blur * pixelRatio}px)`
182
+ }
183
+
184
+
185
+ public copyWorld(canvas: ILeaferCanvas, from?: IBoundsData, to?: IBoundsData, blendMode?: string): void {
186
+ if (blendMode) this.blendMode = blendMode
187
+ if (from) {
188
+ const { pixelRatio } = this
189
+ if (!to) to = from
190
+ 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)
191
+ } else {
192
+ this.drawImage(canvas.view as HTMLCanvasElement, 0, 0)
193
+ }
194
+ if (blendMode) this.blendMode = 'source-over'
195
+ }
196
+
197
+ public copyWorldToInner(canvas: ILeaferCanvas, fromWorld: IMatrixWithBoundsData, toInnerBounds: IBoundsData, blendMode?: string): void {
198
+ if (blendMode) this.blendMode = blendMode
199
+ if (fromWorld.b || fromWorld.c) {
200
+ this.save()
201
+ this.resetTransform()
202
+ this.copyWorld(canvas, fromWorld, BoundsHelper.tempToOuterOf(toInnerBounds, fromWorld))
203
+ this.restore()
204
+ } else {
205
+ const { pixelRatio } = this
206
+ this.drawImage(canvas.view as HTMLCanvasElement, fromWorld.x * pixelRatio, fromWorld.y * pixelRatio, fromWorld.width * pixelRatio, fromWorld.height * pixelRatio, toInnerBounds.x, toInnerBounds.y, toInnerBounds.width, toInnerBounds.height)
207
+ }
208
+ if (blendMode) this.blendMode = 'source-over'
209
+ }
210
+
211
+ public useMask(maskCanvas: ILeaferCanvas, fromBounds?: IBoundsData, toBounds?: IBoundsData): void {
212
+ this.copyWorld(maskCanvas, fromBounds, toBounds, 'destination-in')
213
+ }
214
+
215
+ public fillWorld(bounds: IBoundsData, color: string | object, blendMode?: string): void {
216
+ if (blendMode) this.blendMode = blendMode
217
+ this.fillStyle = color
218
+ temp.copy(bounds).scale(this.pixelRatio)
219
+ this.fillRect(temp.x, temp.y, temp.width, temp.height)
220
+ if (blendMode) this.blendMode = 'source-over'
221
+ }
222
+
223
+ public strokeWorld(bounds: IBoundsData, color: string | object, blendMode?: string): void {
224
+ if (blendMode) this.blendMode = blendMode
225
+ this.strokeStyle = color
226
+ temp.copy(bounds).scale(this.pixelRatio)
227
+ this.strokeRect(temp.x, temp.y, temp.width, temp.height)
228
+ if (blendMode) this.blendMode = 'source-over'
229
+ }
230
+
231
+ public clearWorld(bounds: IBoundsData, ceilPixel?: boolean): void {
232
+ temp.copy(bounds).scale(this.pixelRatio)
233
+ if (ceilPixel) temp.ceil()
234
+ this.clearRect(temp.x, temp.y, temp.width, temp.height)
235
+ }
236
+
237
+ public clipWorld(bounds: IBoundsData, ceilPixel?: boolean): void {
238
+ this.beginPath()
239
+ temp.copy(bounds).scale(this.pixelRatio)
240
+ if (ceilPixel) temp.ceil()
241
+ this.rect(temp.x, temp.y, temp.width, temp.height)
242
+ this.clip()
243
+
244
+ }
245
+
246
+ public clear(): void {
247
+ const { pixelRatio } = this
248
+ this.clearRect(0, 0, this.width * pixelRatio, this.height * pixelRatio)
249
+ }
250
+
251
+
252
+ // other
253
+
254
+ public isSameSize(size: IScreenSizeData): boolean {
255
+ return this.width === size.width && this.height === size.height && this.pixelRatio === size.pixelRatio
256
+ }
257
+
258
+ // 需要有 manager变量
259
+ public getSameCanvas(useSameWorldTransform?: boolean): ILeaferCanvas {
260
+ const { width, height, pixelRatio } = this
261
+
262
+ const options = { width, height, pixelRatio }
263
+ const canvas = this.manager ? this.manager.get(options) : Creator.canvas(options)
264
+
265
+ canvas.save()
266
+
267
+ if (useSameWorldTransform) {
268
+ const w = this.worldTransform
269
+ canvas.setTransform(w.a, w.b, w.c, w.d, w.e, w.f)
270
+ }
271
+
272
+ return canvas
273
+ }
274
+
275
+ public getBiggerCanvas(addWidth: number, addHeight: number): ILeaferCanvas {
276
+ let { width, height, pixelRatio } = this
277
+ if (addWidth) width += addWidth
278
+ if (addHeight) height += addHeight
279
+
280
+ const options = { width, height, pixelRatio }
281
+ const canvas = this.manager ? this.manager.get(options) : Creator.canvas(options)
282
+
283
+ canvas.save()
284
+ return canvas
285
+ }
286
+
287
+ public recycle(): void {
288
+ this.restore()
289
+ this.manager ? this.manager.recycle(this) : this.destroy()
290
+ }
291
+
292
+ public unrealCanvas(): void { }
293
+
294
+ public destroy(): void {
295
+ this.manager = null
296
+ this.view = null
297
+ this.parentView = null
298
+ this.context = null
299
+ this.config = null
300
+ }
301
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { CanvasManager } from './CanvasManager'
2
2
  export { HitCanvasManager } from './HitCanvasManager'
3
+ export { LeaferCanvasBase, canvasSizeAttrs } from './LeaferCanvasBase'
4
+