@hxa-rn/react-native-gcanvas 6.0.24-beta.1 → 6.0.26

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 (86) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/LICENSE +203 -21
  3. package/README.md +207 -207
  4. package/harmony/gcanvas/LICENSE +203 -21
  5. package/harmony/gcanvas/NOTICE +32 -32
  6. package/harmony/gcanvas/OAT.xml +37 -37
  7. package/harmony/gcanvas/build-profile.json5 +16 -16
  8. package/harmony/gcanvas/hvigorfile.ts +1 -1
  9. package/harmony/gcanvas/index.ets +1 -1
  10. package/harmony/gcanvas/oh-package.json5 +14 -14
  11. package/harmony/gcanvas/src/main/cpp/CMakeLists.txt +126 -126
  12. package/harmony/gcanvas/src/main/cpp/GCanvasNapi.cpp +485 -485
  13. package/harmony/gcanvas/src/main/cpp/GCanvasXComponent.cpp +106 -106
  14. package/harmony/gcanvas/src/main/cpp/GcanvasPackage.h +24 -24
  15. package/harmony/gcanvas/src/main/ets/GCanvasModuleTurboModule.ts +448 -448
  16. package/harmony/gcanvas/src/main/ets/GCanvasNativeEngine.ts +206 -206
  17. package/harmony/gcanvas/src/main/ets/components/GCanvasView.ets +64 -64
  18. package/harmony/gcanvas/src/main/module.json5 +21 -21
  19. package/harmony/gcanvas/src/main/resources/base/element/string.json +12 -12
  20. package/harmony/gcanvas/src/main/resources/en_US/element/string.json +8 -8
  21. package/harmony/gcanvas/src/main/resources/zh_CN/element/string.json +12 -12
  22. package/harmony/gcanvas.har +0 -0
  23. package/package.json +4 -4
  24. package/packages/gcanvas/.babelrc +4 -4
  25. package/packages/gcanvas/.eslintrc.js +133 -133
  26. package/packages/gcanvas/package.json +19 -19
  27. package/packages/gcanvas/rollup.config.js +13 -13
  28. package/packages/gcanvas/src/bridge/react-native.js +286 -286
  29. package/packages/gcanvas/src/bridge/weex.js +210 -210
  30. package/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
  31. package/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
  32. package/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
  33. package/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
  34. package/packages/gcanvas/src/context/2d/RenderingContext.js +787 -787
  35. package/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
  36. package/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
  37. package/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
  38. package/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
  39. package/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
  40. package/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
  41. package/packages/gcanvas/src/context/webgl/Program.js +20 -20
  42. package/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
  43. package/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
  44. package/packages/gcanvas/src/context/webgl/Shader.js +21 -21
  45. package/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
  46. package/packages/gcanvas/src/context/webgl/Texture.js +21 -21
  47. package/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
  48. package/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
  49. package/packages/gcanvas/src/env/canvas.js +210 -210
  50. package/packages/gcanvas/src/env/image.js +76 -76
  51. package/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
  52. package/packages/gcanvas/src/index.js +56 -56
  53. package/src/components/CanvasView.js +20 -20
  54. package/src/components/GCanvasComponent.js +408 -408
  55. package/src/index.js +69 -69
  56. package/src/packages/gcanvas/.babelrc +4 -4
  57. package/src/packages/gcanvas/.eslintrc.js +133 -133
  58. package/src/packages/gcanvas/package.json +19 -19
  59. package/src/packages/gcanvas/rollup.config.js +13 -13
  60. package/src/packages/gcanvas/src/bridge/react-native.js +288 -288
  61. package/src/packages/gcanvas/src/bridge/weex.js +210 -210
  62. package/src/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
  63. package/src/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
  64. package/src/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
  65. package/src/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
  66. package/src/packages/gcanvas/src/context/2d/RenderingContext.js +801 -801
  67. package/src/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
  68. package/src/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
  69. package/src/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
  70. package/src/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
  71. package/src/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
  72. package/src/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
  73. package/src/packages/gcanvas/src/context/webgl/Program.js +20 -20
  74. package/src/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
  75. package/src/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
  76. package/src/packages/gcanvas/src/context/webgl/Shader.js +21 -21
  77. package/src/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
  78. package/src/packages/gcanvas/src/context/webgl/Texture.js +21 -21
  79. package/src/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
  80. package/src/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
  81. package/src/packages/gcanvas/src/env/canvas.js +210 -210
  82. package/src/packages/gcanvas/src/env/image.js +76 -76
  83. package/src/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
  84. package/src/packages/gcanvas/src/index.js +56 -56
  85. package/src/specs/v1/GCanvasViewNativeComponent.ts +42 -42
  86. package/src/specs/v1/NativeGCanvasModule.ts +140 -140
@@ -1,801 +1,801 @@
1
- import ImageData from '@canvas/image-data/index';
2
- import base64 from 'base64-js';
3
- import FillStylePattern from './FillStylePattern';
4
- import FillStyleLinearGradient from './FillStyleLinearGradient';
5
- import FillStyleRadialGradient from './FillStyleRadialGradient';
6
-
7
- export default class CanvasRenderingContext2D {
8
- _drawCommands = '';
9
-
10
- _savedGlobalAlpha = [];
11
-
12
- timer = null;
13
- componentId = null;
14
-
15
- // _imageMap = new GHashMap();
16
- // _textureMap = new GHashMap();
17
-
18
-
19
- _resetContextAttributes({
20
- globalAlpha = 1.0,
21
-
22
- fillStyle = 'rgb(0,0,0)',
23
- strokeStyle = 'rgb(0,0,0)',
24
-
25
- shadowColor = 'rgb(0,0,0)',
26
- shadowBlur = 0,
27
- shadowOffsetX = 0,
28
- shadowOffsetY = 0,
29
- imageSmoothingEnabled = 1,
30
- lineWidth = 1,
31
- lineCap = 'butt',
32
- lineJoin = 'miter',
33
- lineDash = [],
34
- lineDashOffset = 0,
35
-
36
- miterLimit = 10,
37
-
38
- globalCompositeOperation = 'source-over',
39
-
40
- textAlign = 'start',
41
- textBaseline = 'alphabetic',
42
-
43
- font = '10px sans-serif',
44
- }) {
45
- this.globalAlpha = globalAlpha;
46
-
47
- this.fillStyle = fillStyle;
48
- this.strokeStyle = strokeStyle;
49
-
50
- this.shadowColor = shadowColor;
51
- this.shadowBlur = shadowBlur;
52
- this.shadowOffsetX = shadowOffsetX;
53
- this.shadowOffsetY = shadowOffsetY;
54
- this.imageSmoothingEnabled = imageSmoothingEnabled;
55
- this.lineWidth = lineWidth;
56
- this.lineCap = lineCap;
57
- this.lineJoin = lineJoin;
58
- this.setLineDash(lineDash);
59
- this.lineDashOffset = lineDashOffset;
60
-
61
- this.miterLimit = miterLimit;
62
-
63
- this.globalCompositeOperation = globalCompositeOperation;
64
-
65
- this.textAlign = textAlign;
66
-
67
- this.textBaseline = textBaseline;
68
-
69
- this.font = font;
70
- }
71
-
72
- constructor(canvas) {
73
- this._canvas = canvas;
74
- this.className = 'CanvasRenderingContext2D';
75
- this._resetContextAttributes({});
76
- }
77
-
78
- get canvas() {
79
- return this._canvas;
80
- }
81
-
82
- set fillStyle(value) {
83
- this._fillStyle = value;
84
-
85
- if (typeof value == 'string') {
86
- this._drawCommands = this._drawCommands.concat('F' + value + ';');
87
- } else if (value instanceof FillStylePattern) {
88
- const image = value._img;
89
- const ret = CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
90
- this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';');
91
- const flush = () => this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
92
- // 等 bind 完成再提交 G。立刻 flush 时 TextureMgr 还是空的,
93
- // fillRect 图案宽高为 0,右半会一直停在浅底,再点也不变。
94
- if (ret && typeof ret.then === 'function') {
95
- this._patternBind = ret.then(flush, flush);
96
- } else {
97
- flush();
98
- this._patternBind = Promise.resolve();
99
- }
100
- } else if (value instanceof FillStyleLinearGradient) {
101
- var command = 'D' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ','
102
- + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ','
103
- + value._stop_count;
104
- for (var i = 0; i < value._stop_count; ++i) {
105
- command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
106
- }
107
- this._drawCommands = this._drawCommands.concat(command + ';');
108
- } else if (value instanceof FillStyleRadialGradient) {
109
- var command = 'H' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ',' + value._start_pos._r.toFixed(2) + ','
110
- + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ',' + value._end_pos._r.toFixed(2) + ','
111
- + value._stop_count;
112
- for (var i = 0; i < value._stop_count; ++i) {
113
- command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
114
- }
115
- this._drawCommands = this._drawCommands.concat(command + ';');
116
- }
117
- }
118
-
119
- get fillStyle() {
120
- return this._fillStyle;
121
- }
122
-
123
- get globalAlpha() {
124
- return this._globalAlpha;
125
- }
126
-
127
- set globalAlpha(value) {
128
- this._globalAlpha = value;
129
- this._drawCommands = this._drawCommands.concat('a' + value.toFixed(2) + ';');
130
- }
131
-
132
-
133
- get strokeStyle() {
134
- return this._strokeStyle;
135
- }
136
-
137
- set strokeStyle(value) {
138
- this._strokeStyle = value;
139
-
140
- if (typeof value == 'string') {
141
- this._drawCommands = this._drawCommands.concat('S' + value + ';');
142
- } else if (value instanceof FillStylePattern) {
143
- const image = value._img;
144
- const ret = CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
145
- this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';');
146
- const flush = () => this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
147
- if (ret && typeof ret.then === 'function') {
148
- this._patternBind = ret.then(flush, flush);
149
- } else {
150
- flush();
151
- this._patternBind = Promise.resolve();
152
- }
153
- } else if (value instanceof FillStyleLinearGradient) {
154
- var command = 'D' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ','
155
- + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ','
156
- + value._stop_count;
157
-
158
- for (var i = 0; i < value._stop_count; ++i) {
159
- command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
160
- }
161
- this._drawCommands = this._drawCommands.concat(command + ';');
162
- } else if (value instanceof FillStyleRadialGradient) {
163
- var command = 'H' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ',' + value._start_pos._r.toFixed(2) + ','
164
- + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y + ','.toFixed(2) + value._end_pos._r.toFixed(2) + ','
165
- + value._stop_count;
166
-
167
- for (var i = 0; i < value._stop_count; ++i) {
168
- command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
169
- }
170
- this._drawCommands = this._drawCommands.concat(command + ';');
171
- }
172
- }
173
-
174
- get shadowColor() {
175
- return this._shadowColor;
176
- }
177
-
178
- set shadowColor(value) {
179
- this._shadowColor = value;
180
- this._drawCommands = this._drawCommands.concat('K' + value + ';');
181
- }
182
-
183
- get shadowBlur() {
184
- return this._shadowBlur;
185
- }
186
-
187
- set shadowBlur(value) {
188
- this._shadowBlur = value;
189
- this._drawCommands = this._drawCommands.concat('Z' + value + ';');
190
- }
191
-
192
- get shadowOffsetX() {
193
- return this._shadowOffsetX;
194
- }
195
-
196
- set shadowOffsetX(value) {
197
- this._shadowOffsetX = value;
198
- this._drawCommands = this._drawCommands.concat('X' + value + ';');
199
- }
200
-
201
- get shadowOffsetY() {
202
- return this._shadowOffsetY;
203
- }
204
-
205
- set shadowOffsetY(value) {
206
- this._shadowOffsetY = value;
207
- this._drawCommands = this._drawCommands.concat('Y' + value + ';');
208
- }
209
-
210
- get imageSmoothingEnabled() {
211
- return this._imageSmoothingEnabled;
212
- }
213
-
214
- set imageSmoothingEnabled(value) {
215
- // 'if' can be here because needDisableImageSmoothing in ios/BridgeModule/GCanvasPlugin.mm
216
- // and core/android/3d/view/grenderer.cpp also do the work when resetGlViewport
217
- if (this._imageSmoothingEnabled !== Number(value)) {
218
- this._imageSmoothingEnabled = Number(value);
219
- this._drawCommands = this._drawCommands.concat('O' + this._imageSmoothingEnabled + ';');
220
- this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
221
- }
222
- }
223
-
224
- get lineDashOffset() {
225
- return this._lineDashOffset;
226
- }
227
-
228
- set lineDashOffset(value) {
229
- this._lineWidth = value;
230
- this._drawCommands = this._drawCommands.concat('N' + value + ';');
231
- }
232
-
233
- get lineWidth() {
234
- return this._lineWidth;
235
- }
236
-
237
- set lineWidth(value) {
238
- this._lineWidth = value;
239
- this._drawCommands = this._drawCommands.concat('W' + value + ';');
240
- }
241
-
242
- get lineCap() {
243
- return this._lineCap;
244
- }
245
-
246
- set lineCap(value) {
247
- this._lineCap = value;
248
- this._drawCommands = this._drawCommands.concat('C' + value + ';');
249
- }
250
-
251
- get lineJoin() {
252
- return this._lineJoin;
253
- }
254
-
255
- set lineJoin(value) {
256
- this._lineJoin = value;
257
- this._drawCommands = this._drawCommands.concat('J' + value + ';');
258
- }
259
-
260
- get miterLimit() {
261
- return this._miterLimit;
262
- }
263
-
264
- set miterLimit(value) {
265
- this._miterLimit = value;
266
- this._drawCommands = this._drawCommands.concat('M' + value + ';');
267
- }
268
-
269
- get globalCompositeOperation() {
270
- return this._globalCompositeOperation;
271
- }
272
-
273
- set globalCompositeOperation(value) {
274
- // to avoid blinks, ref to https://github.com/flyskywhy/react-native-gcanvas/issues/31
275
- this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
276
-
277
- this._globalCompositeOperation = value;
278
- let mode = 0;
279
- switch (value) {
280
- case 'source-over':
281
- mode = 0;
282
- break;
283
- case 'source-atop':
284
- mode = 5;
285
- break;
286
- case 'source-in':
287
- mode = 0; // TODO
288
- break;
289
- case 'source-out':
290
- mode = 10;
291
- break;
292
- case 'destination-over':
293
- mode = 4;
294
- break;
295
- case 'destination-atop':
296
- mode = 4; // TODO
297
- break;
298
- case 'destination-in':
299
- mode = 11;
300
- break;
301
- case 'destination-out':
302
- mode = 3;
303
- break;
304
- case 'lighter':
305
- mode = 1;
306
- break;
307
- case 'copy':
308
- mode = 7;
309
- break;
310
- case 'xor':
311
- mode = 6;
312
- break;
313
- // TODO: add more mode ref to
314
- // https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
315
- // GCompositeOperation in core/src/gcanvas/GContext2dType.h
316
- // GBlendOperationFuncs in core/src/gcanvas/GCanvas2dContext.cpp
317
- default:
318
- mode = 0;
319
- }
320
-
321
- this._drawCommands = this._drawCommands.concat('B' + mode + ';');
322
- }
323
-
324
- get textAlign() {
325
- return this._textAlign;
326
- }
327
-
328
- set textAlign(value) {
329
- this._textAlign = value;
330
- let Align = 0;
331
- switch (value) {
332
- case 'start':
333
- Align = 0;
334
- break;
335
- case 'end':
336
- Align = 1;
337
- break;
338
- case 'left':
339
- Align = 2;
340
- break;
341
- case 'center':
342
- Align = 3;
343
- break;
344
- case 'right':
345
- Align = 4;
346
- break;
347
- default:
348
- Align = 0;
349
- }
350
-
351
- this._drawCommands = this._drawCommands.concat('A' + Align + ';');
352
- }
353
-
354
- get textBaseline() {
355
- return this._textBaseline;
356
- }
357
-
358
- set textBaseline(value) {
359
- this._textBaseline = value;
360
- let baseline = 0;
361
- switch (value) {
362
- case 'alphabetic':
363
- baseline = 0;
364
- break;
365
- case 'middle':
366
- baseline = 1;
367
- break;
368
- case 'top':
369
- baseline = 2;
370
- break;
371
- case 'hanging':
372
- baseline = 3;
373
- break;
374
- case 'bottom':
375
- baseline = 4;
376
- break;
377
- case 'ideographic':
378
- baseline = 5;
379
- break;
380
- default:
381
- baseline = 0;
382
- break;
383
- }
384
-
385
- this._drawCommands = this._drawCommands.concat('E' + baseline + ';');
386
- }
387
-
388
- get font() {
389
- return this._font;
390
- }
391
-
392
- set font(value) {
393
- this._font = value;
394
- this._drawCommands = this._drawCommands.concat('j' + value + ';');
395
- }
396
-
397
- getLineDash() {
398
- return this._lineDash;
399
- }
400
-
401
- setLineDash(value) {
402
- if( Array.isArray(value) ) {
403
- this._lineDash = value;
404
- this._drawCommands = this._drawCommands.concat('I'+ value.length +','+ value.join(',') + ';');
405
- }
406
- }
407
- setTransform(a, b, c, d, tx, ty) {
408
- this._drawCommands = this._drawCommands.concat('t'
409
- + (a === 1 ? '1' : a.toFixed(2)) + ','
410
- + (b === 0 ? '0' : b.toFixed(2)) + ','
411
- + (c === 0 ? '0' : c.toFixed(2)) + ','
412
- + (d === 1 ? '1' : d.toFixed(2)) + ',' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';');
413
- }
414
-
415
- transform(a, b, c, d, tx, ty) {
416
- this._drawCommands = this._drawCommands.concat('f'
417
- + (a === 1 ? '1' : a.toFixed(2)) + ','
418
- + (b === 0 ? '0' : b.toFixed(2)) + ','
419
- + (c === 0 ? '0' : c.toFixed(2)) + ','
420
- + (d === 1 ? '1' : d.toFixed(2)) + ',' + tx + ',' + ty + ';');
421
- }
422
-
423
- resetTransform() {
424
- this._drawCommands = this._drawCommands.concat('m;');
425
- }
426
-
427
- scale(a, d) {
428
- this._drawCommands = this._drawCommands.concat('k' + a.toFixed(2) + ','
429
- + d.toFixed(2) + ';');
430
- }
431
-
432
- rotate(angle) {
433
- this._drawCommands = this._drawCommands
434
- .concat('r' + angle.toFixed(6) + ';');
435
- }
436
-
437
- translate(tx, ty) {
438
- this._drawCommands = this._drawCommands.concat('l' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';');
439
- }
440
-
441
- save() {
442
- this._savedGlobalAlpha.push(this._globalAlpha);
443
- this._drawCommands = this._drawCommands.concat('v;');
444
- }
445
-
446
- restore() {
447
- this._drawCommands = this._drawCommands.concat('e;');
448
- this._globalAlpha = this._savedGlobalAlpha.pop();
449
- }
450
-
451
- createPattern(img, pattern) {
452
- return new FillStylePattern(img, pattern);
453
- }
454
-
455
- createLinearGradient(x0, y0, x1, y1) {
456
- return new FillStyleLinearGradient(x0, y0, x1, y1);
457
- }
458
-
459
- createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
460
- return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1);
461
- };
462
-
463
- strokeRect(x, y, w, h) {
464
- this._drawCommands = this._drawCommands.concat('s' + x + ',' + y + ',' + w + ',' + h + ';');
465
- }
466
-
467
-
468
- clearRect(x, y, w, h) {
469
- this._drawCommands = this._drawCommands.concat('c' + x + ',' + y + ',' + w
470
- + ',' + h + ';');
471
- }
472
-
473
- clip() {
474
- this._drawCommands = this._drawCommands.concat('p;');
475
- }
476
-
477
- resetClip() {
478
- this._drawCommands = this._drawCommands.concat('q;');
479
- }
480
-
481
- closePath() {
482
- this._drawCommands = this._drawCommands.concat('o;');
483
- }
484
-
485
- moveTo(x, y) {
486
- this._drawCommands = this._drawCommands.concat('g' + x.toFixed(2) + ',' + y.toFixed(2) + ';');
487
- }
488
-
489
- lineTo(x, y) {
490
- this._drawCommands = this._drawCommands.concat('i' + x.toFixed(2) + ',' + y.toFixed(2) + ';');
491
- }
492
-
493
- quadraticCurveTo = function(cpx, cpy, x, y) {
494
- this._drawCommands = this._drawCommands.concat('u' + cpx + ',' + cpy + ',' + x + ',' + y + ';');
495
- }
496
-
497
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, ) {
498
- this._drawCommands = this._drawCommands.concat(
499
- 'z' + cp1x.toFixed(2) + ',' + cp1y.toFixed(2) + ',' + cp2x.toFixed(2) + ',' + cp2y.toFixed(2) + ',' +
500
- x.toFixed(2) + ',' + y.toFixed(2) + ';');
501
- }
502
-
503
- arcTo(x1, y1, x2, y2, radius) {
504
- this._drawCommands = this._drawCommands.concat('h' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' + radius + ';');
505
- }
506
-
507
- beginPath() {
508
- this._drawCommands = this._drawCommands.concat('b;');
509
- }
510
-
511
-
512
- fillRect(x, y, w, h) {
513
- this._drawCommands = this._drawCommands.concat('n' + x + ',' + y + ',' + w
514
- + ',' + h + ';');
515
- }
516
-
517
- rect(x, y, w, h) {
518
- this._drawCommands = this._drawCommands.concat('w' + x + ',' + y + ',' + w + ',' + h + ';');
519
- }
520
-
521
- fill() {
522
- this._drawCommands = this._drawCommands.concat('L;');
523
- }
524
-
525
- stroke(path) {
526
- this._drawCommands = this._drawCommands.concat('x;');
527
- }
528
-
529
- arc(x, y, radius, startAngle, endAngle, anticlockwise) {
530
- let ianticlockwise = 0;
531
- if (anticlockwise) {
532
- ianticlockwise = 1;
533
- }
534
-
535
- this._drawCommands = this._drawCommands.concat(
536
- 'y' + x.toFixed(2) + ',' + y.toFixed(2) + ','
537
- + radius.toFixed(2) + ',' + startAngle + ',' + endAngle + ',' + ianticlockwise
538
- + ';'
539
- );
540
- }
541
-
542
- fillText(text, x, y) {
543
- let tmptext = text.replace(/!/g, '!!');
544
- tmptext = tmptext.replace(/,/g, '!,');
545
- tmptext = tmptext.replace(/;/g, '!;');
546
- this._drawCommands = this._drawCommands.concat('T' + tmptext + ',' + x + ',' + y + ',0.0;');
547
- }
548
-
549
- strokeText = function(text, x, y) {
550
- let tmptext = text.replace(/!/g, '!!');
551
- tmptext = tmptext.replace(/,/g, '!,');
552
- tmptext = tmptext.replace(/;/g, '!;');
553
- this._drawCommands = this._drawCommands.concat('U' + tmptext + ',' + x + ',' + y + ',0.0;');
554
- }
555
-
556
- measureText = function(text) {
557
- cancelAnimationFrame(this._canvas._renderLoopId);
558
- this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
559
- let tmpMeasure = CanvasRenderingContext2D.GBridge.callNative(
560
- this.componentId,
561
- 'V' + text + ';',
562
- false,
563
- '2d',
564
- 'sync',
565
- 'execWithoutDisplay',
566
- );
567
- this._canvas._renderLoopId = requestAnimationFrame(this._canvas._renderLoop.bind(this._canvas));
568
-
569
- let textMetrics = {
570
- width: 37.343750,
571
- actualBoundingBoxAscent: 18.4,
572
- actualBoundingBoxDescent: 5.6,
573
- };
574
- try {
575
- const {width, height, actualBoundingBoxAscent, actualBoundingBoxDescent} = JSON.parse(tmpMeasure);
576
- textMetrics.width = width;
577
- if (actualBoundingBoxAscent > 0 && actualBoundingBoxDescent > 0) {
578
- textMetrics.actualBoundingBoxAscent = actualBoundingBoxAscent;
579
- textMetrics.actualBoundingBoxDescent = actualBoundingBoxDescent;
580
- } else {
581
- textMetrics.actualBoundingBoxAscent = height * 1.15 / 1.5;
582
- textMetrics.actualBoundingBoxDescent = height - textMetrics.actualBoundingBoxAscent;
583
- }
584
- } catch (err) {
585
- __DEV__ && console.warn('measureText parse error:' + tmpMeasure);
586
- }
587
-
588
- return textMetrics;
589
- }
590
-
591
- isPointInPath = function(x, y) {
592
- throw new Error('GCanvas not supported yet');
593
- }
594
-
595
- drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
596
- const numArgs = arguments.length;
597
- let srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH;
598
-
599
- if (numArgs === 3) {
600
- srcX = 0;
601
- srcY = 0;
602
- srcW = image.width;
603
- srcH = image.height;
604
- dstX = parseFloat(sx) || 0.0;
605
- dstY = parseFloat(sy) || 0.0;
606
- dstW = image.width;
607
- dstH = image.height;
608
- } else if (numArgs === 5) {
609
- srcX = 0;
610
- srcY = 0;
611
- srcW = image.width;
612
- srcH = image.height;
613
- dstX = parseFloat(sx) || 0.0;
614
- dstY = parseFloat(sy) || 0.0;
615
- dstW = parseInt(sw) || image.width;
616
- dstH = parseInt(sh) || image.height;
617
- } else if (numArgs === 9) {
618
- srcX = parseFloat(sx) || 0.0;
619
- srcY = parseFloat(sy) || 0.0;
620
- srcW = parseInt(sw) || image.width;
621
- srcH = parseInt(sh) || image.height;
622
- dstX = parseFloat(dx) || 0.0;
623
- dstY = parseFloat(dy) || 0.0;
624
- dstW = parseInt(dw) || image.width;
625
- dstH = parseInt(dh) || image.height;
626
- }
627
-
628
- const imageIsCanvas = image.hasOwnProperty('nodeName') && image.nodeName.toLowerCase() === 'canvas';
629
- if (imageIsCanvas) {
630
- if (!image._context) {
631
- return;
632
- }
633
-
634
- // If use 'execWithDisplay' here, will cause 2nd imageIsCanvas flick of
635
- // https://github.com/flyskywhy/GCanvasRNExamples/blob/master/app/components/AudioWaveSurfer.js
636
- //
637
- // Even if use flushJsCommands2CallNative here, will cause imageIsCanvas not display on screen if
638
- // not follow by other ctx.some().
639
- this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
640
-
641
- image._context.flushJsCommands2CallNative('sync');
642
-
643
- let sCanvasId = image.id;
644
- // even README.md said "despite of values into `canvas.width =` and `canvas.height =`",
645
- // because canvas of sCanvasId is mostly comes from document.createElement('canvas')
646
- // that just like offscreen canvas, so it will use style {position: 'absolute'} and
647
- // that means it's clientWidth will not change (to avoid re-render as offscreen), so
648
- // it's meaningful to use canvas.width usually be changed after document.createElement('canvas')
649
- let sCanvasWidth = image.width;
650
- let sCanvasHeight = image.height;
651
- CanvasRenderingContext2D.GBridge.drawCanvas2Canvas({
652
- srcComponentId: sCanvasId,
653
- dstComponentId: this.componentId,
654
- tw: sCanvasWidth,
655
- th: sCanvasHeight,
656
- sx: srcX,
657
- sy: srcY,
658
- sw: srcW,
659
- sh: srcH,
660
- dx: dstX,
661
- dy: dstY,
662
- dw: dstW,
663
- dh: dstH,
664
- });
665
- } else {
666
- CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
667
- this._drawCommands += 'd' + image._id + ','
668
- + srcX + ',' + srcY + ',' + srcW + ',' + srcH + ','
669
- + dstX + ',' + dstY + ',' + dstW + ',' + dstH + ';';
670
-
671
- // If use 'execWithDisplay' here, will cause 2nd image flick with 'forceCanvas = true' of
672
- // https://github.com/flyskywhy/GCanvasRNExamples/blob/master/app/components/Pixi.js ,
673
- // because in '2d' not 'webgl' PIXI, every PIXI app tick will let all image sprites be drawImage().
674
- //
675
- // Even if use flushJsCommands2CallNative here, will cause image not display on screen if
676
- // not follow by other ctx.some().
677
- // this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
678
- }
679
- }
680
-
681
- createImageData(widthOrImagedata, height) {
682
- if (arguments.length === 2) {
683
- return new ImageData(widthOrImagedata, height);
684
- } else {
685
- return new ImageData(widthOrImagedata.width, widthOrImagedata.height);
686
- }
687
- }
688
-
689
- // not a Web canvas API, used by @flyskywhy/react-native-gcanvas
690
- flushJsCommands2CallNative(
691
- methodType = 'async',
692
- optionType = 'execWithDisplay',
693
- ) {
694
- const commands = this._drawCommands;
695
- this._drawCommands = ''; // let cmds cache be empty
696
-
697
- if (commands !== '') {
698
- CanvasRenderingContext2D.GBridge.callNative(
699
- this.componentId,
700
- commands,
701
- false,
702
- '2d',
703
- methodType,
704
- optionType,
705
- );
706
- }
707
- }
708
-
709
- // no need ctx.getImageData(x * PixelRatio.get(), y * PixelRatio.get(), w * PixelRatio.get(), h * PixelRatio.get())
710
- // e.g. ctx.getImageData(0, 0, 2, 2) will return a `w === 2, h === 2` ImageData
711
- // since PixelsSampler() in GetImageData() of core/src/gcanvas/GCanvas2dContext.cpp
712
- // will scale the image automatically
713
- getImageData(sx, sy, sw, sh) {
714
- // if not stop _renderLoop(), sometimes will cause display issue with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN
715
- cancelAnimationFrame(this._canvas._renderLoopId);
716
- // If use 'async' here, will cause last commands be executed after getImageData's 'sync'.
717
- if (this._drawCommands.includes(';T') || this._drawCommands.startsWith('T')) {
718
- // Use 'sync' + 'execWithDisplay' here to make sure last graphics by fillText() be generated
719
- // before execute getImageData's 'R', ref to context.fillText in
720
- // https://github.com/flyskywhy/react-native-runescape-text/blob/main/src/classes/Motion.js
721
- // TODO: more commands other than 'T' if someone issue on github
722
- this.flushJsCommands2CallNative('sync', 'execWithDisplay');
723
- } else {
724
- // If use 'execWithDisplay' here, will cause low JS FPS on iOS with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN ,
725
- // because 'lightener' will call getImageData frequently while moving finger, will cause setNeedsDisplay() then
726
- // drawInRect() be invoked more than 1 times in 16ms e.g. 1times/1ms thus cause low JS FPS!
727
- this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
728
- }
729
- // now can getImageData from last generated (even not displayed) graphics, otherwise will `return new ImageData(w, h)`
730
- // thus `this.ctx.putImageData()` has no effect with the first 'Click me to draw some on canvas' in README.md
731
-
732
- let x = sx;
733
- let y = sy;
734
- let w = sw;
735
- let h = sh;
736
- if (sx + sw > this.canvas.clientWidth) {
737
- x = 0;
738
- w = this.canvas.clientWidth;
739
- }
740
- if (sy + sh > this.canvas.clientHeight) {
741
- y = 0;
742
- h = this.canvas.clientHeight;
743
- }
744
-
745
- let base64Data = CanvasRenderingContext2D.GBridge.callNative(
746
- this.componentId,
747
- 'R' + x + ',' + y + ',' + w + ',' + h + ';',
748
- false,
749
- '2d',
750
- 'sync',
751
- 'execWithoutDisplay',
752
- );
753
-
754
- // start _renderLoop() again, and thus display last generated graphics on screen
755
- // TODO: allow empty commands invoke callNative() once in flushJsCommands2CallNative()
756
- // while start _renderLoop() just in case no commands after getImageData's 'R'
757
- // thus no display
758
- // TODO: this.flushJsCommands2CallNative('sync', 'execWithDisplay') on Android
759
- // test with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN
760
- // to see if low JS FPS
761
- this._canvas._renderLoopId = requestAnimationFrame(this._canvas._renderLoop.bind(this._canvas));
762
-
763
- if (base64Data === '') {
764
- console.warn('getImageData: not good to be here, should refactor source code somewhere');
765
- return new ImageData(w, h);
766
- }
767
-
768
- return new ImageData(new Uint8ClampedArray(base64.toByteArray(base64Data)), w, h);
769
- }
770
-
771
- // no need ctx.getImageData(imageData, x * PixelRatio.get(), y * PixelRatio.get())
772
- // and the imageData is also not `w * PixelRatio.get(), h * PixelRatio.get()`
773
- putImageData(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
774
- const base64Data = base64.fromByteArray(imageData.data);
775
- let tw = imageData.width; // textureWidth
776
- let th = imageData.height;
777
-
778
- let sx, sy, sw, sh;
779
-
780
- if (arguments.length === 3) {
781
- sx = 0;
782
- sy = 0;
783
- sw = tw;
784
- sh = th;
785
- } else {
786
- sx = dirtyX;
787
- sy = dirtyY;
788
- // dirtyWidth in https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
789
- // means the width of the (src) image data
790
- sw = dirtyWidth;
791
- sh = dirtyHeight;
792
- }
793
-
794
- if (this._canvas._isAutoClearRectBeforePutImageData) {
795
- this.clearRect(x + sx, y + sy, sw, sh);
796
- }
797
-
798
- this._drawCommands = this._drawCommands.concat('P' + base64Data + ',' + tw + ',' + th + ','
799
- + x + ',' + y + ',' + sx + ',' + sy + ',' + sw + ',' + sh + ';');
800
- }
801
- }
1
+ import ImageData from '@canvas/image-data/index';
2
+ import base64 from 'base64-js';
3
+ import FillStylePattern from './FillStylePattern';
4
+ import FillStyleLinearGradient from './FillStyleLinearGradient';
5
+ import FillStyleRadialGradient from './FillStyleRadialGradient';
6
+
7
+ export default class CanvasRenderingContext2D {
8
+ _drawCommands = '';
9
+
10
+ _savedGlobalAlpha = [];
11
+
12
+ timer = null;
13
+ componentId = null;
14
+
15
+ // _imageMap = new GHashMap();
16
+ // _textureMap = new GHashMap();
17
+
18
+
19
+ _resetContextAttributes({
20
+ globalAlpha = 1.0,
21
+
22
+ fillStyle = 'rgb(0,0,0)',
23
+ strokeStyle = 'rgb(0,0,0)',
24
+
25
+ shadowColor = 'rgb(0,0,0)',
26
+ shadowBlur = 0,
27
+ shadowOffsetX = 0,
28
+ shadowOffsetY = 0,
29
+ imageSmoothingEnabled = 1,
30
+ lineWidth = 1,
31
+ lineCap = 'butt',
32
+ lineJoin = 'miter',
33
+ lineDash = [],
34
+ lineDashOffset = 0,
35
+
36
+ miterLimit = 10,
37
+
38
+ globalCompositeOperation = 'source-over',
39
+
40
+ textAlign = 'start',
41
+ textBaseline = 'alphabetic',
42
+
43
+ font = '10px sans-serif',
44
+ }) {
45
+ this.globalAlpha = globalAlpha;
46
+
47
+ this.fillStyle = fillStyle;
48
+ this.strokeStyle = strokeStyle;
49
+
50
+ this.shadowColor = shadowColor;
51
+ this.shadowBlur = shadowBlur;
52
+ this.shadowOffsetX = shadowOffsetX;
53
+ this.shadowOffsetY = shadowOffsetY;
54
+ this.imageSmoothingEnabled = imageSmoothingEnabled;
55
+ this.lineWidth = lineWidth;
56
+ this.lineCap = lineCap;
57
+ this.lineJoin = lineJoin;
58
+ this.setLineDash(lineDash);
59
+ this.lineDashOffset = lineDashOffset;
60
+
61
+ this.miterLimit = miterLimit;
62
+
63
+ this.globalCompositeOperation = globalCompositeOperation;
64
+
65
+ this.textAlign = textAlign;
66
+
67
+ this.textBaseline = textBaseline;
68
+
69
+ this.font = font;
70
+ }
71
+
72
+ constructor(canvas) {
73
+ this._canvas = canvas;
74
+ this.className = 'CanvasRenderingContext2D';
75
+ this._resetContextAttributes({});
76
+ }
77
+
78
+ get canvas() {
79
+ return this._canvas;
80
+ }
81
+
82
+ set fillStyle(value) {
83
+ this._fillStyle = value;
84
+
85
+ if (typeof value == 'string') {
86
+ this._drawCommands = this._drawCommands.concat('F' + value + ';');
87
+ } else if (value instanceof FillStylePattern) {
88
+ const image = value._img;
89
+ const ret = CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
90
+ this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';');
91
+ const flush = () => this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
92
+ // 等 bind 完成再提交 G。立刻 flush 时 TextureMgr 还是空的,
93
+ // fillRect 图案宽高为 0,右半会一直停在浅底,再点也不变。
94
+ if (ret && typeof ret.then === 'function') {
95
+ this._patternBind = ret.then(flush, flush);
96
+ } else {
97
+ flush();
98
+ this._patternBind = Promise.resolve();
99
+ }
100
+ } else if (value instanceof FillStyleLinearGradient) {
101
+ var command = 'D' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ','
102
+ + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ','
103
+ + value._stop_count;
104
+ for (var i = 0; i < value._stop_count; ++i) {
105
+ command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
106
+ }
107
+ this._drawCommands = this._drawCommands.concat(command + ';');
108
+ } else if (value instanceof FillStyleRadialGradient) {
109
+ var command = 'H' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ',' + value._start_pos._r.toFixed(2) + ','
110
+ + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ',' + value._end_pos._r.toFixed(2) + ','
111
+ + value._stop_count;
112
+ for (var i = 0; i < value._stop_count; ++i) {
113
+ command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
114
+ }
115
+ this._drawCommands = this._drawCommands.concat(command + ';');
116
+ }
117
+ }
118
+
119
+ get fillStyle() {
120
+ return this._fillStyle;
121
+ }
122
+
123
+ get globalAlpha() {
124
+ return this._globalAlpha;
125
+ }
126
+
127
+ set globalAlpha(value) {
128
+ this._globalAlpha = value;
129
+ this._drawCommands = this._drawCommands.concat('a' + value.toFixed(2) + ';');
130
+ }
131
+
132
+
133
+ get strokeStyle() {
134
+ return this._strokeStyle;
135
+ }
136
+
137
+ set strokeStyle(value) {
138
+ this._strokeStyle = value;
139
+
140
+ if (typeof value == 'string') {
141
+ this._drawCommands = this._drawCommands.concat('S' + value + ';');
142
+ } else if (value instanceof FillStylePattern) {
143
+ const image = value._img;
144
+ const ret = CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
145
+ this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';');
146
+ const flush = () => this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
147
+ if (ret && typeof ret.then === 'function') {
148
+ this._patternBind = ret.then(flush, flush);
149
+ } else {
150
+ flush();
151
+ this._patternBind = Promise.resolve();
152
+ }
153
+ } else if (value instanceof FillStyleLinearGradient) {
154
+ var command = 'D' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ','
155
+ + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y.toFixed(2) + ','
156
+ + value._stop_count;
157
+
158
+ for (var i = 0; i < value._stop_count; ++i) {
159
+ command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
160
+ }
161
+ this._drawCommands = this._drawCommands.concat(command + ';');
162
+ } else if (value instanceof FillStyleRadialGradient) {
163
+ var command = 'H' + value._start_pos._x.toFixed(2) + ',' + value._start_pos._y.toFixed(2) + ',' + value._start_pos._r.toFixed(2) + ','
164
+ + value._end_pos._x.toFixed(2) + ',' + value._end_pos._y + ','.toFixed(2) + value._end_pos._r.toFixed(2) + ','
165
+ + value._stop_count;
166
+
167
+ for (var i = 0; i < value._stop_count; ++i) {
168
+ command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color;
169
+ }
170
+ this._drawCommands = this._drawCommands.concat(command + ';');
171
+ }
172
+ }
173
+
174
+ get shadowColor() {
175
+ return this._shadowColor;
176
+ }
177
+
178
+ set shadowColor(value) {
179
+ this._shadowColor = value;
180
+ this._drawCommands = this._drawCommands.concat('K' + value + ';');
181
+ }
182
+
183
+ get shadowBlur() {
184
+ return this._shadowBlur;
185
+ }
186
+
187
+ set shadowBlur(value) {
188
+ this._shadowBlur = value;
189
+ this._drawCommands = this._drawCommands.concat('Z' + value + ';');
190
+ }
191
+
192
+ get shadowOffsetX() {
193
+ return this._shadowOffsetX;
194
+ }
195
+
196
+ set shadowOffsetX(value) {
197
+ this._shadowOffsetX = value;
198
+ this._drawCommands = this._drawCommands.concat('X' + value + ';');
199
+ }
200
+
201
+ get shadowOffsetY() {
202
+ return this._shadowOffsetY;
203
+ }
204
+
205
+ set shadowOffsetY(value) {
206
+ this._shadowOffsetY = value;
207
+ this._drawCommands = this._drawCommands.concat('Y' + value + ';');
208
+ }
209
+
210
+ get imageSmoothingEnabled() {
211
+ return this._imageSmoothingEnabled;
212
+ }
213
+
214
+ set imageSmoothingEnabled(value) {
215
+ // 'if' can be here because needDisableImageSmoothing in ios/BridgeModule/GCanvasPlugin.mm
216
+ // and core/android/3d/view/grenderer.cpp also do the work when resetGlViewport
217
+ if (this._imageSmoothingEnabled !== Number(value)) {
218
+ this._imageSmoothingEnabled = Number(value);
219
+ this._drawCommands = this._drawCommands.concat('O' + this._imageSmoothingEnabled + ';');
220
+ this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
221
+ }
222
+ }
223
+
224
+ get lineDashOffset() {
225
+ return this._lineDashOffset;
226
+ }
227
+
228
+ set lineDashOffset(value) {
229
+ this._lineWidth = value;
230
+ this._drawCommands = this._drawCommands.concat('N' + value + ';');
231
+ }
232
+
233
+ get lineWidth() {
234
+ return this._lineWidth;
235
+ }
236
+
237
+ set lineWidth(value) {
238
+ this._lineWidth = value;
239
+ this._drawCommands = this._drawCommands.concat('W' + value + ';');
240
+ }
241
+
242
+ get lineCap() {
243
+ return this._lineCap;
244
+ }
245
+
246
+ set lineCap(value) {
247
+ this._lineCap = value;
248
+ this._drawCommands = this._drawCommands.concat('C' + value + ';');
249
+ }
250
+
251
+ get lineJoin() {
252
+ return this._lineJoin;
253
+ }
254
+
255
+ set lineJoin(value) {
256
+ this._lineJoin = value;
257
+ this._drawCommands = this._drawCommands.concat('J' + value + ';');
258
+ }
259
+
260
+ get miterLimit() {
261
+ return this._miterLimit;
262
+ }
263
+
264
+ set miterLimit(value) {
265
+ this._miterLimit = value;
266
+ this._drawCommands = this._drawCommands.concat('M' + value + ';');
267
+ }
268
+
269
+ get globalCompositeOperation() {
270
+ return this._globalCompositeOperation;
271
+ }
272
+
273
+ set globalCompositeOperation(value) {
274
+ // to avoid blinks, ref to https://github.com/flyskywhy/react-native-gcanvas/issues/31
275
+ this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
276
+
277
+ this._globalCompositeOperation = value;
278
+ let mode = 0;
279
+ switch (value) {
280
+ case 'source-over':
281
+ mode = 0;
282
+ break;
283
+ case 'source-atop':
284
+ mode = 5;
285
+ break;
286
+ case 'source-in':
287
+ mode = 0; // TODO
288
+ break;
289
+ case 'source-out':
290
+ mode = 10;
291
+ break;
292
+ case 'destination-over':
293
+ mode = 4;
294
+ break;
295
+ case 'destination-atop':
296
+ mode = 4; // TODO
297
+ break;
298
+ case 'destination-in':
299
+ mode = 11;
300
+ break;
301
+ case 'destination-out':
302
+ mode = 3;
303
+ break;
304
+ case 'lighter':
305
+ mode = 1;
306
+ break;
307
+ case 'copy':
308
+ mode = 7;
309
+ break;
310
+ case 'xor':
311
+ mode = 6;
312
+ break;
313
+ // TODO: add more mode ref to
314
+ // https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
315
+ // GCompositeOperation in core/src/gcanvas/GContext2dType.h
316
+ // GBlendOperationFuncs in core/src/gcanvas/GCanvas2dContext.cpp
317
+ default:
318
+ mode = 0;
319
+ }
320
+
321
+ this._drawCommands = this._drawCommands.concat('B' + mode + ';');
322
+ }
323
+
324
+ get textAlign() {
325
+ return this._textAlign;
326
+ }
327
+
328
+ set textAlign(value) {
329
+ this._textAlign = value;
330
+ let Align = 0;
331
+ switch (value) {
332
+ case 'start':
333
+ Align = 0;
334
+ break;
335
+ case 'end':
336
+ Align = 1;
337
+ break;
338
+ case 'left':
339
+ Align = 2;
340
+ break;
341
+ case 'center':
342
+ Align = 3;
343
+ break;
344
+ case 'right':
345
+ Align = 4;
346
+ break;
347
+ default:
348
+ Align = 0;
349
+ }
350
+
351
+ this._drawCommands = this._drawCommands.concat('A' + Align + ';');
352
+ }
353
+
354
+ get textBaseline() {
355
+ return this._textBaseline;
356
+ }
357
+
358
+ set textBaseline(value) {
359
+ this._textBaseline = value;
360
+ let baseline = 0;
361
+ switch (value) {
362
+ case 'alphabetic':
363
+ baseline = 0;
364
+ break;
365
+ case 'middle':
366
+ baseline = 1;
367
+ break;
368
+ case 'top':
369
+ baseline = 2;
370
+ break;
371
+ case 'hanging':
372
+ baseline = 3;
373
+ break;
374
+ case 'bottom':
375
+ baseline = 4;
376
+ break;
377
+ case 'ideographic':
378
+ baseline = 5;
379
+ break;
380
+ default:
381
+ baseline = 0;
382
+ break;
383
+ }
384
+
385
+ this._drawCommands = this._drawCommands.concat('E' + baseline + ';');
386
+ }
387
+
388
+ get font() {
389
+ return this._font;
390
+ }
391
+
392
+ set font(value) {
393
+ this._font = value;
394
+ this._drawCommands = this._drawCommands.concat('j' + value + ';');
395
+ }
396
+
397
+ getLineDash() {
398
+ return this._lineDash;
399
+ }
400
+
401
+ setLineDash(value) {
402
+ if( Array.isArray(value) ) {
403
+ this._lineDash = value;
404
+ this._drawCommands = this._drawCommands.concat('I'+ value.length +','+ value.join(',') + ';');
405
+ }
406
+ }
407
+ setTransform(a, b, c, d, tx, ty) {
408
+ this._drawCommands = this._drawCommands.concat('t'
409
+ + (a === 1 ? '1' : a.toFixed(2)) + ','
410
+ + (b === 0 ? '0' : b.toFixed(2)) + ','
411
+ + (c === 0 ? '0' : c.toFixed(2)) + ','
412
+ + (d === 1 ? '1' : d.toFixed(2)) + ',' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';');
413
+ }
414
+
415
+ transform(a, b, c, d, tx, ty) {
416
+ this._drawCommands = this._drawCommands.concat('f'
417
+ + (a === 1 ? '1' : a.toFixed(2)) + ','
418
+ + (b === 0 ? '0' : b.toFixed(2)) + ','
419
+ + (c === 0 ? '0' : c.toFixed(2)) + ','
420
+ + (d === 1 ? '1' : d.toFixed(2)) + ',' + tx + ',' + ty + ';');
421
+ }
422
+
423
+ resetTransform() {
424
+ this._drawCommands = this._drawCommands.concat('m;');
425
+ }
426
+
427
+ scale(a, d) {
428
+ this._drawCommands = this._drawCommands.concat('k' + a.toFixed(2) + ','
429
+ + d.toFixed(2) + ';');
430
+ }
431
+
432
+ rotate(angle) {
433
+ this._drawCommands = this._drawCommands
434
+ .concat('r' + angle.toFixed(6) + ';');
435
+ }
436
+
437
+ translate(tx, ty) {
438
+ this._drawCommands = this._drawCommands.concat('l' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';');
439
+ }
440
+
441
+ save() {
442
+ this._savedGlobalAlpha.push(this._globalAlpha);
443
+ this._drawCommands = this._drawCommands.concat('v;');
444
+ }
445
+
446
+ restore() {
447
+ this._drawCommands = this._drawCommands.concat('e;');
448
+ this._globalAlpha = this._savedGlobalAlpha.pop();
449
+ }
450
+
451
+ createPattern(img, pattern) {
452
+ return new FillStylePattern(img, pattern);
453
+ }
454
+
455
+ createLinearGradient(x0, y0, x1, y1) {
456
+ return new FillStyleLinearGradient(x0, y0, x1, y1);
457
+ }
458
+
459
+ createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
460
+ return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1);
461
+ };
462
+
463
+ strokeRect(x, y, w, h) {
464
+ this._drawCommands = this._drawCommands.concat('s' + x + ',' + y + ',' + w + ',' + h + ';');
465
+ }
466
+
467
+
468
+ clearRect(x, y, w, h) {
469
+ this._drawCommands = this._drawCommands.concat('c' + x + ',' + y + ',' + w
470
+ + ',' + h + ';');
471
+ }
472
+
473
+ clip() {
474
+ this._drawCommands = this._drawCommands.concat('p;');
475
+ }
476
+
477
+ resetClip() {
478
+ this._drawCommands = this._drawCommands.concat('q;');
479
+ }
480
+
481
+ closePath() {
482
+ this._drawCommands = this._drawCommands.concat('o;');
483
+ }
484
+
485
+ moveTo(x, y) {
486
+ this._drawCommands = this._drawCommands.concat('g' + x.toFixed(2) + ',' + y.toFixed(2) + ';');
487
+ }
488
+
489
+ lineTo(x, y) {
490
+ this._drawCommands = this._drawCommands.concat('i' + x.toFixed(2) + ',' + y.toFixed(2) + ';');
491
+ }
492
+
493
+ quadraticCurveTo = function(cpx, cpy, x, y) {
494
+ this._drawCommands = this._drawCommands.concat('u' + cpx + ',' + cpy + ',' + x + ',' + y + ';');
495
+ }
496
+
497
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, ) {
498
+ this._drawCommands = this._drawCommands.concat(
499
+ 'z' + cp1x.toFixed(2) + ',' + cp1y.toFixed(2) + ',' + cp2x.toFixed(2) + ',' + cp2y.toFixed(2) + ',' +
500
+ x.toFixed(2) + ',' + y.toFixed(2) + ';');
501
+ }
502
+
503
+ arcTo(x1, y1, x2, y2, radius) {
504
+ this._drawCommands = this._drawCommands.concat('h' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' + radius + ';');
505
+ }
506
+
507
+ beginPath() {
508
+ this._drawCommands = this._drawCommands.concat('b;');
509
+ }
510
+
511
+
512
+ fillRect(x, y, w, h) {
513
+ this._drawCommands = this._drawCommands.concat('n' + x + ',' + y + ',' + w
514
+ + ',' + h + ';');
515
+ }
516
+
517
+ rect(x, y, w, h) {
518
+ this._drawCommands = this._drawCommands.concat('w' + x + ',' + y + ',' + w + ',' + h + ';');
519
+ }
520
+
521
+ fill() {
522
+ this._drawCommands = this._drawCommands.concat('L;');
523
+ }
524
+
525
+ stroke(path) {
526
+ this._drawCommands = this._drawCommands.concat('x;');
527
+ }
528
+
529
+ arc(x, y, radius, startAngle, endAngle, anticlockwise) {
530
+ let ianticlockwise = 0;
531
+ if (anticlockwise) {
532
+ ianticlockwise = 1;
533
+ }
534
+
535
+ this._drawCommands = this._drawCommands.concat(
536
+ 'y' + x.toFixed(2) + ',' + y.toFixed(2) + ','
537
+ + radius.toFixed(2) + ',' + startAngle + ',' + endAngle + ',' + ianticlockwise
538
+ + ';'
539
+ );
540
+ }
541
+
542
+ fillText(text, x, y) {
543
+ let tmptext = text.replace(/!/g, '!!');
544
+ tmptext = tmptext.replace(/,/g, '!,');
545
+ tmptext = tmptext.replace(/;/g, '!;');
546
+ this._drawCommands = this._drawCommands.concat('T' + tmptext + ',' + x + ',' + y + ',0.0;');
547
+ }
548
+
549
+ strokeText = function(text, x, y) {
550
+ let tmptext = text.replace(/!/g, '!!');
551
+ tmptext = tmptext.replace(/,/g, '!,');
552
+ tmptext = tmptext.replace(/;/g, '!;');
553
+ this._drawCommands = this._drawCommands.concat('U' + tmptext + ',' + x + ',' + y + ',0.0;');
554
+ }
555
+
556
+ measureText = function(text) {
557
+ cancelAnimationFrame(this._canvas._renderLoopId);
558
+ this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
559
+ let tmpMeasure = CanvasRenderingContext2D.GBridge.callNative(
560
+ this.componentId,
561
+ 'V' + text + ';',
562
+ false,
563
+ '2d',
564
+ 'sync',
565
+ 'execWithoutDisplay',
566
+ );
567
+ this._canvas._renderLoopId = requestAnimationFrame(this._canvas._renderLoop.bind(this._canvas));
568
+
569
+ let textMetrics = {
570
+ width: 37.343750,
571
+ actualBoundingBoxAscent: 18.4,
572
+ actualBoundingBoxDescent: 5.6,
573
+ };
574
+ try {
575
+ const {width, height, actualBoundingBoxAscent, actualBoundingBoxDescent} = JSON.parse(tmpMeasure);
576
+ textMetrics.width = width;
577
+ if (actualBoundingBoxAscent > 0 && actualBoundingBoxDescent > 0) {
578
+ textMetrics.actualBoundingBoxAscent = actualBoundingBoxAscent;
579
+ textMetrics.actualBoundingBoxDescent = actualBoundingBoxDescent;
580
+ } else {
581
+ textMetrics.actualBoundingBoxAscent = height * 1.15 / 1.5;
582
+ textMetrics.actualBoundingBoxDescent = height - textMetrics.actualBoundingBoxAscent;
583
+ }
584
+ } catch (err) {
585
+ __DEV__ && console.warn('measureText parse error:' + tmpMeasure);
586
+ }
587
+
588
+ return textMetrics;
589
+ }
590
+
591
+ isPointInPath = function(x, y) {
592
+ throw new Error('GCanvas not supported yet');
593
+ }
594
+
595
+ drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
596
+ const numArgs = arguments.length;
597
+ let srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH;
598
+
599
+ if (numArgs === 3) {
600
+ srcX = 0;
601
+ srcY = 0;
602
+ srcW = image.width;
603
+ srcH = image.height;
604
+ dstX = parseFloat(sx) || 0.0;
605
+ dstY = parseFloat(sy) || 0.0;
606
+ dstW = image.width;
607
+ dstH = image.height;
608
+ } else if (numArgs === 5) {
609
+ srcX = 0;
610
+ srcY = 0;
611
+ srcW = image.width;
612
+ srcH = image.height;
613
+ dstX = parseFloat(sx) || 0.0;
614
+ dstY = parseFloat(sy) || 0.0;
615
+ dstW = parseInt(sw) || image.width;
616
+ dstH = parseInt(sh) || image.height;
617
+ } else if (numArgs === 9) {
618
+ srcX = parseFloat(sx) || 0.0;
619
+ srcY = parseFloat(sy) || 0.0;
620
+ srcW = parseInt(sw) || image.width;
621
+ srcH = parseInt(sh) || image.height;
622
+ dstX = parseFloat(dx) || 0.0;
623
+ dstY = parseFloat(dy) || 0.0;
624
+ dstW = parseInt(dw) || image.width;
625
+ dstH = parseInt(dh) || image.height;
626
+ }
627
+
628
+ const imageIsCanvas = image.hasOwnProperty('nodeName') && image.nodeName.toLowerCase() === 'canvas';
629
+ if (imageIsCanvas) {
630
+ if (!image._context) {
631
+ return;
632
+ }
633
+
634
+ // If use 'execWithDisplay' here, will cause 2nd imageIsCanvas flick of
635
+ // https://github.com/flyskywhy/GCanvasRNExamples/blob/master/app/components/AudioWaveSurfer.js
636
+ //
637
+ // Even if use flushJsCommands2CallNative here, will cause imageIsCanvas not display on screen if
638
+ // not follow by other ctx.some().
639
+ this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
640
+
641
+ image._context.flushJsCommands2CallNative('sync');
642
+
643
+ let sCanvasId = image.id;
644
+ // even README.md said "despite of values into `canvas.width =` and `canvas.height =`",
645
+ // because canvas of sCanvasId is mostly comes from document.createElement('canvas')
646
+ // that just like offscreen canvas, so it will use style {position: 'absolute'} and
647
+ // that means it's clientWidth will not change (to avoid re-render as offscreen), so
648
+ // it's meaningful to use canvas.width usually be changed after document.createElement('canvas')
649
+ let sCanvasWidth = image.width;
650
+ let sCanvasHeight = image.height;
651
+ CanvasRenderingContext2D.GBridge.drawCanvas2Canvas({
652
+ srcComponentId: sCanvasId,
653
+ dstComponentId: this.componentId,
654
+ tw: sCanvasWidth,
655
+ th: sCanvasHeight,
656
+ sx: srcX,
657
+ sy: srcY,
658
+ sw: srcW,
659
+ sh: srcH,
660
+ dx: dstX,
661
+ dy: dstY,
662
+ dw: dstW,
663
+ dh: dstH,
664
+ });
665
+ } else {
666
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
667
+ this._drawCommands += 'd' + image._id + ','
668
+ + srcX + ',' + srcY + ',' + srcW + ',' + srcH + ','
669
+ + dstX + ',' + dstY + ',' + dstW + ',' + dstH + ';';
670
+
671
+ // If use 'execWithDisplay' here, will cause 2nd image flick with 'forceCanvas = true' of
672
+ // https://github.com/flyskywhy/GCanvasRNExamples/blob/master/app/components/Pixi.js ,
673
+ // because in '2d' not 'webgl' PIXI, every PIXI app tick will let all image sprites be drawImage().
674
+ //
675
+ // Even if use flushJsCommands2CallNative here, will cause image not display on screen if
676
+ // not follow by other ctx.some().
677
+ // this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
678
+ }
679
+ }
680
+
681
+ createImageData(widthOrImagedata, height) {
682
+ if (arguments.length === 2) {
683
+ return new ImageData(widthOrImagedata, height);
684
+ } else {
685
+ return new ImageData(widthOrImagedata.width, widthOrImagedata.height);
686
+ }
687
+ }
688
+
689
+ // not a Web canvas API, used by @flyskywhy/react-native-gcanvas
690
+ flushJsCommands2CallNative(
691
+ methodType = 'async',
692
+ optionType = 'execWithDisplay',
693
+ ) {
694
+ const commands = this._drawCommands;
695
+ this._drawCommands = ''; // let cmds cache be empty
696
+
697
+ if (commands !== '') {
698
+ CanvasRenderingContext2D.GBridge.callNative(
699
+ this.componentId,
700
+ commands,
701
+ false,
702
+ '2d',
703
+ methodType,
704
+ optionType,
705
+ );
706
+ }
707
+ }
708
+
709
+ // no need ctx.getImageData(x * PixelRatio.get(), y * PixelRatio.get(), w * PixelRatio.get(), h * PixelRatio.get())
710
+ // e.g. ctx.getImageData(0, 0, 2, 2) will return a `w === 2, h === 2` ImageData
711
+ // since PixelsSampler() in GetImageData() of core/src/gcanvas/GCanvas2dContext.cpp
712
+ // will scale the image automatically
713
+ getImageData(sx, sy, sw, sh) {
714
+ // if not stop _renderLoop(), sometimes will cause display issue with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN
715
+ cancelAnimationFrame(this._canvas._renderLoopId);
716
+ // If use 'async' here, will cause last commands be executed after getImageData's 'sync'.
717
+ if (this._drawCommands.includes(';T') || this._drawCommands.startsWith('T')) {
718
+ // Use 'sync' + 'execWithDisplay' here to make sure last graphics by fillText() be generated
719
+ // before execute getImageData's 'R', ref to context.fillText in
720
+ // https://github.com/flyskywhy/react-native-runescape-text/blob/main/src/classes/Motion.js
721
+ // TODO: more commands other than 'T' if someone issue on github
722
+ this.flushJsCommands2CallNative('sync', 'execWithDisplay');
723
+ } else {
724
+ // If use 'execWithDisplay' here, will cause low JS FPS on iOS with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN ,
725
+ // because 'lightener' will call getImageData frequently while moving finger, will cause setNeedsDisplay() then
726
+ // drawInRect() be invoked more than 1 times in 16ms e.g. 1times/1ms thus cause low JS FPS!
727
+ this.flushJsCommands2CallNative('sync', 'execWithoutDisplay');
728
+ }
729
+ // now can getImageData from last generated (even not displayed) graphics, otherwise will `return new ImageData(w, h)`
730
+ // thus `this.ctx.putImageData()` has no effect with the first 'Click me to draw some on canvas' in README.md
731
+
732
+ let x = sx;
733
+ let y = sy;
734
+ let w = sw;
735
+ let h = sh;
736
+ if (sx + sw > this.canvas.clientWidth) {
737
+ x = 0;
738
+ w = this.canvas.clientWidth;
739
+ }
740
+ if (sy + sh > this.canvas.clientHeight) {
741
+ y = 0;
742
+ h = this.canvas.clientHeight;
743
+ }
744
+
745
+ let base64Data = CanvasRenderingContext2D.GBridge.callNative(
746
+ this.componentId,
747
+ 'R' + x + ',' + y + ',' + w + ',' + h + ';',
748
+ false,
749
+ '2d',
750
+ 'sync',
751
+ 'execWithoutDisplay',
752
+ );
753
+
754
+ // start _renderLoop() again, and thus display last generated graphics on screen
755
+ // TODO: allow empty commands invoke callNative() once in flushJsCommands2CallNative()
756
+ // while start _renderLoop() just in case no commands after getImageData's 'R'
757
+ // thus no display
758
+ // TODO: this.flushJsCommands2CallNative('sync', 'execWithDisplay') on Android
759
+ // test with 'lightener' tool of https://github.com/flyskywhy/PixelShapeRN
760
+ // to see if low JS FPS
761
+ this._canvas._renderLoopId = requestAnimationFrame(this._canvas._renderLoop.bind(this._canvas));
762
+
763
+ if (base64Data === '') {
764
+ console.warn('getImageData: not good to be here, should refactor source code somewhere');
765
+ return new ImageData(w, h);
766
+ }
767
+
768
+ return new ImageData(new Uint8ClampedArray(base64.toByteArray(base64Data)), w, h);
769
+ }
770
+
771
+ // no need ctx.getImageData(imageData, x * PixelRatio.get(), y * PixelRatio.get())
772
+ // and the imageData is also not `w * PixelRatio.get(), h * PixelRatio.get()`
773
+ putImageData(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
774
+ const base64Data = base64.fromByteArray(imageData.data);
775
+ let tw = imageData.width; // textureWidth
776
+ let th = imageData.height;
777
+
778
+ let sx, sy, sw, sh;
779
+
780
+ if (arguments.length === 3) {
781
+ sx = 0;
782
+ sy = 0;
783
+ sw = tw;
784
+ sh = th;
785
+ } else {
786
+ sx = dirtyX;
787
+ sy = dirtyY;
788
+ // dirtyWidth in https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
789
+ // means the width of the (src) image data
790
+ sw = dirtyWidth;
791
+ sh = dirtyHeight;
792
+ }
793
+
794
+ if (this._canvas._isAutoClearRectBeforePutImageData) {
795
+ this.clearRect(x + sx, y + sy, sw, sh);
796
+ }
797
+
798
+ this._drawCommands = this._drawCommands.concat('P' + base64Data + ',' + tw + ',' + th + ','
799
+ + x + ',' + y + ',' + sx + ',' + sy + ',' + sw + ',' + sh + ';');
800
+ }
801
+ }