@leafer/canvas-web 1.0.0-alpha.10 → 1.0.0-alpha.23

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-web",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.23",
4
4
  "description": "@leafer/canvas-web",
5
5
  "author": "Chao (Leafer) Wan",
6
6
  "license": "MIT",
@@ -19,11 +19,11 @@
19
19
  "leaferjs"
20
20
  ],
21
21
  "dependencies": {
22
- "@leafer/math": "1.0.0-alpha.10",
23
- "@leafer/event": "1.0.0-alpha.10",
24
- "@leafer/debug": "1.0.0-alpha.10"
22
+ "@leafer/math": "1.0.0-alpha.23",
23
+ "@leafer/event": "1.0.0-alpha.23",
24
+ "@leafer/debug": "1.0.0-alpha.23"
25
25
  },
26
26
  "devDependencies": {
27
- "@leafer/interface": "1.0.0-alpha.10"
27
+ "@leafer/interface": "1.0.0-alpha.23"
28
28
  }
29
29
  }
package/src/CanvasBase.ts CHANGED
@@ -19,6 +19,7 @@ function contextMethod() {
19
19
  }
20
20
  }
21
21
 
22
+ const emptyArray: number[] = []
22
23
 
23
24
  export class CanvasBase {
24
25
 
@@ -62,8 +63,12 @@ export class CanvasBase {
62
63
  @contextAttr('lineJoin')
63
64
  public strokeJoin: string
64
65
 
65
- @contextAttr('lineDash')
66
- public dashPattern: Array<number>
66
+ public set dashPattern(value: number[]) {
67
+ this.context.setLineDash(value || emptyArray)
68
+ }
69
+ public get dashPattern(): number[] {
70
+ return this.context.getLineDash()
71
+ }
67
72
 
68
73
  @contextAttr('lineDashOffset')
69
74
  public dashOffset: number
@@ -122,7 +127,7 @@ export class CanvasBase {
122
127
 
123
128
  // end
124
129
 
125
- public bindContextMethod(): void {
130
+ public __bindContext(): void {
126
131
  let method: IFunction
127
132
  contextMethodNameList.forEach(name => {
128
133
  method = (this.context as IObject)[name]
@@ -147,6 +152,9 @@ export class CanvasBase {
147
152
  @contextMethod()
148
153
  public restore(): void { }
149
154
 
155
+ @contextMethod()
156
+ public transform(_a: number, _b: number, _c: number, _d: number, _e: number, _f: number): void { }
157
+
150
158
  @contextMethod()
151
159
  public translate(_x: number, _y: number): void { }
152
160
 
@@ -1,12 +1,15 @@
1
1
  import { IBounds, ILeaferCanvas, ICanvasStrokeOptions, ICanvasContext2D, ILeaferCanvasConfig, IMatrixData, IBoundsData, IAutoBounds, ISizeData, IScreenSizeData, IResizeEventListener, IMatrixWithBoundsData, IPointData, InnerId, ICanvasManager, IWindingRule } from '@leafer/interface'
2
2
  import { Bounds, BoundsHelper, IncrementId } from '@leafer/math'
3
3
  import { ResizeEvent } from '@leafer/event'
4
+ import { Platform } from '@leafer/platform'
4
5
  import { Debug } from '@leafer/debug'
5
6
 
6
7
  import { CanvasBase } from './CanvasBase'
7
8
 
8
9
 
9
10
  const debug = Debug.get('LeaferCanvas')
11
+
12
+ const temp = new Bounds()
10
13
  const minSize: IScreenSizeData = {
11
14
  width: 1,
12
15
  height: 1,
@@ -15,10 +18,9 @@ const minSize: IScreenSizeData = {
15
18
 
16
19
  export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
17
20
 
18
- public readonly innerId: InnerId
19
-
20
21
  public manager: ICanvasManager
21
- public view: HTMLCanvasElement
22
+
23
+ public readonly innerId: InnerId
22
24
 
23
25
  public pixelRatio: number
24
26
  public get pixelWidth(): number { return this.width * this.pixelRatio }
@@ -26,91 +28,137 @@ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
26
28
 
27
29
  public bounds: IBounds
28
30
 
31
+ public view: HTMLCanvasElement | OffscreenCanvas
32
+ public parentView: HTMLElement
33
+
34
+ public offscreen: boolean
35
+
29
36
  public recycled?: boolean
30
37
 
31
38
  protected resizeObserver: ResizeObserver
32
39
 
40
+ protected savedblendMode: string
41
+
33
42
  constructor(config?: ILeaferCanvasConfig, manager?: ICanvasManager) {
34
43
  super()
35
44
 
36
45
  if (!config) config = minSize
46
+ if (!config.pixelRatio) config.pixelRatio = devicePixelRatio
37
47
 
38
48
  this.manager = manager
39
49
  this.innerId = IncrementId.create(IncrementId.CNAVAS)
40
50
 
41
- if (config.view) {
51
+ const { view, width, height, pixelRatio, fill, hittable } = config
52
+ const autoLayout = !width || !height
42
53
 
43
- const { view } = config
54
+ this.pixelRatio = pixelRatio
55
+ this.offscreen = Platform.isWorker || config.offscreen
44
56
 
45
- let realView: unknown = (typeof view === 'string') ? document.getElementById(view) : view as HTMLElement
46
- if (realView) {
47
- if (realView instanceof HTMLCanvasElement) {
57
+ if (this.offscreen) {
58
+ view ? this.view = view as OffscreenCanvas : this.__createView()
59
+ } else {
60
+ view ? this.__createViewFrom(view) : this.__createView()
61
+ const { style } = this.view as HTMLCanvasElement
62
+ if (fill) style.backgroundColor = fill
63
+ if (!hittable) style.pointerEvents = 'none'
64
+ if (autoLayout) style.display || (style.display = 'block')
65
+ this.parentView = (this.view as HTMLCanvasElement).parentElement
66
+ }
48
67
 
49
- this.view = realView
68
+ this.__init()
69
+ if (!autoLayout) this.resize(config as IScreenSizeData)
70
+ }
50
71
 
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
- }
72
+ protected __init(): void {
73
+ this.context = this.view.getContext('2d') as ICanvasContext2D
74
+ this.__bindContext()
75
+ }
76
+
77
+ protected __createView(): void {
78
+ this.view = this.offscreen ? new OffscreenCanvas(1, 1) : document.createElement('canvas')
79
+ }
80
+
81
+ protected __createViewFrom(inputView: string | object): void {
82
+ let find: unknown = (typeof inputView === 'string') ? document.getElementById(inputView) : inputView as HTMLElement
83
+ if (find) {
84
+ if (find instanceof HTMLCanvasElement) {
85
+
86
+ this.view = find
61
87
 
62
- this.view = document.createElement('canvas');
63
- (realView as HTMLElement).appendChild(this.view)
64
- }
65
88
  } else {
66
- debug.error(`can't find view by id: ${view}`)
67
- }
68
89
 
69
- }
90
+ let parent = find as HTMLDivElement
91
+ if (find === window || find === document) {
92
+ const div = document.createElement('div')
93
+ const { style } = div
94
+ style.position = 'absolute'
95
+ style.top = style.bottom = style.left = style.right = '0px'
96
+ document.body.appendChild(div)
97
+ parent = div
98
+ }
70
99
 
71
- if (!this.view) this.view = document.createElement('canvas')
72
- this.pixelRatio = config.pixelRatio
100
+ this.__createView()
101
+ const view = this.view as HTMLCanvasElement
73
102
 
74
- if (!config.webgl) {
75
- this.context = this.view.getContext('2d') as ICanvasContext2D
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)
103
+ if (parent.hasChildNodes()) {
104
+ const { style } = view
105
+ style.position = 'absolute'
106
+ style.top = style.left = '0px'
107
+ parent.style.position || (parent.style.position = 'relative')
108
+ }
79
109
 
80
- this.bindContextMethod()
110
+ parent.appendChild(view)
111
+ }
112
+ } else {
113
+ debug.error(`can't find view by id: ${inputView}`)
114
+ this.__createView()
81
115
  }
82
116
  }
83
117
 
84
- public debug(): void { }
118
+ public debug(): void {
119
+ const panel = document.createElement('div')
120
+ panel.style.position = 'absolute'
121
+ panel.style.top = '10px'
122
+ panel.style.left = '10px'
123
+ panel.style.transform = 'scale(0.5)'
124
+ panel.style.transformOrigin = '0px 0px'
125
+ panel.style.pointerEvents = 'none'
126
+ panel.style.zIndex = '100'
127
+ document.body.appendChild(panel)
128
+ panel.appendChild(this.view as HTMLCanvasElement)
129
+ }
85
130
 
86
131
  public pixel(num: number): number { return num * this.pixelRatio }
87
132
 
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))
133
+ public startAutoLayout(autoBounds: IAutoBounds, listener: IResizeEventListener): void {
134
+ if (!this.offscreen) {
135
+ const view = this.view as HTMLCanvasElement
136
+ const check = (parentSize: ISizeData) => {
137
+ const { x, y, width, height } = autoBounds.getBoundsFrom(parentSize)
138
+ const { style } = view
139
+ style.marginLeft = x + 'px'
140
+ style.marginTop = y + 'px'
141
+
142
+ if (width !== this.width || height !== this.height) {
143
+ const { pixelRatio } = this
144
+ const size = { width, height, pixelRatio }
145
+ const oldSize = { width: this.width, height: this.height, pixelRatio: this.pixelRatio }
146
+ this.resize(size)
147
+ if (this.width !== undefined) listener(new ResizeEvent(size, oldSize))
148
+ }
101
149
  }
102
- }
103
150
 
104
- this.resizeObserver = new ResizeObserver((entries) => {
105
- for (const entry of entries) {
106
- check(entry.contentRect)
107
- }
108
- })
151
+ this.resizeObserver = new ResizeObserver((entries) => {
152
+ for (const entry of entries) {
153
+ check(entry.contentRect)
154
+ }
155
+ })
109
156
 
110
- const parent = this.view.parentElement
111
- if (parent) {
112
- this.resizeObserver.observe(parent)
113
- check(parent.getBoundingClientRect())
157
+ const parent = this.parentView
158
+ if (parent) {
159
+ this.resizeObserver.observe(parent)
160
+ check(parent.getBoundingClientRect())
161
+ }
114
162
  }
115
163
  }
116
164
 
@@ -122,30 +170,43 @@ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
122
170
  }
123
171
 
124
172
  public resize(size: IScreenSizeData): void {
125
- const { style } = this.view
126
173
  const { width, height, pixelRatio } = size
127
174
  if (this.isSameSize(size)) return
128
175
 
129
176
  let takeCanvas: ILeaferCanvas
130
177
  if (this.context && this.width) {
131
178
  takeCanvas = this.getSameCanvas()
132
- takeCanvas.copy(this)
179
+ takeCanvas.copyWorld(this)
133
180
  }
134
181
 
135
182
  Object.assign(this, { width, height, pixelRatio })
136
183
  this.bounds = new Bounds(0, 0, width, height)
137
184
 
138
- style.width = width + 'px'
139
- style.height = height + 'px'
185
+ if (!this.offscreen) {
186
+ const { style } = this.view as HTMLCanvasElement
187
+ style.width = width + 'px'
188
+ style.height = height + 'px'
189
+ }
190
+
140
191
  this.view.width = width * pixelRatio
141
192
  this.view.height = height * pixelRatio
193
+ this.smooth = false
142
194
 
143
195
  if (this.context && takeCanvas) {
144
- this.copy(takeCanvas)
196
+ this.copyWorld(takeCanvas)
145
197
  takeCanvas.recycle()
146
198
  }
147
199
  }
148
200
 
201
+ public saveBlendMode(blendMode: string): void {
202
+ this.savedblendMode = this.blendMode
203
+ this.blendMode = blendMode
204
+ }
205
+
206
+ public restoreBlendMode(): void {
207
+ this.blendMode = this.savedblendMode
208
+ }
209
+
149
210
  public setWorld(matrix: IMatrixData, parentMatrix?: IMatrixData, onlyTranslate?: boolean): void {
150
211
  const { pixelRatio } = this
151
212
  if (parentMatrix) {
@@ -197,11 +258,21 @@ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
197
258
  this.strokeCap = options.strokeCap
198
259
  this.strokeJoin = options.strokeJoin
199
260
  this.dashPattern = options.dashPattern
261
+ this.dashOffset = options.dashOffset
200
262
  this.miterLimit = options.miterLimit
201
263
  }
202
264
  }
203
265
 
204
- public setShadow(x: number, y: number, blur: number, color?: string): void {
266
+ public hitFill(point: IPointData, fillRule?: IWindingRule): boolean {
267
+ return this.context.isPointInPath(point.x, point.y, fillRule)
268
+ }
269
+
270
+ public hitStroke(point: IPointData): boolean {
271
+ return this.context.isPointInStroke(point.x, point.y)
272
+ }
273
+
274
+
275
+ public setWorldShadow(x: number, y: number, blur: number, color?: string): void {
205
276
  const { pixelRatio } = this
206
277
  this.shadowOffsetX = x * pixelRatio
207
278
  this.shadowOffsetY = y * pixelRatio
@@ -209,79 +280,65 @@ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
209
280
  this.shadowColor = color || 'black'
210
281
  }
211
282
 
212
- public setBlur(blur: number): void {
283
+ public setWorldBlur(blur: number): void {
213
284
  const { pixelRatio } = this
214
285
  this.filter = `blur(${blur * pixelRatio}px)`
215
286
  }
216
287
 
217
288
 
218
- public hitPath(point: IPointData, fillRule?: IWindingRule): 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 {
289
+ public copyWorld(canvas: ILeaferCanvas, from?: IBoundsData, to?: IBoundsData, blendMode?: string): void {
290
+ if (blendMode) this.blendMode = blendMode
234
291
  if (from) {
235
- if (!to) to = from
236
292
  const { pixelRatio } = this
237
- if (blendMode) this.blendMode = blendMode
293
+ if (!to) to = from
238
294
  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
295
  } else {
241
296
  this.drawImage(canvas.view as HTMLCanvasElement, 0, 0)
242
297
  }
243
-
298
+ if (blendMode) this.blendMode = 'normal'
244
299
  }
245
300
 
246
- public copyWorldToLocal(canvas: ILeaferCanvas, fromWorld: IMatrixWithBoundsData, toLocalBounds: IBoundsData, blendMode?: string): void {
247
- const { pixelRatio } = this
301
+ public copyWorldToInner(canvas: ILeaferCanvas, fromWorld: IMatrixWithBoundsData, toInnerBounds: IBoundsData, blendMode?: string): void {
248
302
  if (blendMode) this.blendMode = blendMode
249
303
  if (fromWorld.b || fromWorld.c) {
250
304
  this.save()
251
305
  this.resetTransform()
252
- this.copy(canvas, fromWorld, BoundsHelper.tempTimesMatrix(toLocalBounds, fromWorld))
306
+ this.copyWorld(canvas, fromWorld, BoundsHelper.tempToOuterOf(toInnerBounds, fromWorld))
253
307
  this.restore()
254
308
  } 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)
309
+ const { pixelRatio } = this
310
+ 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)
256
311
  }
257
312
  if (blendMode) this.blendMode = 'normal'
258
313
  }
259
314
 
260
- public fillBounds(bounds: IBoundsData, color: string | object, blendMode?: string): void {
261
- const { pixelRatio } = this
315
+ public fillWorld(bounds: IBoundsData, color: string | object, blendMode?: string): void {
262
316
  if (blendMode) this.blendMode = blendMode
263
317
  this.fillStyle = color
264
- this.fillRect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
318
+ temp.copy(bounds).scale(this.pixelRatio)
319
+ this.fillRect(temp.x, temp.y, temp.width, temp.height)
265
320
  if (blendMode) this.blendMode = 'normal'
266
321
  }
267
322
 
268
- public strokeBounds(bounds: IBoundsData, color: string | object, blendMode?: string): void {
269
- const { pixelRatio } = this
323
+ public strokeWorld(bounds: IBoundsData, color: string | object, blendMode?: string): void {
270
324
  if (blendMode) this.blendMode = blendMode
271
325
  this.strokeStyle = color
272
- this.strokeRect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
326
+ temp.copy(bounds).scale(this.pixelRatio)
327
+ this.strokeRect(temp.x, temp.y, temp.width, temp.height)
273
328
  if (blendMode) this.blendMode = 'normal'
274
329
  }
275
330
 
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)
331
+ public clearWorld(bounds: IBoundsData, ceilPixel?: boolean): void {
332
+ temp.copy(bounds).scale(this.pixelRatio)
333
+ if (ceilPixel) temp.ceil()
334
+ this.clearRect(temp.x, temp.y, temp.width, temp.height)
279
335
  }
280
336
 
281
- public clipBounds(bounds: IBoundsData): void {
282
- const { pixelRatio } = this
337
+ public clipWorld(bounds: IBoundsData, ceilPixel?: boolean): void {
283
338
  this.beginPath()
284
- this.rect(bounds.x * pixelRatio, bounds.y * pixelRatio, bounds.width * pixelRatio, bounds.height * pixelRatio)
339
+ temp.copy(bounds).scale(this.pixelRatio)
340
+ if (ceilPixel) temp.ceil()
341
+ this.rect(temp.x, temp.y, temp.width, temp.height)
285
342
  this.clip()
286
343
 
287
344
  }
@@ -323,15 +380,27 @@ export class LeaferCanvas extends CanvasBase implements ILeaferCanvas {
323
380
  this.manager.recycle(this)
324
381
  }
325
382
 
383
+ public unloadView(): void {
384
+ if (this.parentView) {
385
+ const view = this.view as HTMLCanvasElement
386
+ const fill = view.style.backgroundColor
387
+ if (fill) this.parentView.style.backgroundColor = fill
388
+ view.remove() // App needs to use
389
+ }
390
+ }
326
391
 
327
392
  public destroy(): void {
328
393
  if (this.view) {
329
394
  super.destroy()
330
395
  this.stopAutoLayout()
396
+ if (!this.offscreen) {
397
+ const view = this.view as HTMLCanvasElement
398
+ if (view.parentElement) view.remove()
399
+ }
331
400
  this.manager = null
332
- if (this.view && this.view.parentElement) this.view.remove()
333
401
  this.view = null
334
- this.bounds = null
402
+ this.parentView = null
403
+ this.context = null
335
404
  }
336
405
  }
337
406